@7365admin1/module-hygiene 4.30.3 → 4.32.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 +42 -10
- package/dist/index.js +692 -291
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +692 -291
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/escape-regex.util.test.mjs +26 -0
- package/test/schedule-followups.test.mjs +24 -0
- package/test/schedule-task-rules.test.mjs +185 -0
package/dist/index.js
CHANGED
|
@@ -1002,6 +1002,11 @@ function capitalizeText(text) {
|
|
|
1002
1002
|
return text.toLowerCase().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
1005
|
+
// src/utils/escape-regex.util.ts
|
|
1006
|
+
function escapeRegex(value) {
|
|
1007
|
+
return String(value ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1005
1010
|
// src/repositories/hygiene-area.repository.ts
|
|
1006
1011
|
function useAreaRepo() {
|
|
1007
1012
|
const db = import_node_server_utils4.useAtlas.getDb();
|
|
@@ -1098,7 +1103,7 @@ function useAreaRepo() {
|
|
|
1098
1103
|
cacheOptions.type = type;
|
|
1099
1104
|
}
|
|
1100
1105
|
if (search) {
|
|
1101
|
-
query.$or = [{ name: { $regex: search, $options: "i" } }];
|
|
1106
|
+
query.$or = [{ name: { $regex: escapeRegex(search), $options: "i" } }];
|
|
1102
1107
|
cacheOptions.search = search;
|
|
1103
1108
|
}
|
|
1104
1109
|
const cacheKey = (0, import_node_server_utils4.makeCacheKey)(namespace_collection, cacheOptions);
|
|
@@ -1560,7 +1565,7 @@ function useUnitRepository() {
|
|
|
1560
1565
|
throw new import_node_server_utils7.BadRequestError("Invalid site ID format.");
|
|
1561
1566
|
}
|
|
1562
1567
|
if (search) {
|
|
1563
|
-
query.$or = [{ name: { $regex: search, $options: "i" } }];
|
|
1568
|
+
query.$or = [{ name: { $regex: escapeRegex(search), $options: "i" } }];
|
|
1564
1569
|
cacheOptions.search = search;
|
|
1565
1570
|
}
|
|
1566
1571
|
const cacheKey = (0, import_node_server_utils7.makeCacheKey)(namespace_collection, cacheOptions);
|
|
@@ -3228,7 +3233,9 @@ var areaChecklistSchema = import_joi9.default.object({
|
|
|
3228
3233
|
}).required()
|
|
3229
3234
|
).min(1).required(),
|
|
3230
3235
|
isScheduleTask: import_joi9.default.boolean().optional().default(false),
|
|
3231
|
-
scheduleTaskId: import_joi9.default.string().hex().optional().allow(null)
|
|
3236
|
+
scheduleTaskId: import_joi9.default.string().hex().optional().allow(null),
|
|
3237
|
+
scheduleTaskTitle: import_joi9.default.string().optional().allow("", null),
|
|
3238
|
+
scheduleTaskTime: import_joi9.default.string().optional().allow("", null)
|
|
3232
3239
|
}).required()
|
|
3233
3240
|
).optional().default([])
|
|
3234
3241
|
});
|
|
@@ -3258,6 +3265,8 @@ function MAreaChecklist(value) {
|
|
|
3258
3265
|
set: checklistItem.set,
|
|
3259
3266
|
isScheduleTask: checklistItem.isScheduleTask ?? false,
|
|
3260
3267
|
scheduleTaskId: checklistItem.scheduleTaskId ?? void 0,
|
|
3268
|
+
scheduleTaskTitle: checklistItem.scheduleTaskTitle ?? void 0,
|
|
3269
|
+
scheduleTaskTime: checklistItem.scheduleTaskTime ?? void 0,
|
|
3261
3270
|
units: checklistItem.units.map((unit) => ({
|
|
3262
3271
|
unit: new import_mongodb8.ObjectId(unit.unit),
|
|
3263
3272
|
name: unit.name,
|
|
@@ -3740,6 +3749,9 @@ function useAreaChecklistRepo() {
|
|
|
3740
3749
|
isScheduleTask: {
|
|
3741
3750
|
$ifNull: ["$checklist.isScheduleTask", false]
|
|
3742
3751
|
},
|
|
3752
|
+
scheduleTaskId: { $ifNull: ["$checklist.scheduleTaskId", null] },
|
|
3753
|
+
scheduleTaskTitle: { $ifNull: ["$checklist.scheduleTaskTitle", null] },
|
|
3754
|
+
scheduleTaskTime: { $ifNull: ["$checklist.scheduleTaskTime", null] },
|
|
3743
3755
|
unit: "$checklist.units.unit",
|
|
3744
3756
|
name: "$checklist.units.name",
|
|
3745
3757
|
approve: "$checklist.units.approve",
|
|
@@ -3776,6 +3788,9 @@ function useAreaChecklistRepo() {
|
|
|
3776
3788
|
$group: {
|
|
3777
3789
|
_id: "$set",
|
|
3778
3790
|
isScheduleTask: { $first: "$isScheduleTask" },
|
|
3791
|
+
scheduleTaskId: { $first: "$scheduleTaskId" },
|
|
3792
|
+
scheduleTaskTitle: { $first: "$scheduleTaskTitle" },
|
|
3793
|
+
scheduleTaskTime: { $first: "$scheduleTaskTime" },
|
|
3779
3794
|
units: {
|
|
3780
3795
|
$push: {
|
|
3781
3796
|
unit: "$unit",
|
|
@@ -3796,6 +3811,9 @@ function useAreaChecklistRepo() {
|
|
|
3796
3811
|
_id: 0,
|
|
3797
3812
|
set: "$_id",
|
|
3798
3813
|
isScheduleTask: 1,
|
|
3814
|
+
scheduleTaskId: 1,
|
|
3815
|
+
scheduleTaskTitle: 1,
|
|
3816
|
+
scheduleTaskTime: 1,
|
|
3799
3817
|
units: 1
|
|
3800
3818
|
}
|
|
3801
3819
|
},
|
|
@@ -3927,6 +3945,8 @@ function useAreaChecklistRepo() {
|
|
|
3927
3945
|
isScheduleTask: {
|
|
3928
3946
|
$ifNull: ["$checklist.isScheduleTask", false]
|
|
3929
3947
|
},
|
|
3948
|
+
scheduleTaskTitle: { $ifNull: ["$checklist.scheduleTaskTitle", null] },
|
|
3949
|
+
scheduleTaskTime: { $ifNull: ["$checklist.scheduleTaskTime", null] },
|
|
3930
3950
|
scheduleTaskId: {
|
|
3931
3951
|
$ifNull: ["$checklist.scheduleTaskId", null]
|
|
3932
3952
|
},
|
|
@@ -3967,6 +3987,8 @@ function useAreaChecklistRepo() {
|
|
|
3967
3987
|
$group: {
|
|
3968
3988
|
_id: "$set",
|
|
3969
3989
|
isScheduleTask: { $first: "$isScheduleTask" },
|
|
3990
|
+
scheduleTaskTitle: { $first: "$scheduleTaskTitle" },
|
|
3991
|
+
scheduleTaskTime: { $first: "$scheduleTaskTime" },
|
|
3970
3992
|
scheduleTaskId: { $first: "$scheduleTaskId" },
|
|
3971
3993
|
remarks: { $first: "$remarks" },
|
|
3972
3994
|
attachment: { $first: "$attachment" },
|
|
@@ -3991,6 +4013,8 @@ function useAreaChecklistRepo() {
|
|
|
3991
4013
|
_id: 0,
|
|
3992
4014
|
set: "$_id",
|
|
3993
4015
|
isScheduleTask: 1,
|
|
4016
|
+
scheduleTaskTitle: 1,
|
|
4017
|
+
scheduleTaskTime: 1,
|
|
3994
4018
|
scheduleTaskId: 1,
|
|
3995
4019
|
remarks: "$remarks",
|
|
3996
4020
|
attachment: "$attachment",
|
|
@@ -4466,6 +4490,265 @@ function useAreaChecklistRepo() {
|
|
|
4466
4490
|
};
|
|
4467
4491
|
}
|
|
4468
4492
|
|
|
4493
|
+
// src/utils/schedule-task-rules.util.ts
|
|
4494
|
+
var import_joi10 = __toESM(require("joi"));
|
|
4495
|
+
var REPEATS = ["none", "daily", "weekdays", "weekly", "monthly"];
|
|
4496
|
+
var SGT_MS = 8 * 60 * 60 * 1e3;
|
|
4497
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
4498
|
+
var TIME_RE = /^([0-1]\d|2[0-3]):([0-5]\d)$/;
|
|
4499
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
4500
|
+
function sgtParts(now) {
|
|
4501
|
+
const s = new Date(now.getTime() + SGT_MS);
|
|
4502
|
+
const date = `${s.getUTCFullYear()}-${pad(s.getUTCMonth() + 1)}-${pad(s.getUTCDate())}`;
|
|
4503
|
+
return { date, minutes: s.getUTCHours() * 60 + s.getUTCMinutes(), weekday: s.getUTCDay() };
|
|
4504
|
+
}
|
|
4505
|
+
function isRealDate(value) {
|
|
4506
|
+
if (typeof value !== "string" || !DATE_RE.test(value))
|
|
4507
|
+
return false;
|
|
4508
|
+
const [y, m, d] = value.split("-").map(Number);
|
|
4509
|
+
const t = new Date(Date.UTC(y, m - 1, d));
|
|
4510
|
+
return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
|
|
4511
|
+
}
|
|
4512
|
+
var toMinutes = (time) => {
|
|
4513
|
+
const [h, m] = time.split(":").map(Number);
|
|
4514
|
+
return h * 60 + m;
|
|
4515
|
+
};
|
|
4516
|
+
var weekdayOf = (date) => {
|
|
4517
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
4518
|
+
return new Date(Date.UTC(y, m - 1, d)).getUTCDay();
|
|
4519
|
+
};
|
|
4520
|
+
var daysInMonth = (date) => {
|
|
4521
|
+
const [y, m] = date.split("-").map(Number);
|
|
4522
|
+
return new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
4523
|
+
};
|
|
4524
|
+
var addDays = (date, n) => {
|
|
4525
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
4526
|
+
const t = new Date(Date.UTC(y, m - 1, d + n));
|
|
4527
|
+
return `${t.getUTCFullYear()}-${pad(t.getUTCMonth() + 1)}-${pad(t.getUTCDate())}`;
|
|
4528
|
+
};
|
|
4529
|
+
var isRepeating = (task) => Boolean(task.repeat) && task.repeat !== "none";
|
|
4530
|
+
function occursOn(task, date) {
|
|
4531
|
+
if (task.endDate && date > task.endDate)
|
|
4532
|
+
return false;
|
|
4533
|
+
if (!isRepeating(task))
|
|
4534
|
+
return Array.isArray(task.dates) && task.dates.includes(date);
|
|
4535
|
+
if (task.startDate && date < task.startDate)
|
|
4536
|
+
return false;
|
|
4537
|
+
const weekday = weekdayOf(date);
|
|
4538
|
+
switch (task.repeat) {
|
|
4539
|
+
case "daily":
|
|
4540
|
+
return true;
|
|
4541
|
+
case "weekdays":
|
|
4542
|
+
return weekday >= 1 && weekday <= 5;
|
|
4543
|
+
case "weekly":
|
|
4544
|
+
return Array.isArray(task.daysOfWeek) && task.daysOfWeek.includes(weekday);
|
|
4545
|
+
case "monthly": {
|
|
4546
|
+
const wanted = task.dayOfMonth ?? (task.startDate ? Number(task.startDate.slice(8, 10)) : 1);
|
|
4547
|
+
return Number(date.slice(8, 10)) === Math.min(wanted, daysInMonth(date));
|
|
4548
|
+
}
|
|
4549
|
+
default:
|
|
4550
|
+
return false;
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
function savedTodayAt(task, today) {
|
|
4554
|
+
const stamp = task.updatedAt || task.createdAt;
|
|
4555
|
+
if (!stamp)
|
|
4556
|
+
return null;
|
|
4557
|
+
const saved = new Date(stamp);
|
|
4558
|
+
if (Number.isNaN(saved.getTime()))
|
|
4559
|
+
return null;
|
|
4560
|
+
const p = sgtParts(saved);
|
|
4561
|
+
return p.date === today ? p.minutes : null;
|
|
4562
|
+
}
|
|
4563
|
+
function shouldRunNow(task, now = /* @__PURE__ */ new Date()) {
|
|
4564
|
+
if (task.paused || !TIME_RE.test(task.time ?? ""))
|
|
4565
|
+
return false;
|
|
4566
|
+
const { date, minutes } = sgtParts(now);
|
|
4567
|
+
if (task.lastRunDate === date || !occursOn(task, date))
|
|
4568
|
+
return false;
|
|
4569
|
+
const due = toMinutes(task.time);
|
|
4570
|
+
if (minutes < due)
|
|
4571
|
+
return false;
|
|
4572
|
+
const saved = savedTodayAt(task, date);
|
|
4573
|
+
return !(saved !== null && saved >= due);
|
|
4574
|
+
}
|
|
4575
|
+
var iso = (date, time) => `${date}T${time}:00+08:00`;
|
|
4576
|
+
function upcoming(task, now) {
|
|
4577
|
+
if (typeof task.time !== "string" || !TIME_RE.test(task.time))
|
|
4578
|
+
return null;
|
|
4579
|
+
const { date: today, minutes } = sgtParts(now);
|
|
4580
|
+
const due = toMinutes(task.time);
|
|
4581
|
+
if (occursOn(task, today) && (minutes < due || shouldRunNow({ ...task, paused: false }, now))) {
|
|
4582
|
+
return iso(today, task.time);
|
|
4583
|
+
}
|
|
4584
|
+
if (!isRepeating(task)) {
|
|
4585
|
+
const later = (task.dates ?? []).filter((d) => d > today && (!task.endDate || d <= task.endDate)).sort();
|
|
4586
|
+
return later.length ? iso(later[0], task.time) : null;
|
|
4587
|
+
}
|
|
4588
|
+
const from = task.startDate && task.startDate > today ? task.startDate : today;
|
|
4589
|
+
for (let i = from === today ? 1 : 0; i <= 400; i++) {
|
|
4590
|
+
const d = addDays(from, i);
|
|
4591
|
+
if (task.endDate && d > task.endDate)
|
|
4592
|
+
return null;
|
|
4593
|
+
if (occursOn(task, d))
|
|
4594
|
+
return iso(d, task.time);
|
|
4595
|
+
}
|
|
4596
|
+
return null;
|
|
4597
|
+
}
|
|
4598
|
+
function nextRun(task, now = /* @__PURE__ */ new Date()) {
|
|
4599
|
+
return task.paused ? null : upcoming(task, now);
|
|
4600
|
+
}
|
|
4601
|
+
function scheduleState(task, now = /* @__PURE__ */ new Date()) {
|
|
4602
|
+
if (task.paused)
|
|
4603
|
+
return "paused";
|
|
4604
|
+
return upcoming(task, now) ? "active" : "ended";
|
|
4605
|
+
}
|
|
4606
|
+
function lastRun(task) {
|
|
4607
|
+
return task.lastRunDate && TIME_RE.test(task.time ?? "") ? iso(task.lastRunDate, task.time) : null;
|
|
4608
|
+
}
|
|
4609
|
+
function scheduleDisplay(task, now = /* @__PURE__ */ new Date()) {
|
|
4610
|
+
try {
|
|
4611
|
+
return {
|
|
4612
|
+
nextRun: nextRun(task, now),
|
|
4613
|
+
lastRun: lastRun(task),
|
|
4614
|
+
scheduleStatus: scheduleState(task, now)
|
|
4615
|
+
};
|
|
4616
|
+
} catch {
|
|
4617
|
+
return { nextRun: null, lastRun: null, scheduleStatus: null };
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
function validateScheduleRules(task, now = /* @__PURE__ */ new Date()) {
|
|
4621
|
+
const repeating = isRepeating(task);
|
|
4622
|
+
const { date: today, minutes } = sgtParts(now);
|
|
4623
|
+
if (repeating) {
|
|
4624
|
+
if (!task.startDate)
|
|
4625
|
+
return "Please choose a start date for a repeating schedule.";
|
|
4626
|
+
if (task.repeat === "weekly" && !(task.daysOfWeek && task.daysOfWeek.length)) {
|
|
4627
|
+
return "Pick at least one weekday for a weekly repeat.";
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
const start = repeating ? task.startDate : [...task.dates ?? []].sort()[0];
|
|
4631
|
+
if (task.endDate && start && start > task.endDate) {
|
|
4632
|
+
return "The end date must be on or after the start date.";
|
|
4633
|
+
}
|
|
4634
|
+
if (task.endDate && task.endDate < today)
|
|
4635
|
+
return "The end date has already passed.";
|
|
4636
|
+
if (!repeating) {
|
|
4637
|
+
const dates = (task.dates ?? []).filter((d) => !task.endDate || d <= task.endDate);
|
|
4638
|
+
if (dates.length && dates.every((d) => d < today)) {
|
|
4639
|
+
return "All the dates you picked have already passed. Pick today or a later date.";
|
|
4640
|
+
}
|
|
4641
|
+
if (dates.length && dates.every((d) => d <= today) && dates.includes(today) && TIME_RE.test(task.time) && toMinutes(task.time) <= minutes) {
|
|
4642
|
+
return "That time has already passed today. Pick a later time or another date.";
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
if (!upcoming({ ...task, paused: false, updatedAt: now }, now)) {
|
|
4646
|
+
return "This schedule would never run. Check the dates, end date and time.";
|
|
4647
|
+
}
|
|
4648
|
+
return null;
|
|
4649
|
+
}
|
|
4650
|
+
var scheduleDate = import_joi10.default.string().pattern(DATE_RE).custom((value, helpers) => isRealDate(value) ? value : helpers.error("date.invalid")).messages({
|
|
4651
|
+
"string.pattern.base": "Dates must look like 2026-10-21.",
|
|
4652
|
+
"string.empty": "Please choose a date.",
|
|
4653
|
+
"date.invalid": "That date does not exist. Dates must look like 2026-10-21.",
|
|
4654
|
+
"string.base": "Dates must look like 2026-10-21."
|
|
4655
|
+
});
|
|
4656
|
+
var REPEATING = import_joi10.default.valid("daily", "weekdays", "weekly", "monthly").required();
|
|
4657
|
+
var scheduleTaskFields = {
|
|
4658
|
+
title: import_joi10.default.string().trim().required().messages({
|
|
4659
|
+
"any.required": "Please enter a title.",
|
|
4660
|
+
"string.empty": "Please enter a title.",
|
|
4661
|
+
"string.base": "Please enter a title."
|
|
4662
|
+
}),
|
|
4663
|
+
time: import_joi10.default.string().pattern(TIME_RE).required().messages({
|
|
4664
|
+
"any.required": "Please choose a time.",
|
|
4665
|
+
"string.empty": "Please choose a time.",
|
|
4666
|
+
"string.pattern.base": "Time must be in 24-hour format like 08:30.",
|
|
4667
|
+
"string.base": "Time must be in 24-hour format like 08:30."
|
|
4668
|
+
}),
|
|
4669
|
+
dates: import_joi10.default.when("repeat", {
|
|
4670
|
+
is: REPEATING,
|
|
4671
|
+
then: import_joi10.default.array().items(scheduleDate).optional().default([]),
|
|
4672
|
+
otherwise: import_joi10.default.array().min(1).items(scheduleDate).required()
|
|
4673
|
+
}).messages({
|
|
4674
|
+
"any.required": "Pick at least one date, or choose a repeat.",
|
|
4675
|
+
"array.min": "Pick at least one date, or choose a repeat.",
|
|
4676
|
+
"array.base": "Dates must be a list of dates."
|
|
4677
|
+
}),
|
|
4678
|
+
description: import_joi10.default.string().optional().allow("", null),
|
|
4679
|
+
areas: import_joi10.default.array().min(1).items(
|
|
4680
|
+
import_joi10.default.object({
|
|
4681
|
+
name: import_joi10.default.string().required(),
|
|
4682
|
+
value: import_joi10.default.any().required()
|
|
4683
|
+
})
|
|
4684
|
+
).required().messages({
|
|
4685
|
+
"any.required": "Pick at least one area.",
|
|
4686
|
+
"array.min": "Pick at least one area.",
|
|
4687
|
+
"array.base": "Pick at least one area."
|
|
4688
|
+
}),
|
|
4689
|
+
repeat: import_joi10.default.string().valid(...REPEATS).optional().allow("", null).messages({ "any.only": "Repeat must be one of: none, daily, weekdays, weekly, monthly." }),
|
|
4690
|
+
daysOfWeek: import_joi10.default.array().items(import_joi10.default.number().integer().min(0).max(6)).unique().optional().allow(null).messages({
|
|
4691
|
+
"number.base": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4692
|
+
"number.min": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4693
|
+
"number.max": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4694
|
+
"number.integer": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4695
|
+
"array.unique": "Each weekday can only be picked once."
|
|
4696
|
+
}),
|
|
4697
|
+
dayOfMonth: import_joi10.default.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." }),
|
|
4698
|
+
startDate: scheduleDate.optional().allow("", null),
|
|
4699
|
+
endDate: scheduleDate.optional().allow("", null),
|
|
4700
|
+
paused: import_joi10.default.boolean().optional().allow(null).messages({ "boolean.base": "Paused must be true or false." })
|
|
4701
|
+
};
|
|
4702
|
+
var OPTIONAL_KEYS = ["repeat", "daysOfWeek", "dayOfMonth", "startDate", "endDate", "paused"];
|
|
4703
|
+
var isBlank = (v) => v === "" || v === null;
|
|
4704
|
+
function cleanScheduleInput(value) {
|
|
4705
|
+
const out = { ...value };
|
|
4706
|
+
for (const key of OPTIONAL_KEYS)
|
|
4707
|
+
if (isBlank(out[key]))
|
|
4708
|
+
delete out[key];
|
|
4709
|
+
if (out.repeat === "none") {
|
|
4710
|
+
delete out.daysOfWeek;
|
|
4711
|
+
delete out.dayOfMonth;
|
|
4712
|
+
delete out.startDate;
|
|
4713
|
+
}
|
|
4714
|
+
return out;
|
|
4715
|
+
}
|
|
4716
|
+
var TIMING_KEYS = ["time", "dates", "repeat", "daysOfWeek", "dayOfMonth", "startDate", "endDate"];
|
|
4717
|
+
function splitScheduleUpdate(payload) {
|
|
4718
|
+
const set = {};
|
|
4719
|
+
const unset = [];
|
|
4720
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
4721
|
+
if (OPTIONAL_KEYS.includes(key) && isBlank(value))
|
|
4722
|
+
unset.push(key);
|
|
4723
|
+
else if (value !== void 0)
|
|
4724
|
+
set[key] = value;
|
|
4725
|
+
}
|
|
4726
|
+
if (set.repeat === "none") {
|
|
4727
|
+
for (const key of ["daysOfWeek", "dayOfMonth", "startDate"]) {
|
|
4728
|
+
if (!unset.includes(key))
|
|
4729
|
+
unset.push(key);
|
|
4730
|
+
delete set[key];
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
return { set, unset };
|
|
4734
|
+
}
|
|
4735
|
+
function regularSetsMissing(checklist, setCount) {
|
|
4736
|
+
const regular = (checklist ?? []).filter((c) => c.isScheduleTask !== true).length;
|
|
4737
|
+
return Math.max(0, setCount - regular);
|
|
4738
|
+
}
|
|
4739
|
+
var normTiming = (t) => ({
|
|
4740
|
+
time: t.time ?? null,
|
|
4741
|
+
dates: [...Array.isArray(t.dates) ? t.dates : []].sort(),
|
|
4742
|
+
repeat: !t.repeat || t.repeat === "none" ? "none" : t.repeat,
|
|
4743
|
+
daysOfWeek: [...Array.isArray(t.daysOfWeek) ? t.daysOfWeek : []].sort(),
|
|
4744
|
+
dayOfMonth: t.dayOfMonth ?? null,
|
|
4745
|
+
startDate: t.startDate || null,
|
|
4746
|
+
endDate: t.endDate || null
|
|
4747
|
+
});
|
|
4748
|
+
function timingChanged(stored, after) {
|
|
4749
|
+
return JSON.stringify(normTiming(stored)) !== JSON.stringify(normTiming(after));
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4469
4752
|
// src/services/hygiene-area-checklist.service.ts
|
|
4470
4753
|
function useAreaChecklistService() {
|
|
4471
4754
|
const { getAreasForChecklist } = useAreaRepo();
|
|
@@ -4533,10 +4816,10 @@ function useAreaChecklistService() {
|
|
|
4533
4816
|
`Area checklist for area ${area.name} exists but has no auto-gen sets. Inserting auto-gen sets.`
|
|
4534
4817
|
);
|
|
4535
4818
|
const setCount2 = Number(area.set) || 1;
|
|
4536
|
-
const
|
|
4537
|
-
if (
|
|
4819
|
+
const setsToAdd = regularSetsMissing(existing.checklist, setCount2);
|
|
4820
|
+
if (setsToAdd <= 0) {
|
|
4538
4821
|
import_node_server_utils18.logger.info(
|
|
4539
|
-
`Area checklist for area ${area.name} already has ${
|
|
4822
|
+
`Area checklist for area ${area.name} already has its ${setCount2} regular set(s). Skipping auto-gen sets.`
|
|
4540
4823
|
);
|
|
4541
4824
|
return existing._id;
|
|
4542
4825
|
}
|
|
@@ -4544,7 +4827,6 @@ function useAreaChecklistService() {
|
|
|
4544
4827
|
(max, c) => Math.max(max, c.set ?? 0),
|
|
4545
4828
|
0
|
|
4546
4829
|
) ?? 0;
|
|
4547
|
-
const setsToAdd = setCount2 - totalExistingSets;
|
|
4548
4830
|
const autoGenSets = Array.from(
|
|
4549
4831
|
{ length: setsToAdd },
|
|
4550
4832
|
(_, index) => ({
|
|
@@ -4705,7 +4987,7 @@ function useAreaChecklistService() {
|
|
|
4705
4987
|
}
|
|
4706
4988
|
|
|
4707
4989
|
// src/controllers/hygiene-area-checklist.controller.ts
|
|
4708
|
-
var
|
|
4990
|
+
var import_joi11 = __toESM(require("joi"));
|
|
4709
4991
|
var import_node_server_utils20 = require("@7365admin1/node-server-utils");
|
|
4710
4992
|
var import_core11 = require("@7365admin1/core");
|
|
4711
4993
|
|
|
@@ -4713,6 +4995,8 @@ var import_core11 = require("@7365admin1/core");
|
|
|
4713
4995
|
var import_puppeteer = require("puppeteer");
|
|
4714
4996
|
var import_node_server_utils19 = require("@7365admin1/node-server-utils");
|
|
4715
4997
|
var import_mongodb11 = require("mongodb");
|
|
4998
|
+
|
|
4999
|
+
// src/utils/checklist-pdf-html.util.ts
|
|
4716
5000
|
function escapeHtml(text) {
|
|
4717
5001
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4718
5002
|
}
|
|
@@ -4725,7 +5009,7 @@ function buildChecklistHtml(areaChecklists) {
|
|
|
4725
5009
|
const unitRows = (setItem.units || []).map(
|
|
4726
5010
|
(unit) => `
|
|
4727
5011
|
<tr>
|
|
4728
|
-
<td class="unit-name">${escapeHtml(unit.name || "")}</td>
|
|
5012
|
+
<td class="unit-name">${escapeHtml(unit.name || "")}${unit.approve || unit.reject ? "" : ' <span class="missed-label">Missed</span>'}</td>
|
|
4729
5013
|
<td class="icon-cell">${unit.reject ? '<span class="reject-icon">✗</span>' : ""}</td>
|
|
4730
5014
|
<td class="icon-cell">${unit.approve ? '<span class="approve-icon">✓</span>' : ""}</td>
|
|
4731
5015
|
</tr>`
|
|
@@ -4862,6 +5146,12 @@ function buildChecklistHtml(areaChecklists) {
|
|
|
4862
5146
|
font-size: 16px;
|
|
4863
5147
|
font-weight: bold;
|
|
4864
5148
|
}
|
|
5149
|
+
.missed-label {
|
|
5150
|
+
color: #b3261e;
|
|
5151
|
+
font-size: 11px;
|
|
5152
|
+
font-weight: bold;
|
|
5153
|
+
margin-left: 6px;
|
|
5154
|
+
}
|
|
4865
5155
|
.reject-icon {
|
|
4866
5156
|
color: #e74c3c;
|
|
4867
5157
|
font-size: 16px;
|
|
@@ -4877,6 +5167,8 @@ function buildChecklistHtml(areaChecklists) {
|
|
|
4877
5167
|
</body>
|
|
4878
5168
|
</html>`;
|
|
4879
5169
|
}
|
|
5170
|
+
|
|
5171
|
+
// src/services/hygiene-checklist-pdf.service.ts
|
|
4880
5172
|
function useChecklistPdfService() {
|
|
4881
5173
|
async function generateChecklistPdf(scheduleId) {
|
|
4882
5174
|
const db = import_node_server_utils19.useAtlas.getDb();
|
|
@@ -4946,9 +5238,9 @@ function useAreaChecklistController() {
|
|
|
4946
5238
|
site: req.params.site,
|
|
4947
5239
|
schedule: req.params.schedule
|
|
4948
5240
|
};
|
|
4949
|
-
const validation =
|
|
4950
|
-
site:
|
|
4951
|
-
schedule:
|
|
5241
|
+
const validation = import_joi11.default.object({
|
|
5242
|
+
site: import_joi11.default.string().hex().required(),
|
|
5243
|
+
schedule: import_joi11.default.string().hex().required()
|
|
4952
5244
|
});
|
|
4953
5245
|
const { error, value } = validation.validate(payload);
|
|
4954
5246
|
if (error) {
|
|
@@ -4968,14 +5260,14 @@ function useAreaChecklistController() {
|
|
|
4968
5260
|
}
|
|
4969
5261
|
async function getAllAreaChecklist(req, res, next) {
|
|
4970
5262
|
const query = { ...req.query, ...req.params };
|
|
4971
|
-
const validation =
|
|
4972
|
-
page:
|
|
4973
|
-
limit:
|
|
4974
|
-
search:
|
|
4975
|
-
type:
|
|
4976
|
-
status:
|
|
4977
|
-
schedule:
|
|
4978
|
-
serviceType:
|
|
5263
|
+
const validation = import_joi11.default.object({
|
|
5264
|
+
page: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5265
|
+
limit: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5266
|
+
search: import_joi11.default.string().optional().allow("", null),
|
|
5267
|
+
type: import_joi11.default.string().valid(...allowedTypes, "all").optional().allow("", null),
|
|
5268
|
+
status: import_joi11.default.string().valid(...allowedStatus, "all").optional().allow("", null),
|
|
5269
|
+
schedule: import_joi11.default.string().hex().required(),
|
|
5270
|
+
serviceType: import_joi11.default.string().valid(...Object.values(import_core11.AppServiceType)).required()
|
|
4979
5271
|
});
|
|
4980
5272
|
const { error } = validation.validate(query);
|
|
4981
5273
|
if (error) {
|
|
@@ -5010,14 +5302,14 @@ function useAreaChecklistController() {
|
|
|
5010
5302
|
}
|
|
5011
5303
|
async function getAreaChecklistHistory(req, res, next) {
|
|
5012
5304
|
const query = { ...req.query, ...req.params };
|
|
5013
|
-
const validation =
|
|
5014
|
-
page:
|
|
5015
|
-
limit:
|
|
5016
|
-
search:
|
|
5017
|
-
type:
|
|
5018
|
-
schedule:
|
|
5019
|
-
status:
|
|
5020
|
-
createdAt:
|
|
5305
|
+
const validation = import_joi11.default.object({
|
|
5306
|
+
page: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5307
|
+
limit: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5308
|
+
search: import_joi11.default.string().optional().allow("", null),
|
|
5309
|
+
type: import_joi11.default.string().optional().allow("", ...allowedTypes),
|
|
5310
|
+
schedule: import_joi11.default.string().hex().required(),
|
|
5311
|
+
status: import_joi11.default.string().allow("", null, ...allowedStatus),
|
|
5312
|
+
createdAt: import_joi11.default.alternatives().try(import_joi11.default.date(), import_joi11.default.string()).optional().allow("", null)
|
|
5021
5313
|
});
|
|
5022
5314
|
const { error } = validation.validate(query);
|
|
5023
5315
|
if (error) {
|
|
@@ -5051,7 +5343,7 @@ function useAreaChecklistController() {
|
|
|
5051
5343
|
}
|
|
5052
5344
|
}
|
|
5053
5345
|
async function getAreaChecklistHistoryDetails(req, res, next) {
|
|
5054
|
-
const validation =
|
|
5346
|
+
const validation = import_joi11.default.string().hex().required();
|
|
5055
5347
|
const _id = req.params.id;
|
|
5056
5348
|
const { error, value } = validation.validate(_id);
|
|
5057
5349
|
if (error) {
|
|
@@ -5071,12 +5363,12 @@ function useAreaChecklistController() {
|
|
|
5071
5363
|
}
|
|
5072
5364
|
async function getAreaChecklistUnits(req, res, next) {
|
|
5073
5365
|
const query = { ...req.query, ...req.params };
|
|
5074
|
-
const validation =
|
|
5075
|
-
page:
|
|
5076
|
-
limit:
|
|
5077
|
-
search:
|
|
5078
|
-
serviceType:
|
|
5079
|
-
id:
|
|
5366
|
+
const validation = import_joi11.default.object({
|
|
5367
|
+
page: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5368
|
+
limit: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5369
|
+
search: import_joi11.default.string().optional().allow("", null),
|
|
5370
|
+
serviceType: import_joi11.default.string().valid(...Object.values(import_core11.AppServiceType)).required(),
|
|
5371
|
+
id: import_joi11.default.string().hex().required()
|
|
5080
5372
|
});
|
|
5081
5373
|
const { error } = validation.validate(query);
|
|
5082
5374
|
if (error) {
|
|
@@ -5126,21 +5418,21 @@ function useAreaChecklistController() {
|
|
|
5126
5418
|
...decisionValues,
|
|
5127
5419
|
completedBy
|
|
5128
5420
|
};
|
|
5129
|
-
const validation =
|
|
5130
|
-
id:
|
|
5131
|
-
set:
|
|
5132
|
-
unit:
|
|
5133
|
-
decision:
|
|
5134
|
-
approve:
|
|
5135
|
-
reject:
|
|
5136
|
-
remarks:
|
|
5421
|
+
const validation = import_joi11.default.object({
|
|
5422
|
+
id: import_joi11.default.string().hex().required(),
|
|
5423
|
+
set: import_joi11.default.number().integer().min(1).required(),
|
|
5424
|
+
unit: import_joi11.default.string().hex().required(),
|
|
5425
|
+
decision: import_joi11.default.string().valid("approve", "reject").required(),
|
|
5426
|
+
approve: import_joi11.default.boolean().optional(),
|
|
5427
|
+
reject: import_joi11.default.boolean().optional(),
|
|
5428
|
+
remarks: import_joi11.default.string().optional().allow("", null),
|
|
5137
5429
|
/*
|
|
5138
5430
|
* Shape only. WHETHER a photo is required depends on the set's current
|
|
5139
5431
|
* state in the database, which Joi cannot see — see the check below.
|
|
5140
5432
|
* Each entry must still be a real id: a blank string carries no evidence.
|
|
5141
5433
|
*/
|
|
5142
|
-
attachment:
|
|
5143
|
-
completedBy:
|
|
5434
|
+
attachment: import_joi11.default.array().items(import_joi11.default.string().trim().min(1)).optional(),
|
|
5435
|
+
completedBy: import_joi11.default.string().hex().required()
|
|
5144
5436
|
});
|
|
5145
5437
|
const { error } = validation.validate(payload);
|
|
5146
5438
|
if (error) {
|
|
@@ -5177,7 +5469,7 @@ function useAreaChecklistController() {
|
|
|
5177
5469
|
}
|
|
5178
5470
|
}
|
|
5179
5471
|
async function downloadChecklistPdf(req, res, next) {
|
|
5180
|
-
const validation =
|
|
5472
|
+
const validation = import_joi11.default.string().hex().required();
|
|
5181
5473
|
const schedule = req.params.schedule;
|
|
5182
5474
|
const { error, value } = validation.validate(schedule);
|
|
5183
5475
|
if (error) {
|
|
@@ -5223,16 +5515,16 @@ function useAreaChecklistController() {
|
|
|
5223
5515
|
}
|
|
5224
5516
|
|
|
5225
5517
|
// src/models/hygiene-supply.model.ts
|
|
5226
|
-
var
|
|
5518
|
+
var import_joi12 = __toESM(require("joi"));
|
|
5227
5519
|
var import_mongodb12 = require("mongodb");
|
|
5228
5520
|
var import_node_server_utils21 = require("@7365admin1/node-server-utils");
|
|
5229
5521
|
var import_core12 = require("@7365admin1/core");
|
|
5230
|
-
var supplySchema =
|
|
5231
|
-
site:
|
|
5232
|
-
serviceType:
|
|
5233
|
-
name:
|
|
5234
|
-
unitOfMeasurement:
|
|
5235
|
-
attachment:
|
|
5522
|
+
var supplySchema = import_joi12.default.object({
|
|
5523
|
+
site: import_joi12.default.string().hex().required(),
|
|
5524
|
+
serviceType: import_joi12.default.string().valid(...Object.values(import_core12.AppServiceType)).required(),
|
|
5525
|
+
name: import_joi12.default.string().required(),
|
|
5526
|
+
unitOfMeasurement: import_joi12.default.string().required(),
|
|
5527
|
+
attachment: import_joi12.default.string().allow(null, "").optional()
|
|
5236
5528
|
});
|
|
5237
5529
|
function MSupply(value) {
|
|
5238
5530
|
const { error } = supplySchema.validate(value);
|
|
@@ -5360,7 +5652,7 @@ function useSupplyRepository() {
|
|
|
5360
5652
|
throw new import_node_server_utils22.BadRequestError("Invalid site ID format.");
|
|
5361
5653
|
}
|
|
5362
5654
|
if (search) {
|
|
5363
|
-
query.name = { $regex: search, $options: "i" };
|
|
5655
|
+
query.name = { $regex: escapeRegex(search), $options: "i" };
|
|
5364
5656
|
cacheOptions.search = search;
|
|
5365
5657
|
}
|
|
5366
5658
|
const cacheKey = (0, import_node_server_utils22.makeCacheKey)(namespace_collection, cacheOptions);
|
|
@@ -5522,7 +5814,7 @@ function useSupplyRepository() {
|
|
|
5522
5814
|
}
|
|
5523
5815
|
|
|
5524
5816
|
// src/controllers/hygiene-supply.controller.ts
|
|
5525
|
-
var
|
|
5817
|
+
var import_joi13 = __toESM(require("joi"));
|
|
5526
5818
|
var import_node_server_utils23 = require("@7365admin1/node-server-utils");
|
|
5527
5819
|
var import_core13 = require("@7365admin1/core");
|
|
5528
5820
|
function useSupplyController() {
|
|
@@ -5553,12 +5845,12 @@ function useSupplyController() {
|
|
|
5553
5845
|
}
|
|
5554
5846
|
async function getSupplies(req, res, next) {
|
|
5555
5847
|
const query = { ...req.query, ...req.params };
|
|
5556
|
-
const validation =
|
|
5557
|
-
page:
|
|
5558
|
-
limit:
|
|
5559
|
-
search:
|
|
5560
|
-
site:
|
|
5561
|
-
serviceType:
|
|
5848
|
+
const validation = import_joi13.default.object({
|
|
5849
|
+
page: import_joi13.default.number().min(1).optional().allow("", null),
|
|
5850
|
+
limit: import_joi13.default.number().min(1).optional().allow("", null),
|
|
5851
|
+
search: import_joi13.default.string().optional().allow("", null),
|
|
5852
|
+
site: import_joi13.default.string().hex().required(),
|
|
5853
|
+
serviceType: import_joi13.default.string().valid(...Object.values(import_core13.AppServiceType)).required()
|
|
5562
5854
|
});
|
|
5563
5855
|
const { error } = validation.validate(query);
|
|
5564
5856
|
if (error) {
|
|
@@ -5588,7 +5880,7 @@ function useSupplyController() {
|
|
|
5588
5880
|
}
|
|
5589
5881
|
}
|
|
5590
5882
|
async function getSupplyById(req, res, next) {
|
|
5591
|
-
const validation =
|
|
5883
|
+
const validation = import_joi13.default.string().hex().required();
|
|
5592
5884
|
const _id = req.params.id;
|
|
5593
5885
|
const { error, value } = validation.validate(_id);
|
|
5594
5886
|
if (error) {
|
|
@@ -5608,12 +5900,12 @@ function useSupplyController() {
|
|
|
5608
5900
|
}
|
|
5609
5901
|
async function updateSupply(req, res, next) {
|
|
5610
5902
|
const payload = { id: req.params.id, ...req.body };
|
|
5611
|
-
const validation =
|
|
5612
|
-
id:
|
|
5613
|
-
name:
|
|
5614
|
-
unitOfMeasurement:
|
|
5615
|
-
qty:
|
|
5616
|
-
attachment:
|
|
5903
|
+
const validation = import_joi13.default.object({
|
|
5904
|
+
id: import_joi13.default.string().hex().required(),
|
|
5905
|
+
name: import_joi13.default.string().optional().allow("", null),
|
|
5906
|
+
unitOfMeasurement: import_joi13.default.string().optional().allow("", null),
|
|
5907
|
+
qty: import_joi13.default.number().min(0).optional().allow("", null),
|
|
5908
|
+
attachment: import_joi13.default.string().optional().allow("", null)
|
|
5617
5909
|
});
|
|
5618
5910
|
const { error } = validation.validate(payload);
|
|
5619
5911
|
if (error) {
|
|
@@ -5634,7 +5926,7 @@ function useSupplyController() {
|
|
|
5634
5926
|
}
|
|
5635
5927
|
async function deleteSupply(req, res, next) {
|
|
5636
5928
|
const id = req.params.id;
|
|
5637
|
-
const validation =
|
|
5929
|
+
const validation = import_joi13.default.string().hex().required();
|
|
5638
5930
|
const { error, value } = validation.validate(id);
|
|
5639
5931
|
if (error) {
|
|
5640
5932
|
import_node_server_utils23.logger.log({ level: "error", message: error.message });
|
|
@@ -5661,18 +5953,18 @@ function useSupplyController() {
|
|
|
5661
5953
|
}
|
|
5662
5954
|
|
|
5663
5955
|
// src/models/hygiene-stock.model.ts
|
|
5664
|
-
var
|
|
5956
|
+
var import_joi14 = __toESM(require("joi"));
|
|
5665
5957
|
var import_mongodb14 = require("mongodb");
|
|
5666
5958
|
var import_node_server_utils24 = require("@7365admin1/node-server-utils");
|
|
5667
5959
|
var import_core14 = require("@7365admin1/core");
|
|
5668
|
-
var stockSchema =
|
|
5669
|
-
site:
|
|
5670
|
-
serviceType:
|
|
5671
|
-
supply:
|
|
5672
|
-
in:
|
|
5673
|
-
out:
|
|
5674
|
-
balance:
|
|
5675
|
-
remarks:
|
|
5960
|
+
var stockSchema = import_joi14.default.object({
|
|
5961
|
+
site: import_joi14.default.string().hex().required(),
|
|
5962
|
+
serviceType: import_joi14.default.string().valid(...Object.values(import_core14.AppServiceType)).required(),
|
|
5963
|
+
supply: import_joi14.default.string().hex().required(),
|
|
5964
|
+
in: import_joi14.default.number().min(0).optional(),
|
|
5965
|
+
out: import_joi14.default.number().min(0).optional(),
|
|
5966
|
+
balance: import_joi14.default.number().min(0).required(),
|
|
5967
|
+
remarks: import_joi14.default.string().optional().allow("", null)
|
|
5676
5968
|
});
|
|
5677
5969
|
function MStock(value) {
|
|
5678
5970
|
const { error } = stockSchema.validate(value);
|
|
@@ -5887,7 +6179,7 @@ function useStockService() {
|
|
|
5887
6179
|
}
|
|
5888
6180
|
|
|
5889
6181
|
// src/controllers/hygiene-stock.controller.ts
|
|
5890
|
-
var
|
|
6182
|
+
var import_joi15 = __toESM(require("joi"));
|
|
5891
6183
|
var import_node_server_utils27 = require("@7365admin1/node-server-utils");
|
|
5892
6184
|
var import_core15 = require("@7365admin1/core");
|
|
5893
6185
|
function useStockController() {
|
|
@@ -5895,12 +6187,12 @@ function useStockController() {
|
|
|
5895
6187
|
const { createStock: _createStock } = useStockService();
|
|
5896
6188
|
async function createStock(req, res, next) {
|
|
5897
6189
|
const payload = { ...req.body, ...req.params };
|
|
5898
|
-
const validation =
|
|
5899
|
-
site:
|
|
5900
|
-
serviceType:
|
|
5901
|
-
supply:
|
|
5902
|
-
qty:
|
|
5903
|
-
remarks:
|
|
6190
|
+
const validation = import_joi15.default.object({
|
|
6191
|
+
site: import_joi15.default.string().hex().required(),
|
|
6192
|
+
serviceType: import_joi15.default.string().valid(...Object.values(import_core15.AppServiceType)).required(),
|
|
6193
|
+
supply: import_joi15.default.string().hex().required(),
|
|
6194
|
+
qty: import_joi15.default.number().min(0).required(),
|
|
6195
|
+
remarks: import_joi15.default.string().optional().allow("", null)
|
|
5904
6196
|
});
|
|
5905
6197
|
const { error } = validation.validate(payload);
|
|
5906
6198
|
if (error) {
|
|
@@ -5920,13 +6212,13 @@ function useStockController() {
|
|
|
5920
6212
|
}
|
|
5921
6213
|
async function getStocksBySupplyId(req, res, next) {
|
|
5922
6214
|
const query = { ...req.query, ...req.params };
|
|
5923
|
-
const validation =
|
|
5924
|
-
page:
|
|
5925
|
-
limit:
|
|
5926
|
-
search:
|
|
5927
|
-
site:
|
|
5928
|
-
serviceType:
|
|
5929
|
-
supply:
|
|
6215
|
+
const validation = import_joi15.default.object({
|
|
6216
|
+
page: import_joi15.default.number().min(1).optional().allow("", null),
|
|
6217
|
+
limit: import_joi15.default.number().min(1).optional().allow("", null),
|
|
6218
|
+
search: import_joi15.default.string().optional().allow("", null),
|
|
6219
|
+
site: import_joi15.default.string().hex().required(),
|
|
6220
|
+
serviceType: import_joi15.default.string().valid(...Object.values(import_core15.AppServiceType)).required(),
|
|
6221
|
+
supply: import_joi15.default.string().hex().required()
|
|
5930
6222
|
});
|
|
5931
6223
|
const { error } = validation.validate(query);
|
|
5932
6224
|
if (error) {
|
|
@@ -5988,21 +6280,21 @@ async function applyCheckOutDecision(decision, decide, returnStock) {
|
|
|
5988
6280
|
}
|
|
5989
6281
|
|
|
5990
6282
|
// src/models/hygiene-checkout-item.model.ts
|
|
5991
|
-
var
|
|
6283
|
+
var import_joi16 = __toESM(require("joi"));
|
|
5992
6284
|
var import_mongodb16 = require("mongodb");
|
|
5993
6285
|
var import_node_server_utils28 = require("@7365admin1/node-server-utils");
|
|
5994
6286
|
var import_core16 = require("@7365admin1/core");
|
|
5995
6287
|
var allowedCheckOutItemStatus = ["pending", "completed"];
|
|
5996
|
-
var checkOutItemSchema =
|
|
5997
|
-
site:
|
|
5998
|
-
serviceType:
|
|
5999
|
-
supply:
|
|
6000
|
-
supplyName:
|
|
6001
|
-
supplyQty:
|
|
6002
|
-
qty:
|
|
6003
|
-
attachment:
|
|
6004
|
-
createdBy:
|
|
6005
|
-
createdByName:
|
|
6288
|
+
var checkOutItemSchema = import_joi16.default.object({
|
|
6289
|
+
site: import_joi16.default.string().hex().required(),
|
|
6290
|
+
serviceType: import_joi16.default.string().valid(...Object.values(import_core16.AppServiceType)).required(),
|
|
6291
|
+
supply: import_joi16.default.string().hex().required(),
|
|
6292
|
+
supplyName: import_joi16.default.string().required(),
|
|
6293
|
+
supplyQty: import_joi16.default.number().min(0).required(),
|
|
6294
|
+
qty: import_joi16.default.number().min(0).required(),
|
|
6295
|
+
attachment: import_joi16.default.array().items(import_joi16.default.string()).optional().allow(null),
|
|
6296
|
+
createdBy: import_joi16.default.string().hex().required(),
|
|
6297
|
+
createdByName: import_joi16.default.string().required()
|
|
6006
6298
|
});
|
|
6007
6299
|
function MCheckOutItem(value) {
|
|
6008
6300
|
const { error } = checkOutItemSchema.validate(value);
|
|
@@ -6126,7 +6418,7 @@ function useCheckOutItemRepository() {
|
|
|
6126
6418
|
throw new import_node_server_utils29.BadRequestError("Invalid site ID format.");
|
|
6127
6419
|
}
|
|
6128
6420
|
if (search) {
|
|
6129
|
-
query.supplyName = { $regex: search, $options: "i" };
|
|
6421
|
+
query.supplyName = { $regex: escapeRegex(search), $options: "i" };
|
|
6130
6422
|
cacheOptions.search = search;
|
|
6131
6423
|
}
|
|
6132
6424
|
const cacheKey = (0, import_node_server_utils29.makeCacheKey)(namespace_collection, cacheOptions);
|
|
@@ -6482,7 +6774,7 @@ function useCheckOutItemService() {
|
|
|
6482
6774
|
}
|
|
6483
6775
|
|
|
6484
6776
|
// src/controllers/hygiene-checkout-item.controller.ts
|
|
6485
|
-
var
|
|
6777
|
+
var import_joi17 = __toESM(require("joi"));
|
|
6486
6778
|
var import_node_server_utils31 = require("@7365admin1/node-server-utils");
|
|
6487
6779
|
var import_core18 = require("@7365admin1/core");
|
|
6488
6780
|
function useCheckOutItemController() {
|
|
@@ -6506,13 +6798,13 @@ function useCheckOutItemController() {
|
|
|
6506
6798
|
...req.params,
|
|
6507
6799
|
createdBy
|
|
6508
6800
|
};
|
|
6509
|
-
const validation =
|
|
6510
|
-
site:
|
|
6511
|
-
serviceType:
|
|
6512
|
-
supply:
|
|
6513
|
-
qty:
|
|
6514
|
-
attachment:
|
|
6515
|
-
createdBy:
|
|
6801
|
+
const validation = import_joi17.default.object({
|
|
6802
|
+
site: import_joi17.default.string().hex().required(),
|
|
6803
|
+
serviceType: import_joi17.default.string().valid(...Object.values(import_core18.AppServiceType)).required(),
|
|
6804
|
+
supply: import_joi17.default.string().hex().required(),
|
|
6805
|
+
qty: import_joi17.default.number().min(0).required(),
|
|
6806
|
+
attachment: import_joi17.default.array().items(import_joi17.default.string()).optional().allow(null),
|
|
6807
|
+
createdBy: import_joi17.default.string().hex().required()
|
|
6516
6808
|
});
|
|
6517
6809
|
const { error } = validation.validate(payload);
|
|
6518
6810
|
if (error) {
|
|
@@ -6541,15 +6833,15 @@ function useCheckOutItemController() {
|
|
|
6541
6833
|
...req.params,
|
|
6542
6834
|
createdBy
|
|
6543
6835
|
};
|
|
6544
|
-
const validation =
|
|
6545
|
-
site:
|
|
6546
|
-
serviceType:
|
|
6547
|
-
createdBy:
|
|
6548
|
-
items:
|
|
6549
|
-
|
|
6550
|
-
supply:
|
|
6551
|
-
qty:
|
|
6552
|
-
attachment:
|
|
6836
|
+
const validation = import_joi17.default.object({
|
|
6837
|
+
site: import_joi17.default.string().hex().required(),
|
|
6838
|
+
serviceType: import_joi17.default.string().valid(...Object.values(import_core18.AppServiceType)).required(),
|
|
6839
|
+
createdBy: import_joi17.default.string().hex().required(),
|
|
6840
|
+
items: import_joi17.default.array().items(
|
|
6841
|
+
import_joi17.default.object({
|
|
6842
|
+
supply: import_joi17.default.string().hex().required(),
|
|
6843
|
+
qty: import_joi17.default.number().min(0).required(),
|
|
6844
|
+
attachment: import_joi17.default.array().items(import_joi17.default.string()).optional().allow(null)
|
|
6553
6845
|
})
|
|
6554
6846
|
).min(1).required()
|
|
6555
6847
|
});
|
|
@@ -6571,12 +6863,12 @@ function useCheckOutItemController() {
|
|
|
6571
6863
|
}
|
|
6572
6864
|
async function getCheckOutItems(req, res, next) {
|
|
6573
6865
|
const query = { ...req.query, ...req.params };
|
|
6574
|
-
const validation =
|
|
6575
|
-
page:
|
|
6576
|
-
limit:
|
|
6577
|
-
search:
|
|
6578
|
-
site:
|
|
6579
|
-
serviceType:
|
|
6866
|
+
const validation = import_joi17.default.object({
|
|
6867
|
+
page: import_joi17.default.number().min(1).optional().allow("", null),
|
|
6868
|
+
limit: import_joi17.default.number().min(1).optional().allow("", null),
|
|
6869
|
+
search: import_joi17.default.string().optional().allow("", null),
|
|
6870
|
+
site: import_joi17.default.string().hex().required(),
|
|
6871
|
+
serviceType: import_joi17.default.string().valid(...Object.values(import_core18.AppServiceType)).required()
|
|
6580
6872
|
});
|
|
6581
6873
|
const { error } = validation.validate(query);
|
|
6582
6874
|
if (error) {
|
|
@@ -6606,7 +6898,7 @@ function useCheckOutItemController() {
|
|
|
6606
6898
|
}
|
|
6607
6899
|
}
|
|
6608
6900
|
async function getCheckOutItemById(req, res, next) {
|
|
6609
|
-
const validation =
|
|
6901
|
+
const validation = import_joi17.default.string().hex().required();
|
|
6610
6902
|
const _id = req.params.id;
|
|
6611
6903
|
const { error, value } = validation.validate(_id);
|
|
6612
6904
|
if (error) {
|
|
@@ -6645,13 +6937,13 @@ function useCheckOutItemController() {
|
|
|
6645
6937
|
...decisionValues,
|
|
6646
6938
|
completedBy
|
|
6647
6939
|
};
|
|
6648
|
-
const validation =
|
|
6649
|
-
id:
|
|
6650
|
-
decision:
|
|
6651
|
-
approve:
|
|
6652
|
-
reject:
|
|
6653
|
-
remarks:
|
|
6654
|
-
completedBy:
|
|
6940
|
+
const validation = import_joi17.default.object({
|
|
6941
|
+
id: import_joi17.default.string().hex().required(),
|
|
6942
|
+
decision: import_joi17.default.string().valid("approve", "disapprove").required(),
|
|
6943
|
+
approve: import_joi17.default.boolean().required(),
|
|
6944
|
+
reject: import_joi17.default.boolean().required(),
|
|
6945
|
+
remarks: import_joi17.default.string().optional().allow("", null),
|
|
6946
|
+
completedBy: import_joi17.default.string().hex().required()
|
|
6655
6947
|
});
|
|
6656
6948
|
const { error } = validation.validate(payload);
|
|
6657
6949
|
if (error) {
|
|
@@ -6684,23 +6976,15 @@ function useCheckOutItemController() {
|
|
|
6684
6976
|
// src/models/hygiene-schedule-task.model.ts
|
|
6685
6977
|
var import_node_server_utils32 = require("@7365admin1/node-server-utils");
|
|
6686
6978
|
var import_core19 = require("@7365admin1/core");
|
|
6687
|
-
var
|
|
6979
|
+
var import_joi18 = __toESM(require("joi"));
|
|
6688
6980
|
var import_mongodb18 = require("mongodb");
|
|
6689
|
-
var scheduleTaskSchema =
|
|
6690
|
-
site:
|
|
6691
|
-
serviceType:
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
).required(),
|
|
6697
|
-
description: import_joi17.default.string().optional().allow("", null),
|
|
6698
|
-
areas: import_joi17.default.array().min(1).items(
|
|
6699
|
-
import_joi17.default.object({
|
|
6700
|
-
name: import_joi17.default.string().required(),
|
|
6701
|
-
value: import_joi17.default.any().required()
|
|
6702
|
-
})
|
|
6703
|
-
).required()
|
|
6981
|
+
var scheduleTaskSchema = import_joi18.default.object({
|
|
6982
|
+
site: import_joi18.default.string().hex().required(),
|
|
6983
|
+
serviceType: import_joi18.default.string().valid(...Object.values(import_core19.AppServiceType)).required(),
|
|
6984
|
+
...scheduleTaskFields
|
|
6985
|
+
}).custom((value, helpers) => {
|
|
6986
|
+
const message = validateScheduleRules(cleanScheduleInput(value));
|
|
6987
|
+
return message ? helpers.message({ custom: message }) : value;
|
|
6704
6988
|
});
|
|
6705
6989
|
function MScheduleTask(value) {
|
|
6706
6990
|
const { error } = scheduleTaskSchema.validate(value);
|
|
@@ -6727,14 +7011,26 @@ function MScheduleTask(value) {
|
|
|
6727
7011
|
}
|
|
6728
7012
|
});
|
|
6729
7013
|
}
|
|
7014
|
+
const extra = cleanScheduleInput({
|
|
7015
|
+
repeat: value.repeat,
|
|
7016
|
+
daysOfWeek: value.daysOfWeek,
|
|
7017
|
+
dayOfMonth: value.dayOfMonth,
|
|
7018
|
+
startDate: value.startDate,
|
|
7019
|
+
endDate: value.endDate,
|
|
7020
|
+
paused: value.paused
|
|
7021
|
+
});
|
|
7022
|
+
for (const key of Object.keys(extra))
|
|
7023
|
+
if (extra[key] === void 0)
|
|
7024
|
+
delete extra[key];
|
|
6730
7025
|
return {
|
|
6731
7026
|
site: value.site,
|
|
6732
7027
|
serviceType: value.serviceType,
|
|
6733
7028
|
title: value.title,
|
|
6734
7029
|
time: value.time,
|
|
6735
|
-
dates: value.dates,
|
|
7030
|
+
dates: value.dates ?? [],
|
|
6736
7031
|
description: value.description,
|
|
6737
7032
|
areas: value.areas,
|
|
7033
|
+
...extra,
|
|
6738
7034
|
status: "active",
|
|
6739
7035
|
createdAt: /* @__PURE__ */ new Date(),
|
|
6740
7036
|
updatedAt: "",
|
|
@@ -6745,11 +7041,23 @@ function MScheduleTask(value) {
|
|
|
6745
7041
|
// src/repositories/hygiene-schedule-task.repository.ts
|
|
6746
7042
|
var import_mongodb19 = require("mongodb");
|
|
6747
7043
|
var import_node_server_utils33 = require("@7365admin1/node-server-utils");
|
|
7044
|
+
var SCHEDULE_FIELDS = {
|
|
7045
|
+
time: 1,
|
|
7046
|
+
dates: 1,
|
|
7047
|
+
repeat: 1,
|
|
7048
|
+
daysOfWeek: 1,
|
|
7049
|
+
dayOfMonth: 1,
|
|
7050
|
+
startDate: 1,
|
|
7051
|
+
endDate: 1,
|
|
7052
|
+
paused: 1,
|
|
7053
|
+
lastRunDate: 1
|
|
7054
|
+
};
|
|
6748
7055
|
function useScheduleTaskRepository() {
|
|
6749
7056
|
const db = import_node_server_utils33.useAtlas.getDb();
|
|
6750
7057
|
if (!db) {
|
|
6751
7058
|
throw new import_node_server_utils33.InternalServerError("Unable to connect to server.");
|
|
6752
7059
|
}
|
|
7060
|
+
const database = db;
|
|
6753
7061
|
const namespace_collection = "site.schedule-tasks";
|
|
6754
7062
|
const collection = db.collection(namespace_collection);
|
|
6755
7063
|
const { delNamespace, setCache, getCache } = (0, import_node_server_utils33.useCache)(namespace_collection);
|
|
@@ -6817,14 +7125,14 @@ function useScheduleTaskRepository() {
|
|
|
6817
7125
|
throw new import_node_server_utils33.BadRequestError("Invalid site ID format.");
|
|
6818
7126
|
}
|
|
6819
7127
|
if (search) {
|
|
6820
|
-
query.$or = [{ title: { $regex: search, $options: "i" } }];
|
|
7128
|
+
query.$or = [{ title: { $regex: escapeRegex(search), $options: "i" } }];
|
|
6821
7129
|
cacheOptions.search = search;
|
|
6822
7130
|
}
|
|
6823
7131
|
const cacheKey = (0, import_node_server_utils33.makeCacheKey)(namespace_collection, cacheOptions);
|
|
6824
7132
|
const cachedData = await getCache(cacheKey);
|
|
6825
7133
|
if (cachedData) {
|
|
6826
7134
|
import_node_server_utils33.logger.info(`Cache hit for key: ${cacheKey}`);
|
|
6827
|
-
return cachedData;
|
|
7135
|
+
return enrichList(cachedData, site, serviceType);
|
|
6828
7136
|
}
|
|
6829
7137
|
try {
|
|
6830
7138
|
const items = await collection.aggregate([
|
|
@@ -6833,7 +7141,8 @@ function useScheduleTaskRepository() {
|
|
|
6833
7141
|
$project: {
|
|
6834
7142
|
title: 1,
|
|
6835
7143
|
areas: 1,
|
|
6836
|
-
status: 1
|
|
7144
|
+
status: 1,
|
|
7145
|
+
...SCHEDULE_FIELDS
|
|
6837
7146
|
}
|
|
6838
7147
|
},
|
|
6839
7148
|
{ $sort: { _id: -1 } },
|
|
@@ -6847,15 +7156,104 @@ function useScheduleTaskRepository() {
|
|
|
6847
7156
|
}).catch((err) => {
|
|
6848
7157
|
import_node_server_utils33.logger.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
6849
7158
|
});
|
|
6850
|
-
return data;
|
|
7159
|
+
return enrichList(data, site, serviceType);
|
|
6851
7160
|
} catch (error) {
|
|
6852
7161
|
throw error;
|
|
6853
7162
|
}
|
|
6854
7163
|
}
|
|
6855
|
-
async function
|
|
7164
|
+
async function enrichList(data, site, serviceType) {
|
|
7165
|
+
const items = data?.items ?? [];
|
|
7166
|
+
const now = /* @__PURE__ */ new Date();
|
|
7167
|
+
let statuses = /* @__PURE__ */ new Map();
|
|
7168
|
+
try {
|
|
7169
|
+
statuses = await getRunStatuses(items, site, serviceType);
|
|
7170
|
+
} catch (error) {
|
|
7171
|
+
import_node_server_utils33.logger.error("Could not read schedule task run status", error);
|
|
7172
|
+
}
|
|
7173
|
+
return {
|
|
7174
|
+
...data,
|
|
7175
|
+
items: items.map((item) => {
|
|
7176
|
+
try {
|
|
7177
|
+
return {
|
|
7178
|
+
...item,
|
|
7179
|
+
...scheduleDisplay(item, now),
|
|
7180
|
+
lastRunStatus: statuses.get(String(item._id)) ?? null
|
|
7181
|
+
};
|
|
7182
|
+
} catch (error) {
|
|
7183
|
+
import_node_server_utils33.logger.error(`Could not enrich schedule task ${item?._id}`, error);
|
|
7184
|
+
return { ...item, nextRun: null, lastRun: null, scheduleStatus: null, lastRunStatus: null };
|
|
7185
|
+
}
|
|
7186
|
+
})
|
|
7187
|
+
};
|
|
7188
|
+
}
|
|
7189
|
+
async function getRunStatuses(items, site, serviceType) {
|
|
7190
|
+
const result = /* @__PURE__ */ new Map();
|
|
7191
|
+
const ran = items.filter((i) => i.lastRunDate);
|
|
7192
|
+
if (!ran.length)
|
|
7193
|
+
return result;
|
|
7194
|
+
const parents = await database.collection("site.cleaning.schedules").find(
|
|
7195
|
+
{
|
|
7196
|
+
site,
|
|
7197
|
+
serviceType,
|
|
7198
|
+
dateStr: { $in: [...new Set(ran.map((i) => i.lastRunDate))] }
|
|
7199
|
+
},
|
|
7200
|
+
{ projection: { dateStr: 1 } }
|
|
7201
|
+
).toArray();
|
|
7202
|
+
if (!parents.length)
|
|
7203
|
+
return result;
|
|
7204
|
+
const dateOf = new Map(parents.map((p) => [String(p._id), p.dateStr]));
|
|
7205
|
+
const taskIds = ran.map((i) => new import_mongodb19.ObjectId(i._id));
|
|
7206
|
+
const rows = await database.collection("site.cleaning.schedule.areas").aggregate([
|
|
7207
|
+
{
|
|
7208
|
+
$match: {
|
|
7209
|
+
schedule: { $in: parents.map((p) => p._id) },
|
|
7210
|
+
"checklist.scheduleTaskId": { $in: taskIds }
|
|
7211
|
+
}
|
|
7212
|
+
},
|
|
7213
|
+
{ $unwind: "$checklist" },
|
|
7214
|
+
{ $match: { "checklist.scheduleTaskId": { $in: taskIds } } },
|
|
7215
|
+
{ $unwind: "$checklist.units" },
|
|
7216
|
+
{
|
|
7217
|
+
$group: {
|
|
7218
|
+
_id: { task: "$checklist.scheduleTaskId", schedule: "$schedule" },
|
|
7219
|
+
total: { $sum: 1 },
|
|
7220
|
+
completed: {
|
|
7221
|
+
$sum: { $cond: [{ $eq: ["$checklist.units.status", "completed"] }, 1, 0] }
|
|
7222
|
+
},
|
|
7223
|
+
closed: {
|
|
7224
|
+
$sum: { $cond: [{ $eq: ["$checklist.units.status", "closed"] }, 1, 0] }
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7227
|
+
}
|
|
7228
|
+
]).toArray();
|
|
7229
|
+
const sums = /* @__PURE__ */ new Map();
|
|
7230
|
+
for (const row of rows) {
|
|
7231
|
+
const task = ran.find((i) => String(i._id) === String(row._id.task));
|
|
7232
|
+
if (!task || dateOf.get(String(row._id.schedule)) !== task.lastRunDate)
|
|
7233
|
+
continue;
|
|
7234
|
+
const sum = sums.get(String(task._id)) ?? { total: 0, completed: 0, closed: 0 };
|
|
7235
|
+
sum.total += row.total;
|
|
7236
|
+
sum.completed += row.completed;
|
|
7237
|
+
sum.closed += row.closed;
|
|
7238
|
+
sums.set(String(task._id), sum);
|
|
7239
|
+
}
|
|
7240
|
+
for (const [id, sum] of sums) {
|
|
7241
|
+
result.set(id, sum.completed === sum.total ? "done" : sum.closed > 0 ? "missed" : "pending");
|
|
7242
|
+
}
|
|
7243
|
+
return result;
|
|
7244
|
+
}
|
|
7245
|
+
async function getAllScheduleTask(today) {
|
|
6856
7246
|
const query = {
|
|
6857
7247
|
status: { $ne: "deleted" }
|
|
6858
7248
|
};
|
|
7249
|
+
if (today) {
|
|
7250
|
+
query.paused = { $ne: true };
|
|
7251
|
+
query.lastRunDate = { $ne: today };
|
|
7252
|
+
query.$or = [
|
|
7253
|
+
{ dates: today },
|
|
7254
|
+
{ repeat: { $in: ["daily", "weekdays", "weekly", "monthly"] } }
|
|
7255
|
+
];
|
|
7256
|
+
}
|
|
6859
7257
|
try {
|
|
6860
7258
|
const items = await collection.aggregate([{ $match: query }, { $sort: { _id: -1 } }]).toArray();
|
|
6861
7259
|
return items;
|
|
@@ -6863,6 +7261,9 @@ function useScheduleTaskRepository() {
|
|
|
6863
7261
|
throw error;
|
|
6864
7262
|
}
|
|
6865
7263
|
}
|
|
7264
|
+
function withScheduleDisplay(row) {
|
|
7265
|
+
return { ...row, ...scheduleDisplay(row, /* @__PURE__ */ new Date()) };
|
|
7266
|
+
}
|
|
6866
7267
|
async function getScheduleTaskById(_id, session) {
|
|
6867
7268
|
try {
|
|
6868
7269
|
_id = new import_mongodb19.ObjectId(_id);
|
|
@@ -6880,7 +7281,7 @@ function useScheduleTaskRepository() {
|
|
|
6880
7281
|
const cachedData = await getCache(cacheKey);
|
|
6881
7282
|
if (cachedData) {
|
|
6882
7283
|
import_node_server_utils33.logger.info(`Cache hit for key: ${cacheKey}`);
|
|
6883
|
-
return cachedData;
|
|
7284
|
+
return withScheduleDisplay(cachedData);
|
|
6884
7285
|
}
|
|
6885
7286
|
} else {
|
|
6886
7287
|
import_node_server_utils33.logger.info(`Skipping cache during transaction for key: ${cacheKey}`);
|
|
@@ -6891,12 +7292,13 @@ function useScheduleTaskRepository() {
|
|
|
6891
7292
|
{
|
|
6892
7293
|
$project: {
|
|
6893
7294
|
title: 1,
|
|
6894
|
-
time: 1,
|
|
6895
|
-
dates: 1,
|
|
6896
7295
|
description: 1,
|
|
6897
7296
|
areas: 1,
|
|
6898
7297
|
status: 1,
|
|
6899
|
-
createdAt: 1
|
|
7298
|
+
createdAt: 1,
|
|
7299
|
+
site: 1,
|
|
7300
|
+
serviceType: 1,
|
|
7301
|
+
...SCHEDULE_FIELDS
|
|
6900
7302
|
}
|
|
6901
7303
|
}
|
|
6902
7304
|
]).toArray();
|
|
@@ -6908,7 +7310,7 @@ function useScheduleTaskRepository() {
|
|
|
6908
7310
|
}).catch((err) => {
|
|
6909
7311
|
import_node_server_utils33.logger.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
6910
7312
|
});
|
|
6911
|
-
return data[0];
|
|
7313
|
+
return withScheduleDisplay(data[0]);
|
|
6912
7314
|
} catch (error) {
|
|
6913
7315
|
throw error;
|
|
6914
7316
|
}
|
|
@@ -6932,16 +7334,20 @@ function useScheduleTaskRepository() {
|
|
|
6932
7334
|
});
|
|
6933
7335
|
}
|
|
6934
7336
|
try {
|
|
6935
|
-
const
|
|
7337
|
+
const { set, unset } = splitScheduleUpdate(value);
|
|
7338
|
+
const update = {
|
|
7339
|
+
$set: { ...set, updatedAt: /* @__PURE__ */ new Date() }
|
|
7340
|
+
};
|
|
7341
|
+
if (unset.length) {
|
|
7342
|
+
update.$unset = Object.fromEntries(unset.map((key) => [key, ""]));
|
|
7343
|
+
}
|
|
6936
7344
|
const res = await collection.updateOne(
|
|
6937
|
-
{ _id },
|
|
6938
|
-
|
|
7345
|
+
{ _id, status: { $ne: "deleted" } },
|
|
7346
|
+
update,
|
|
6939
7347
|
{ session }
|
|
6940
7348
|
);
|
|
6941
|
-
if (res.
|
|
6942
|
-
throw new import_node_server_utils33.
|
|
6943
|
-
"Unable to update hygiene schedule task."
|
|
6944
|
-
);
|
|
7349
|
+
if (res.matchedCount === 0) {
|
|
7350
|
+
throw new import_node_server_utils33.NotFoundError("Schedule task not found.");
|
|
6945
7351
|
}
|
|
6946
7352
|
delNamespace().then(() => {
|
|
6947
7353
|
import_node_server_utils33.logger.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
@@ -6956,6 +7362,18 @@ function useScheduleTaskRepository() {
|
|
|
6956
7362
|
throw error;
|
|
6957
7363
|
}
|
|
6958
7364
|
}
|
|
7365
|
+
async function markScheduleTaskRan(_id, date) {
|
|
7366
|
+
await collection.updateOne(
|
|
7367
|
+
{ _id: new import_mongodb19.ObjectId(_id) },
|
|
7368
|
+
{ $set: { lastRunDate: date, lastRunAt: /* @__PURE__ */ new Date() } }
|
|
7369
|
+
);
|
|
7370
|
+
delNamespace().catch((err) => {
|
|
7371
|
+
import_node_server_utils33.logger.error(
|
|
7372
|
+
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
7373
|
+
err
|
|
7374
|
+
);
|
|
7375
|
+
});
|
|
7376
|
+
}
|
|
6959
7377
|
async function deleteScheduleTask(_id, session) {
|
|
6960
7378
|
try {
|
|
6961
7379
|
_id = new import_mongodb19.ObjectId(_id);
|
|
@@ -6995,18 +7413,31 @@ function useScheduleTaskRepository() {
|
|
|
6995
7413
|
createScheduleTask,
|
|
6996
7414
|
getScheduleTasks,
|
|
6997
7415
|
getAllScheduleTask,
|
|
7416
|
+
markScheduleTaskRan,
|
|
6998
7417
|
getScheduleTaskById,
|
|
6999
7418
|
updateScheduleTask,
|
|
7000
7419
|
deleteScheduleTask
|
|
7001
7420
|
};
|
|
7002
7421
|
}
|
|
7003
7422
|
|
|
7423
|
+
// src/utils/schedule-task-set.util.ts
|
|
7424
|
+
function buildScheduleTaskSets(task, units, startSet, count) {
|
|
7425
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
7426
|
+
set: startSet + i,
|
|
7427
|
+
units,
|
|
7428
|
+
isScheduleTask: true,
|
|
7429
|
+
scheduleTaskId: task._id,
|
|
7430
|
+
scheduleTaskTitle: task.title,
|
|
7431
|
+
scheduleTaskTime: task.time
|
|
7432
|
+
}));
|
|
7433
|
+
}
|
|
7434
|
+
|
|
7004
7435
|
// src/services/hygiene-schedule-task.service.ts
|
|
7005
7436
|
var import_mongodb20 = require("mongodb");
|
|
7006
7437
|
var import_node_server_utils34 = require("@7365admin1/node-server-utils");
|
|
7007
7438
|
function useScheduleTaskService() {
|
|
7008
7439
|
const { createParentChecklist } = useParentChecklistRepo();
|
|
7009
|
-
const { getAllScheduleTask } = useScheduleTaskRepository();
|
|
7440
|
+
const { getAllScheduleTask, markScheduleTaskRan } = useScheduleTaskRepository();
|
|
7010
7441
|
const {
|
|
7011
7442
|
createAreaChecklist,
|
|
7012
7443
|
getAreaChecklistByAreaAndSchedule,
|
|
@@ -7015,73 +7446,7 @@ function useScheduleTaskService() {
|
|
|
7015
7446
|
const { getAreaById } = useAreaRepo();
|
|
7016
7447
|
function checkScheduleConditions(schedule, serviceType, currentDate = /* @__PURE__ */ new Date()) {
|
|
7017
7448
|
try {
|
|
7018
|
-
|
|
7019
|
-
const timeString = now.toLocaleTimeString("en-US", {
|
|
7020
|
-
hour: "2-digit",
|
|
7021
|
-
minute: "2-digit",
|
|
7022
|
-
hour12: false,
|
|
7023
|
-
timeZone: "Asia/Singapore"
|
|
7024
|
-
});
|
|
7025
|
-
const [currentHour, currentMinute] = timeString.split(":").map(Number);
|
|
7026
|
-
import_node_server_utils34.logger.info(
|
|
7027
|
-
`Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Schedule time ${schedule.time}, Dates ${JSON.stringify(schedule.dates)}`
|
|
7028
|
-
);
|
|
7029
|
-
const currentDateFormatted = now.toLocaleDateString("en-CA", {
|
|
7030
|
-
timeZone: "Asia/Singapore"
|
|
7031
|
-
});
|
|
7032
|
-
if (!schedule.dates || !Array.isArray(schedule.dates) || schedule.dates.length === 0) {
|
|
7033
|
-
import_node_server_utils34.logger.info(`Schedule ${schedule._id}: No dates configured, skipping`);
|
|
7034
|
-
return false;
|
|
7035
|
-
}
|
|
7036
|
-
if (!schedule.dates.includes(currentDateFormatted)) {
|
|
7037
|
-
import_node_server_utils34.logger.info(
|
|
7038
|
-
`Schedule ${schedule._id}: Current date ${currentDateFormatted} is not in scheduled dates [${schedule.dates.join(
|
|
7039
|
-
", "
|
|
7040
|
-
)}]`
|
|
7041
|
-
);
|
|
7042
|
-
return false;
|
|
7043
|
-
}
|
|
7044
|
-
const [scheduleHour, scheduleMinute] = schedule.time.split(":").map(Number);
|
|
7045
|
-
const timeMatches = currentHour === scheduleHour && currentMinute === scheduleMinute;
|
|
7046
|
-
if (!timeMatches) {
|
|
7047
|
-
import_node_server_utils34.logger.info(
|
|
7048
|
-
`Schedule ${schedule._id}: Time does not match. Current: ${currentHour}:${currentMinute}, Expected: ${scheduleHour}:${scheduleMinute}`
|
|
7049
|
-
);
|
|
7050
|
-
return false;
|
|
7051
|
-
}
|
|
7052
|
-
const relevantTimestamp = schedule.updatedAt || schedule.createdAt;
|
|
7053
|
-
if (relevantTimestamp) {
|
|
7054
|
-
const savedAt = new Date(relevantTimestamp);
|
|
7055
|
-
const savedHour = parseInt(
|
|
7056
|
-
savedAt.toLocaleTimeString("en-US", {
|
|
7057
|
-
hour: "2-digit",
|
|
7058
|
-
hour12: false,
|
|
7059
|
-
timeZone: "Asia/Singapore"
|
|
7060
|
-
}),
|
|
7061
|
-
10
|
|
7062
|
-
);
|
|
7063
|
-
const savedMinute = savedAt.toLocaleString("en-US", {
|
|
7064
|
-
minute: "2-digit",
|
|
7065
|
-
timeZone: "Asia/Singapore"
|
|
7066
|
-
});
|
|
7067
|
-
const savedDateFormatted = savedAt.toLocaleDateString("en-CA", {
|
|
7068
|
-
timeZone: "Asia/Singapore"
|
|
7069
|
-
});
|
|
7070
|
-
const savedMinNum = parseInt(savedMinute, 10);
|
|
7071
|
-
const wasSavedThisMinute = savedDateFormatted === currentDateFormatted && savedHour === currentHour && savedMinNum === currentMinute;
|
|
7072
|
-
if (wasSavedThisMinute) {
|
|
7073
|
-
import_node_server_utils34.logger.info(
|
|
7074
|
-
`Schedule ${schedule._id}: Skipping \u2014 task was saved this minute (${savedHour}:${String(
|
|
7075
|
-
savedMinNum
|
|
7076
|
-
).padStart(2, "0")}). Will fire on next occurrence.`
|
|
7077
|
-
);
|
|
7078
|
-
return false;
|
|
7079
|
-
}
|
|
7080
|
-
}
|
|
7081
|
-
import_node_server_utils34.logger.info(
|
|
7082
|
-
`Schedule ${schedule._id}: All conditions matched - Date is within range and time matches`
|
|
7083
|
-
);
|
|
7084
|
-
return true;
|
|
7449
|
+
return shouldRunNow(schedule, currentDate);
|
|
7085
7450
|
} catch (error) {
|
|
7086
7451
|
import_node_server_utils34.logger.error(
|
|
7087
7452
|
`Error checking schedule conditions for ${schedule._id}:`,
|
|
@@ -7090,18 +7455,22 @@ function useScheduleTaskService() {
|
|
|
7090
7455
|
return false;
|
|
7091
7456
|
}
|
|
7092
7457
|
}
|
|
7458
|
+
const MAX_ATTEMPTS = 5;
|
|
7459
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
7093
7460
|
async function processScheduledTasks(currentDate) {
|
|
7094
7461
|
try {
|
|
7095
7462
|
import_node_server_utils34.logger.info("Starting scheduled task processing...");
|
|
7096
|
-
const
|
|
7463
|
+
const today = sgtParts(currentDate ?? /* @__PURE__ */ new Date()).date;
|
|
7464
|
+
const scheduleTasks = await getAllScheduleTask(today);
|
|
7097
7465
|
if (!scheduleTasks || scheduleTasks.length === 0) {
|
|
7098
7466
|
import_node_server_utils34.logger.info("No schedule tasks found to process");
|
|
7099
|
-
return { processed: 0, validated: 0 };
|
|
7467
|
+
return { processed: 0, validated: 0, fired: [] };
|
|
7100
7468
|
}
|
|
7101
7469
|
import_node_server_utils34.logger.info(`Found ${scheduleTasks.length} schedule tasks to check`);
|
|
7102
7470
|
let processedCount = 0;
|
|
7103
7471
|
let validatedCount = 0;
|
|
7104
7472
|
const validatedTasks = [];
|
|
7473
|
+
const fired = [];
|
|
7105
7474
|
for (const scheduleTask of scheduleTasks) {
|
|
7106
7475
|
try {
|
|
7107
7476
|
const serviceType = scheduleTask.serviceType;
|
|
@@ -7144,6 +7513,8 @@ function useScheduleTaskService() {
|
|
|
7144
7513
|
import_node_server_utils34.logger.info(
|
|
7145
7514
|
`Using parent checklist ${parentChecklistId}, now creating/updating area checklists`
|
|
7146
7515
|
);
|
|
7516
|
+
let failedAreas = 0;
|
|
7517
|
+
const addedAreas = [];
|
|
7147
7518
|
for (const area of scheduleTask.areas) {
|
|
7148
7519
|
try {
|
|
7149
7520
|
const areaId = area.value.toString();
|
|
@@ -7206,15 +7577,7 @@ function useScheduleTaskService() {
|
|
|
7206
7577
|
)
|
|
7207
7578
|
) : 0;
|
|
7208
7579
|
const startSetNumber = maxSetInChecklist + 1;
|
|
7209
|
-
const newSets =
|
|
7210
|
-
{ length: areaSetConfig },
|
|
7211
|
-
(_, index) => ({
|
|
7212
|
-
set: startSetNumber + index,
|
|
7213
|
-
units,
|
|
7214
|
-
isScheduleTask: true,
|
|
7215
|
-
scheduleTaskId: scheduleTask._id
|
|
7216
|
-
})
|
|
7217
|
-
);
|
|
7580
|
+
const newSets = buildScheduleTaskSets(scheduleTask, units, startSetNumber, areaSetConfig);
|
|
7218
7581
|
const modified = await pushScheduleTaskSets(
|
|
7219
7582
|
parentChecklistId.toString(),
|
|
7220
7583
|
serviceType,
|
|
@@ -7223,6 +7586,7 @@ function useScheduleTaskService() {
|
|
|
7223
7586
|
newSets
|
|
7224
7587
|
);
|
|
7225
7588
|
if (modified > 0) {
|
|
7589
|
+
addedAreas.push(area.name);
|
|
7226
7590
|
import_node_server_utils34.logger.info(
|
|
7227
7591
|
`Appended ${areaSetConfig} set(s) starting from ${startSetNumber} to area checklist for area ${area.name}`
|
|
7228
7592
|
);
|
|
@@ -7233,16 +7597,7 @@ function useScheduleTaskService() {
|
|
|
7233
7597
|
}
|
|
7234
7598
|
} else {
|
|
7235
7599
|
const areaSetConfig = Number(areaDetails.set) || 1;
|
|
7236
|
-
const
|
|
7237
|
-
const schedTaskSets = Array.from(
|
|
7238
|
-
{ length: areaSetConfig },
|
|
7239
|
-
(_, index) => ({
|
|
7240
|
-
set: index + 1,
|
|
7241
|
-
units,
|
|
7242
|
-
isScheduleTask: true,
|
|
7243
|
-
scheduleTaskId: schedTaskId
|
|
7244
|
-
})
|
|
7245
|
-
);
|
|
7600
|
+
const schedTaskSets = buildScheduleTaskSets(scheduleTask, units, 1, areaSetConfig);
|
|
7246
7601
|
const checklistData = {
|
|
7247
7602
|
schedule: parentChecklistId.toString(),
|
|
7248
7603
|
serviceType,
|
|
@@ -7255,13 +7610,32 @@ function useScheduleTaskService() {
|
|
|
7255
7610
|
`Area ${area.name} (${areaId}): Creating new area checklist with schedule-task sets only`
|
|
7256
7611
|
);
|
|
7257
7612
|
await createAreaChecklist(checklistData);
|
|
7613
|
+
addedAreas.push(area.name);
|
|
7258
7614
|
import_node_server_utils34.logger.info(`Created new area checklist for area ${area.name}`);
|
|
7259
7615
|
}
|
|
7260
7616
|
} catch (error) {
|
|
7261
7617
|
import_node_server_utils34.logger.error(`Error processing area ${area.name}:`, error);
|
|
7618
|
+
failedAreas++;
|
|
7262
7619
|
continue;
|
|
7263
7620
|
}
|
|
7264
7621
|
}
|
|
7622
|
+
const attemptKey = `${scheduleTask._id}:${today}`;
|
|
7623
|
+
const tried = (attempts.get(attemptKey) ?? 0) + 1;
|
|
7624
|
+
attempts.set(attemptKey, tried);
|
|
7625
|
+
if (failedAreas === 0 || tried >= MAX_ATTEMPTS) {
|
|
7626
|
+
await markScheduleTaskRan(scheduleTask._id, today);
|
|
7627
|
+
attempts.delete(attemptKey);
|
|
7628
|
+
}
|
|
7629
|
+
if (addedAreas.length > 0) {
|
|
7630
|
+
fired.push({
|
|
7631
|
+
_id: scheduleTask._id,
|
|
7632
|
+
site: scheduleTask.site,
|
|
7633
|
+
serviceType,
|
|
7634
|
+
title: scheduleTask.title,
|
|
7635
|
+
time: scheduleTask.time,
|
|
7636
|
+
areas: addedAreas
|
|
7637
|
+
});
|
|
7638
|
+
}
|
|
7265
7639
|
processedCount++;
|
|
7266
7640
|
validatedCount++;
|
|
7267
7641
|
validatedTasks.push(scheduleTask);
|
|
@@ -7273,6 +7647,17 @@ function useScheduleTaskService() {
|
|
|
7273
7647
|
`Error processing schedule task ${scheduleTask._id}:`,
|
|
7274
7648
|
error
|
|
7275
7649
|
);
|
|
7650
|
+
try {
|
|
7651
|
+
const key = `${scheduleTask._id}:${today}`;
|
|
7652
|
+
const tried = (attempts.get(key) ?? 0) + 1;
|
|
7653
|
+
attempts.set(key, tried);
|
|
7654
|
+
if (tried >= MAX_ATTEMPTS && scheduleTask._id) {
|
|
7655
|
+
await markScheduleTaskRan(scheduleTask._id, today);
|
|
7656
|
+
attempts.delete(key);
|
|
7657
|
+
}
|
|
7658
|
+
} catch (markError) {
|
|
7659
|
+
import_node_server_utils34.logger.error("Could not mark failing schedule task", markError);
|
|
7660
|
+
}
|
|
7276
7661
|
continue;
|
|
7277
7662
|
}
|
|
7278
7663
|
}
|
|
@@ -7282,7 +7667,8 @@ function useScheduleTaskService() {
|
|
|
7282
7667
|
return {
|
|
7283
7668
|
processed: processedCount,
|
|
7284
7669
|
validated: validatedCount,
|
|
7285
|
-
tasks: validatedTasks
|
|
7670
|
+
tasks: validatedTasks,
|
|
7671
|
+
fired
|
|
7286
7672
|
};
|
|
7287
7673
|
} catch (error) {
|
|
7288
7674
|
import_node_server_utils34.logger.error("Error processing scheduled tasks:", error);
|
|
@@ -7293,7 +7679,7 @@ function useScheduleTaskService() {
|
|
|
7293
7679
|
}
|
|
7294
7680
|
|
|
7295
7681
|
// src/controllers/hygiene-schedule-task.controller.ts
|
|
7296
|
-
var
|
|
7682
|
+
var import_joi19 = __toESM(require("joi"));
|
|
7297
7683
|
var import_node_server_utils35 = require("@7365admin1/node-server-utils");
|
|
7298
7684
|
var import_core20 = require("@7365admin1/core");
|
|
7299
7685
|
function useScheduleTaskController() {
|
|
@@ -7324,12 +7710,12 @@ function useScheduleTaskController() {
|
|
|
7324
7710
|
}
|
|
7325
7711
|
async function getScheduleTasks(req, res, next) {
|
|
7326
7712
|
const query = { ...req.query, ...req.params };
|
|
7327
|
-
const validation =
|
|
7328
|
-
page:
|
|
7329
|
-
limit:
|
|
7330
|
-
search:
|
|
7331
|
-
site:
|
|
7332
|
-
serviceType:
|
|
7713
|
+
const validation = import_joi19.default.object({
|
|
7714
|
+
page: import_joi19.default.number().min(1).optional().allow("", null),
|
|
7715
|
+
limit: import_joi19.default.number().min(1).optional().allow("", null),
|
|
7716
|
+
search: import_joi19.default.string().optional().allow("", null),
|
|
7717
|
+
site: import_joi19.default.string().hex().required(),
|
|
7718
|
+
serviceType: import_joi19.default.string().valid(...Object.values(import_core20.AppServiceType)).required()
|
|
7333
7719
|
});
|
|
7334
7720
|
const { error } = validation.validate(query);
|
|
7335
7721
|
if (error) {
|
|
@@ -7337,8 +7723,8 @@ function useScheduleTaskController() {
|
|
|
7337
7723
|
next(new import_node_server_utils35.BadRequestError(error.message));
|
|
7338
7724
|
return;
|
|
7339
7725
|
}
|
|
7340
|
-
const page = parseInt(req.query.page)
|
|
7341
|
-
const limit = parseInt(req.query.limit)
|
|
7726
|
+
const page = parseInt(req.query.page) || 1;
|
|
7727
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
7342
7728
|
const search = req.query.search ?? "";
|
|
7343
7729
|
const site = req.params.site ?? "";
|
|
7344
7730
|
const serviceType = req.query.serviceType ?? "";
|
|
@@ -7359,7 +7745,7 @@ function useScheduleTaskController() {
|
|
|
7359
7745
|
}
|
|
7360
7746
|
}
|
|
7361
7747
|
async function getScheduleTaskById(req, res, next) {
|
|
7362
|
-
const validation =
|
|
7748
|
+
const validation = import_joi19.default.string().hex().required();
|
|
7363
7749
|
const _id = req.params.id;
|
|
7364
7750
|
const { error, value } = validation.validate(_id);
|
|
7365
7751
|
if (error) {
|
|
@@ -7379,20 +7765,17 @@ function useScheduleTaskController() {
|
|
|
7379
7765
|
}
|
|
7380
7766
|
async function updateScheduleTask(req, res, next) {
|
|
7381
7767
|
const payload = { id: req.params.id, ...req.body };
|
|
7382
|
-
const
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
)
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
value: import_joi18.default.any().required()
|
|
7394
|
-
})
|
|
7395
|
-
).optional()
|
|
7768
|
+
const optional = Object.fromEntries(
|
|
7769
|
+
Object.entries(scheduleTaskFields).map(([key, rule]) => [
|
|
7770
|
+
key,
|
|
7771
|
+
key === "dates" ? import_joi19.default.array().min(1).items(scheduleDate).optional().messages({
|
|
7772
|
+
"array.min": "Pick at least one date, or choose a repeat."
|
|
7773
|
+
}) : rule.optional()
|
|
7774
|
+
])
|
|
7775
|
+
);
|
|
7776
|
+
const validation = import_joi19.default.object({
|
|
7777
|
+
id: import_joi19.default.string().hex().required(),
|
|
7778
|
+
...optional
|
|
7396
7779
|
});
|
|
7397
7780
|
const { error } = validation.validate(payload);
|
|
7398
7781
|
if (error) {
|
|
@@ -7400,6 +7783,24 @@ function useScheduleTaskController() {
|
|
|
7400
7783
|
next(new import_node_server_utils35.BadRequestError(error.message));
|
|
7401
7784
|
return;
|
|
7402
7785
|
}
|
|
7786
|
+
try {
|
|
7787
|
+
const { set, unset } = splitScheduleUpdate(req.body ?? {});
|
|
7788
|
+
if (TIMING_KEYS.some((key) => key in set || unset.includes(key))) {
|
|
7789
|
+
const stored = await _getScheduleTaskById(req.params.id);
|
|
7790
|
+
const merged = { ...stored, ...set };
|
|
7791
|
+
for (const key of unset)
|
|
7792
|
+
delete merged[key];
|
|
7793
|
+
const problem = timingChanged(stored, merged) ? validateScheduleRules(cleanScheduleInput(merged)) : null;
|
|
7794
|
+
if (problem) {
|
|
7795
|
+
next(new import_node_server_utils35.BadRequestError(problem));
|
|
7796
|
+
return;
|
|
7797
|
+
}
|
|
7798
|
+
}
|
|
7799
|
+
} catch (error2) {
|
|
7800
|
+
import_node_server_utils35.logger.log({ level: "error", message: error2.message });
|
|
7801
|
+
next(error2);
|
|
7802
|
+
return;
|
|
7803
|
+
}
|
|
7403
7804
|
try {
|
|
7404
7805
|
const { id, ...value } = payload;
|
|
7405
7806
|
await _updateScheduleTask(id, value);
|
|
@@ -7412,7 +7813,7 @@ function useScheduleTaskController() {
|
|
|
7412
7813
|
}
|
|
7413
7814
|
}
|
|
7414
7815
|
async function deleteScheduleTask(req, res, next) {
|
|
7415
|
-
const validation =
|
|
7816
|
+
const validation = import_joi19.default.string().hex().required();
|
|
7416
7817
|
const _id = req.params.id;
|
|
7417
7818
|
const { error, value } = validation.validate(_id);
|
|
7418
7819
|
if (error) {
|
|
@@ -7656,17 +8057,17 @@ function useQRService() {
|
|
|
7656
8057
|
}
|
|
7657
8058
|
|
|
7658
8059
|
// src/controllers/hygiene-qr.controller.ts
|
|
7659
|
-
var
|
|
8060
|
+
var import_joi20 = __toESM(require("joi"));
|
|
7660
8061
|
var import_node_server_utils37 = require("@7365admin1/node-server-utils");
|
|
7661
8062
|
function useQRController() {
|
|
7662
8063
|
const { generateQRImage: _generateQRImage, generateQRPDF: _generateQRPDF } = useQRService();
|
|
7663
8064
|
async function generateQR(req, res, next) {
|
|
7664
|
-
const validation =
|
|
7665
|
-
url:
|
|
7666
|
-
filename:
|
|
7667
|
-
title:
|
|
7668
|
-
subtitle:
|
|
7669
|
-
download:
|
|
8065
|
+
const validation = import_joi20.default.object({
|
|
8066
|
+
url: import_joi20.default.string().uri().required(),
|
|
8067
|
+
filename: import_joi20.default.string().optional().allow("", null),
|
|
8068
|
+
title: import_joi20.default.string().optional().allow("", null),
|
|
8069
|
+
subtitle: import_joi20.default.string().optional().allow("", null),
|
|
8070
|
+
download: import_joi20.default.boolean().optional().default(false)
|
|
7670
8071
|
});
|
|
7671
8072
|
const query = { ...req.query };
|
|
7672
8073
|
const { error, value } = validation.validate(query);
|