@7365admin1/module-hygiene 4.30.3 → 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 +10 -0
- package/dist/index.d.ts +40 -10
- package/dist/index.js +647 -270
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +647 -270
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/escape-regex.util.test.mjs +26 -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);
|
|
@@ -4466,6 +4471,265 @@ function useAreaChecklistRepo() {
|
|
|
4466
4471
|
};
|
|
4467
4472
|
}
|
|
4468
4473
|
|
|
4474
|
+
// src/utils/schedule-task-rules.util.ts
|
|
4475
|
+
var import_joi10 = __toESM(require("joi"));
|
|
4476
|
+
var REPEATS = ["none", "daily", "weekdays", "weekly", "monthly"];
|
|
4477
|
+
var SGT_MS = 8 * 60 * 60 * 1e3;
|
|
4478
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
4479
|
+
var TIME_RE = /^([0-1]\d|2[0-3]):([0-5]\d)$/;
|
|
4480
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
4481
|
+
function sgtParts(now) {
|
|
4482
|
+
const s = new Date(now.getTime() + SGT_MS);
|
|
4483
|
+
const date = `${s.getUTCFullYear()}-${pad(s.getUTCMonth() + 1)}-${pad(s.getUTCDate())}`;
|
|
4484
|
+
return { date, minutes: s.getUTCHours() * 60 + s.getUTCMinutes(), weekday: s.getUTCDay() };
|
|
4485
|
+
}
|
|
4486
|
+
function isRealDate(value) {
|
|
4487
|
+
if (typeof value !== "string" || !DATE_RE.test(value))
|
|
4488
|
+
return false;
|
|
4489
|
+
const [y, m, d] = value.split("-").map(Number);
|
|
4490
|
+
const t = new Date(Date.UTC(y, m - 1, d));
|
|
4491
|
+
return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
|
|
4492
|
+
}
|
|
4493
|
+
var toMinutes = (time) => {
|
|
4494
|
+
const [h, m] = time.split(":").map(Number);
|
|
4495
|
+
return h * 60 + m;
|
|
4496
|
+
};
|
|
4497
|
+
var weekdayOf = (date) => {
|
|
4498
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
4499
|
+
return new Date(Date.UTC(y, m - 1, d)).getUTCDay();
|
|
4500
|
+
};
|
|
4501
|
+
var daysInMonth = (date) => {
|
|
4502
|
+
const [y, m] = date.split("-").map(Number);
|
|
4503
|
+
return new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
4504
|
+
};
|
|
4505
|
+
var addDays = (date, n) => {
|
|
4506
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
4507
|
+
const t = new Date(Date.UTC(y, m - 1, d + n));
|
|
4508
|
+
return `${t.getUTCFullYear()}-${pad(t.getUTCMonth() + 1)}-${pad(t.getUTCDate())}`;
|
|
4509
|
+
};
|
|
4510
|
+
var isRepeating = (task) => Boolean(task.repeat) && task.repeat !== "none";
|
|
4511
|
+
function occursOn(task, date) {
|
|
4512
|
+
if (task.endDate && date > task.endDate)
|
|
4513
|
+
return false;
|
|
4514
|
+
if (!isRepeating(task))
|
|
4515
|
+
return Array.isArray(task.dates) && task.dates.includes(date);
|
|
4516
|
+
if (task.startDate && date < task.startDate)
|
|
4517
|
+
return false;
|
|
4518
|
+
const weekday = weekdayOf(date);
|
|
4519
|
+
switch (task.repeat) {
|
|
4520
|
+
case "daily":
|
|
4521
|
+
return true;
|
|
4522
|
+
case "weekdays":
|
|
4523
|
+
return weekday >= 1 && weekday <= 5;
|
|
4524
|
+
case "weekly":
|
|
4525
|
+
return Array.isArray(task.daysOfWeek) && task.daysOfWeek.includes(weekday);
|
|
4526
|
+
case "monthly": {
|
|
4527
|
+
const wanted = task.dayOfMonth ?? (task.startDate ? Number(task.startDate.slice(8, 10)) : 1);
|
|
4528
|
+
return Number(date.slice(8, 10)) === Math.min(wanted, daysInMonth(date));
|
|
4529
|
+
}
|
|
4530
|
+
default:
|
|
4531
|
+
return false;
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
function savedTodayAt(task, today) {
|
|
4535
|
+
const stamp = task.updatedAt || task.createdAt;
|
|
4536
|
+
if (!stamp)
|
|
4537
|
+
return null;
|
|
4538
|
+
const saved = new Date(stamp);
|
|
4539
|
+
if (Number.isNaN(saved.getTime()))
|
|
4540
|
+
return null;
|
|
4541
|
+
const p = sgtParts(saved);
|
|
4542
|
+
return p.date === today ? p.minutes : null;
|
|
4543
|
+
}
|
|
4544
|
+
function shouldRunNow(task, now = /* @__PURE__ */ new Date()) {
|
|
4545
|
+
if (task.paused || !TIME_RE.test(task.time ?? ""))
|
|
4546
|
+
return false;
|
|
4547
|
+
const { date, minutes } = sgtParts(now);
|
|
4548
|
+
if (task.lastRunDate === date || !occursOn(task, date))
|
|
4549
|
+
return false;
|
|
4550
|
+
const due = toMinutes(task.time);
|
|
4551
|
+
if (minutes < due)
|
|
4552
|
+
return false;
|
|
4553
|
+
const saved = savedTodayAt(task, date);
|
|
4554
|
+
return !(saved !== null && saved >= due);
|
|
4555
|
+
}
|
|
4556
|
+
var iso = (date, time) => `${date}T${time}:00+08:00`;
|
|
4557
|
+
function upcoming(task, now) {
|
|
4558
|
+
if (typeof task.time !== "string" || !TIME_RE.test(task.time))
|
|
4559
|
+
return null;
|
|
4560
|
+
const { date: today, minutes } = sgtParts(now);
|
|
4561
|
+
const due = toMinutes(task.time);
|
|
4562
|
+
if (occursOn(task, today) && (minutes < due || shouldRunNow({ ...task, paused: false }, now))) {
|
|
4563
|
+
return iso(today, task.time);
|
|
4564
|
+
}
|
|
4565
|
+
if (!isRepeating(task)) {
|
|
4566
|
+
const later = (task.dates ?? []).filter((d) => d > today && (!task.endDate || d <= task.endDate)).sort();
|
|
4567
|
+
return later.length ? iso(later[0], task.time) : null;
|
|
4568
|
+
}
|
|
4569
|
+
const from = task.startDate && task.startDate > today ? task.startDate : today;
|
|
4570
|
+
for (let i = from === today ? 1 : 0; i <= 400; i++) {
|
|
4571
|
+
const d = addDays(from, i);
|
|
4572
|
+
if (task.endDate && d > task.endDate)
|
|
4573
|
+
return null;
|
|
4574
|
+
if (occursOn(task, d))
|
|
4575
|
+
return iso(d, task.time);
|
|
4576
|
+
}
|
|
4577
|
+
return null;
|
|
4578
|
+
}
|
|
4579
|
+
function nextRun(task, now = /* @__PURE__ */ new Date()) {
|
|
4580
|
+
return task.paused ? null : upcoming(task, now);
|
|
4581
|
+
}
|
|
4582
|
+
function scheduleState(task, now = /* @__PURE__ */ new Date()) {
|
|
4583
|
+
if (task.paused)
|
|
4584
|
+
return "paused";
|
|
4585
|
+
return upcoming(task, now) ? "active" : "ended";
|
|
4586
|
+
}
|
|
4587
|
+
function lastRun(task) {
|
|
4588
|
+
return task.lastRunDate && TIME_RE.test(task.time ?? "") ? iso(task.lastRunDate, task.time) : null;
|
|
4589
|
+
}
|
|
4590
|
+
function scheduleDisplay(task, now = /* @__PURE__ */ new Date()) {
|
|
4591
|
+
try {
|
|
4592
|
+
return {
|
|
4593
|
+
nextRun: nextRun(task, now),
|
|
4594
|
+
lastRun: lastRun(task),
|
|
4595
|
+
scheduleStatus: scheduleState(task, now)
|
|
4596
|
+
};
|
|
4597
|
+
} catch {
|
|
4598
|
+
return { nextRun: null, lastRun: null, scheduleStatus: null };
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
function validateScheduleRules(task, now = /* @__PURE__ */ new Date()) {
|
|
4602
|
+
const repeating = isRepeating(task);
|
|
4603
|
+
const { date: today, minutes } = sgtParts(now);
|
|
4604
|
+
if (repeating) {
|
|
4605
|
+
if (!task.startDate)
|
|
4606
|
+
return "Please choose a start date for a repeating schedule.";
|
|
4607
|
+
if (task.repeat === "weekly" && !(task.daysOfWeek && task.daysOfWeek.length)) {
|
|
4608
|
+
return "Pick at least one weekday for a weekly repeat.";
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
const start = repeating ? task.startDate : [...task.dates ?? []].sort()[0];
|
|
4612
|
+
if (task.endDate && start && start > task.endDate) {
|
|
4613
|
+
return "The end date must be on or after the start date.";
|
|
4614
|
+
}
|
|
4615
|
+
if (task.endDate && task.endDate < today)
|
|
4616
|
+
return "The end date has already passed.";
|
|
4617
|
+
if (!repeating) {
|
|
4618
|
+
const dates = (task.dates ?? []).filter((d) => !task.endDate || d <= task.endDate);
|
|
4619
|
+
if (dates.length && dates.every((d) => d < today)) {
|
|
4620
|
+
return "All the dates you picked have already passed. Pick today or a later date.";
|
|
4621
|
+
}
|
|
4622
|
+
if (dates.length && dates.every((d) => d <= today) && dates.includes(today) && TIME_RE.test(task.time) && toMinutes(task.time) <= minutes) {
|
|
4623
|
+
return "That time has already passed today. Pick a later time or another date.";
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
if (!upcoming({ ...task, paused: false, updatedAt: now }, now)) {
|
|
4627
|
+
return "This schedule would never run. Check the dates, end date and time.";
|
|
4628
|
+
}
|
|
4629
|
+
return null;
|
|
4630
|
+
}
|
|
4631
|
+
var scheduleDate = import_joi10.default.string().pattern(DATE_RE).custom((value, helpers) => isRealDate(value) ? value : helpers.error("date.invalid")).messages({
|
|
4632
|
+
"string.pattern.base": "Dates must look like 2026-10-21.",
|
|
4633
|
+
"string.empty": "Please choose a date.",
|
|
4634
|
+
"date.invalid": "That date does not exist. Dates must look like 2026-10-21.",
|
|
4635
|
+
"string.base": "Dates must look like 2026-10-21."
|
|
4636
|
+
});
|
|
4637
|
+
var REPEATING = import_joi10.default.valid("daily", "weekdays", "weekly", "monthly").required();
|
|
4638
|
+
var scheduleTaskFields = {
|
|
4639
|
+
title: import_joi10.default.string().trim().required().messages({
|
|
4640
|
+
"any.required": "Please enter a title.",
|
|
4641
|
+
"string.empty": "Please enter a title.",
|
|
4642
|
+
"string.base": "Please enter a title."
|
|
4643
|
+
}),
|
|
4644
|
+
time: import_joi10.default.string().pattern(TIME_RE).required().messages({
|
|
4645
|
+
"any.required": "Please choose a time.",
|
|
4646
|
+
"string.empty": "Please choose a time.",
|
|
4647
|
+
"string.pattern.base": "Time must be in 24-hour format like 08:30.",
|
|
4648
|
+
"string.base": "Time must be in 24-hour format like 08:30."
|
|
4649
|
+
}),
|
|
4650
|
+
dates: import_joi10.default.when("repeat", {
|
|
4651
|
+
is: REPEATING,
|
|
4652
|
+
then: import_joi10.default.array().items(scheduleDate).optional().default([]),
|
|
4653
|
+
otherwise: import_joi10.default.array().min(1).items(scheduleDate).required()
|
|
4654
|
+
}).messages({
|
|
4655
|
+
"any.required": "Pick at least one date, or choose a repeat.",
|
|
4656
|
+
"array.min": "Pick at least one date, or choose a repeat.",
|
|
4657
|
+
"array.base": "Dates must be a list of dates."
|
|
4658
|
+
}),
|
|
4659
|
+
description: import_joi10.default.string().optional().allow("", null),
|
|
4660
|
+
areas: import_joi10.default.array().min(1).items(
|
|
4661
|
+
import_joi10.default.object({
|
|
4662
|
+
name: import_joi10.default.string().required(),
|
|
4663
|
+
value: import_joi10.default.any().required()
|
|
4664
|
+
})
|
|
4665
|
+
).required().messages({
|
|
4666
|
+
"any.required": "Pick at least one area.",
|
|
4667
|
+
"array.min": "Pick at least one area.",
|
|
4668
|
+
"array.base": "Pick at least one area."
|
|
4669
|
+
}),
|
|
4670
|
+
repeat: import_joi10.default.string().valid(...REPEATS).optional().allow("", null).messages({ "any.only": "Repeat must be one of: none, daily, weekdays, weekly, monthly." }),
|
|
4671
|
+
daysOfWeek: import_joi10.default.array().items(import_joi10.default.number().integer().min(0).max(6)).unique().optional().allow(null).messages({
|
|
4672
|
+
"number.base": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4673
|
+
"number.min": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4674
|
+
"number.max": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4675
|
+
"number.integer": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4676
|
+
"array.unique": "Each weekday can only be picked once."
|
|
4677
|
+
}),
|
|
4678
|
+
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." }),
|
|
4679
|
+
startDate: scheduleDate.optional().allow("", null),
|
|
4680
|
+
endDate: scheduleDate.optional().allow("", null),
|
|
4681
|
+
paused: import_joi10.default.boolean().optional().allow(null).messages({ "boolean.base": "Paused must be true or false." })
|
|
4682
|
+
};
|
|
4683
|
+
var OPTIONAL_KEYS = ["repeat", "daysOfWeek", "dayOfMonth", "startDate", "endDate", "paused"];
|
|
4684
|
+
var isBlank = (v) => v === "" || v === null;
|
|
4685
|
+
function cleanScheduleInput(value) {
|
|
4686
|
+
const out = { ...value };
|
|
4687
|
+
for (const key of OPTIONAL_KEYS)
|
|
4688
|
+
if (isBlank(out[key]))
|
|
4689
|
+
delete out[key];
|
|
4690
|
+
if (out.repeat === "none") {
|
|
4691
|
+
delete out.daysOfWeek;
|
|
4692
|
+
delete out.dayOfMonth;
|
|
4693
|
+
delete out.startDate;
|
|
4694
|
+
}
|
|
4695
|
+
return out;
|
|
4696
|
+
}
|
|
4697
|
+
var TIMING_KEYS = ["time", "dates", "repeat", "daysOfWeek", "dayOfMonth", "startDate", "endDate"];
|
|
4698
|
+
function splitScheduleUpdate(payload) {
|
|
4699
|
+
const set = {};
|
|
4700
|
+
const unset = [];
|
|
4701
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
4702
|
+
if (OPTIONAL_KEYS.includes(key) && isBlank(value))
|
|
4703
|
+
unset.push(key);
|
|
4704
|
+
else if (value !== void 0)
|
|
4705
|
+
set[key] = value;
|
|
4706
|
+
}
|
|
4707
|
+
if (set.repeat === "none") {
|
|
4708
|
+
for (const key of ["daysOfWeek", "dayOfMonth", "startDate"]) {
|
|
4709
|
+
if (!unset.includes(key))
|
|
4710
|
+
unset.push(key);
|
|
4711
|
+
delete set[key];
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
return { set, unset };
|
|
4715
|
+
}
|
|
4716
|
+
function regularSetsMissing(checklist, setCount) {
|
|
4717
|
+
const regular = (checklist ?? []).filter((c) => c.isScheduleTask !== true).length;
|
|
4718
|
+
return Math.max(0, setCount - regular);
|
|
4719
|
+
}
|
|
4720
|
+
var normTiming = (t) => ({
|
|
4721
|
+
time: t.time ?? null,
|
|
4722
|
+
dates: [...Array.isArray(t.dates) ? t.dates : []].sort(),
|
|
4723
|
+
repeat: !t.repeat || t.repeat === "none" ? "none" : t.repeat,
|
|
4724
|
+
daysOfWeek: [...Array.isArray(t.daysOfWeek) ? t.daysOfWeek : []].sort(),
|
|
4725
|
+
dayOfMonth: t.dayOfMonth ?? null,
|
|
4726
|
+
startDate: t.startDate || null,
|
|
4727
|
+
endDate: t.endDate || null
|
|
4728
|
+
});
|
|
4729
|
+
function timingChanged(stored, after) {
|
|
4730
|
+
return JSON.stringify(normTiming(stored)) !== JSON.stringify(normTiming(after));
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4469
4733
|
// src/services/hygiene-area-checklist.service.ts
|
|
4470
4734
|
function useAreaChecklistService() {
|
|
4471
4735
|
const { getAreasForChecklist } = useAreaRepo();
|
|
@@ -4533,10 +4797,10 @@ function useAreaChecklistService() {
|
|
|
4533
4797
|
`Area checklist for area ${area.name} exists but has no auto-gen sets. Inserting auto-gen sets.`
|
|
4534
4798
|
);
|
|
4535
4799
|
const setCount2 = Number(area.set) || 1;
|
|
4536
|
-
const
|
|
4537
|
-
if (
|
|
4800
|
+
const setsToAdd = regularSetsMissing(existing.checklist, setCount2);
|
|
4801
|
+
if (setsToAdd <= 0) {
|
|
4538
4802
|
import_node_server_utils18.logger.info(
|
|
4539
|
-
`Area checklist for area ${area.name} already has ${
|
|
4803
|
+
`Area checklist for area ${area.name} already has its ${setCount2} regular set(s). Skipping auto-gen sets.`
|
|
4540
4804
|
);
|
|
4541
4805
|
return existing._id;
|
|
4542
4806
|
}
|
|
@@ -4544,7 +4808,6 @@ function useAreaChecklistService() {
|
|
|
4544
4808
|
(max, c) => Math.max(max, c.set ?? 0),
|
|
4545
4809
|
0
|
|
4546
4810
|
) ?? 0;
|
|
4547
|
-
const setsToAdd = setCount2 - totalExistingSets;
|
|
4548
4811
|
const autoGenSets = Array.from(
|
|
4549
4812
|
{ length: setsToAdd },
|
|
4550
4813
|
(_, index) => ({
|
|
@@ -4705,7 +4968,7 @@ function useAreaChecklistService() {
|
|
|
4705
4968
|
}
|
|
4706
4969
|
|
|
4707
4970
|
// src/controllers/hygiene-area-checklist.controller.ts
|
|
4708
|
-
var
|
|
4971
|
+
var import_joi11 = __toESM(require("joi"));
|
|
4709
4972
|
var import_node_server_utils20 = require("@7365admin1/node-server-utils");
|
|
4710
4973
|
var import_core11 = require("@7365admin1/core");
|
|
4711
4974
|
|
|
@@ -4946,9 +5209,9 @@ function useAreaChecklistController() {
|
|
|
4946
5209
|
site: req.params.site,
|
|
4947
5210
|
schedule: req.params.schedule
|
|
4948
5211
|
};
|
|
4949
|
-
const validation =
|
|
4950
|
-
site:
|
|
4951
|
-
schedule:
|
|
5212
|
+
const validation = import_joi11.default.object({
|
|
5213
|
+
site: import_joi11.default.string().hex().required(),
|
|
5214
|
+
schedule: import_joi11.default.string().hex().required()
|
|
4952
5215
|
});
|
|
4953
5216
|
const { error, value } = validation.validate(payload);
|
|
4954
5217
|
if (error) {
|
|
@@ -4968,14 +5231,14 @@ function useAreaChecklistController() {
|
|
|
4968
5231
|
}
|
|
4969
5232
|
async function getAllAreaChecklist(req, res, next) {
|
|
4970
5233
|
const query = { ...req.query, ...req.params };
|
|
4971
|
-
const validation =
|
|
4972
|
-
page:
|
|
4973
|
-
limit:
|
|
4974
|
-
search:
|
|
4975
|
-
type:
|
|
4976
|
-
status:
|
|
4977
|
-
schedule:
|
|
4978
|
-
serviceType:
|
|
5234
|
+
const validation = import_joi11.default.object({
|
|
5235
|
+
page: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5236
|
+
limit: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5237
|
+
search: import_joi11.default.string().optional().allow("", null),
|
|
5238
|
+
type: import_joi11.default.string().valid(...allowedTypes, "all").optional().allow("", null),
|
|
5239
|
+
status: import_joi11.default.string().valid(...allowedStatus, "all").optional().allow("", null),
|
|
5240
|
+
schedule: import_joi11.default.string().hex().required(),
|
|
5241
|
+
serviceType: import_joi11.default.string().valid(...Object.values(import_core11.AppServiceType)).required()
|
|
4979
5242
|
});
|
|
4980
5243
|
const { error } = validation.validate(query);
|
|
4981
5244
|
if (error) {
|
|
@@ -5010,14 +5273,14 @@ function useAreaChecklistController() {
|
|
|
5010
5273
|
}
|
|
5011
5274
|
async function getAreaChecklistHistory(req, res, next) {
|
|
5012
5275
|
const query = { ...req.query, ...req.params };
|
|
5013
|
-
const validation =
|
|
5014
|
-
page:
|
|
5015
|
-
limit:
|
|
5016
|
-
search:
|
|
5017
|
-
type:
|
|
5018
|
-
schedule:
|
|
5019
|
-
status:
|
|
5020
|
-
createdAt:
|
|
5276
|
+
const validation = import_joi11.default.object({
|
|
5277
|
+
page: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5278
|
+
limit: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5279
|
+
search: import_joi11.default.string().optional().allow("", null),
|
|
5280
|
+
type: import_joi11.default.string().optional().allow("", ...allowedTypes),
|
|
5281
|
+
schedule: import_joi11.default.string().hex().required(),
|
|
5282
|
+
status: import_joi11.default.string().allow("", null, ...allowedStatus),
|
|
5283
|
+
createdAt: import_joi11.default.alternatives().try(import_joi11.default.date(), import_joi11.default.string()).optional().allow("", null)
|
|
5021
5284
|
});
|
|
5022
5285
|
const { error } = validation.validate(query);
|
|
5023
5286
|
if (error) {
|
|
@@ -5051,7 +5314,7 @@ function useAreaChecklistController() {
|
|
|
5051
5314
|
}
|
|
5052
5315
|
}
|
|
5053
5316
|
async function getAreaChecklistHistoryDetails(req, res, next) {
|
|
5054
|
-
const validation =
|
|
5317
|
+
const validation = import_joi11.default.string().hex().required();
|
|
5055
5318
|
const _id = req.params.id;
|
|
5056
5319
|
const { error, value } = validation.validate(_id);
|
|
5057
5320
|
if (error) {
|
|
@@ -5071,12 +5334,12 @@ function useAreaChecklistController() {
|
|
|
5071
5334
|
}
|
|
5072
5335
|
async function getAreaChecklistUnits(req, res, next) {
|
|
5073
5336
|
const query = { ...req.query, ...req.params };
|
|
5074
|
-
const validation =
|
|
5075
|
-
page:
|
|
5076
|
-
limit:
|
|
5077
|
-
search:
|
|
5078
|
-
serviceType:
|
|
5079
|
-
id:
|
|
5337
|
+
const validation = import_joi11.default.object({
|
|
5338
|
+
page: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5339
|
+
limit: import_joi11.default.number().min(1).optional().allow("", null),
|
|
5340
|
+
search: import_joi11.default.string().optional().allow("", null),
|
|
5341
|
+
serviceType: import_joi11.default.string().valid(...Object.values(import_core11.AppServiceType)).required(),
|
|
5342
|
+
id: import_joi11.default.string().hex().required()
|
|
5080
5343
|
});
|
|
5081
5344
|
const { error } = validation.validate(query);
|
|
5082
5345
|
if (error) {
|
|
@@ -5126,21 +5389,21 @@ function useAreaChecklistController() {
|
|
|
5126
5389
|
...decisionValues,
|
|
5127
5390
|
completedBy
|
|
5128
5391
|
};
|
|
5129
|
-
const validation =
|
|
5130
|
-
id:
|
|
5131
|
-
set:
|
|
5132
|
-
unit:
|
|
5133
|
-
decision:
|
|
5134
|
-
approve:
|
|
5135
|
-
reject:
|
|
5136
|
-
remarks:
|
|
5392
|
+
const validation = import_joi11.default.object({
|
|
5393
|
+
id: import_joi11.default.string().hex().required(),
|
|
5394
|
+
set: import_joi11.default.number().integer().min(1).required(),
|
|
5395
|
+
unit: import_joi11.default.string().hex().required(),
|
|
5396
|
+
decision: import_joi11.default.string().valid("approve", "reject").required(),
|
|
5397
|
+
approve: import_joi11.default.boolean().optional(),
|
|
5398
|
+
reject: import_joi11.default.boolean().optional(),
|
|
5399
|
+
remarks: import_joi11.default.string().optional().allow("", null),
|
|
5137
5400
|
/*
|
|
5138
5401
|
* Shape only. WHETHER a photo is required depends on the set's current
|
|
5139
5402
|
* state in the database, which Joi cannot see — see the check below.
|
|
5140
5403
|
* Each entry must still be a real id: a blank string carries no evidence.
|
|
5141
5404
|
*/
|
|
5142
|
-
attachment:
|
|
5143
|
-
completedBy:
|
|
5405
|
+
attachment: import_joi11.default.array().items(import_joi11.default.string().trim().min(1)).optional(),
|
|
5406
|
+
completedBy: import_joi11.default.string().hex().required()
|
|
5144
5407
|
});
|
|
5145
5408
|
const { error } = validation.validate(payload);
|
|
5146
5409
|
if (error) {
|
|
@@ -5177,7 +5440,7 @@ function useAreaChecklistController() {
|
|
|
5177
5440
|
}
|
|
5178
5441
|
}
|
|
5179
5442
|
async function downloadChecklistPdf(req, res, next) {
|
|
5180
|
-
const validation =
|
|
5443
|
+
const validation = import_joi11.default.string().hex().required();
|
|
5181
5444
|
const schedule = req.params.schedule;
|
|
5182
5445
|
const { error, value } = validation.validate(schedule);
|
|
5183
5446
|
if (error) {
|
|
@@ -5223,16 +5486,16 @@ function useAreaChecklistController() {
|
|
|
5223
5486
|
}
|
|
5224
5487
|
|
|
5225
5488
|
// src/models/hygiene-supply.model.ts
|
|
5226
|
-
var
|
|
5489
|
+
var import_joi12 = __toESM(require("joi"));
|
|
5227
5490
|
var import_mongodb12 = require("mongodb");
|
|
5228
5491
|
var import_node_server_utils21 = require("@7365admin1/node-server-utils");
|
|
5229
5492
|
var import_core12 = require("@7365admin1/core");
|
|
5230
|
-
var supplySchema =
|
|
5231
|
-
site:
|
|
5232
|
-
serviceType:
|
|
5233
|
-
name:
|
|
5234
|
-
unitOfMeasurement:
|
|
5235
|
-
attachment:
|
|
5493
|
+
var supplySchema = import_joi12.default.object({
|
|
5494
|
+
site: import_joi12.default.string().hex().required(),
|
|
5495
|
+
serviceType: import_joi12.default.string().valid(...Object.values(import_core12.AppServiceType)).required(),
|
|
5496
|
+
name: import_joi12.default.string().required(),
|
|
5497
|
+
unitOfMeasurement: import_joi12.default.string().required(),
|
|
5498
|
+
attachment: import_joi12.default.string().allow(null, "").optional()
|
|
5236
5499
|
});
|
|
5237
5500
|
function MSupply(value) {
|
|
5238
5501
|
const { error } = supplySchema.validate(value);
|
|
@@ -5360,7 +5623,7 @@ function useSupplyRepository() {
|
|
|
5360
5623
|
throw new import_node_server_utils22.BadRequestError("Invalid site ID format.");
|
|
5361
5624
|
}
|
|
5362
5625
|
if (search) {
|
|
5363
|
-
query.name = { $regex: search, $options: "i" };
|
|
5626
|
+
query.name = { $regex: escapeRegex(search), $options: "i" };
|
|
5364
5627
|
cacheOptions.search = search;
|
|
5365
5628
|
}
|
|
5366
5629
|
const cacheKey = (0, import_node_server_utils22.makeCacheKey)(namespace_collection, cacheOptions);
|
|
@@ -5522,7 +5785,7 @@ function useSupplyRepository() {
|
|
|
5522
5785
|
}
|
|
5523
5786
|
|
|
5524
5787
|
// src/controllers/hygiene-supply.controller.ts
|
|
5525
|
-
var
|
|
5788
|
+
var import_joi13 = __toESM(require("joi"));
|
|
5526
5789
|
var import_node_server_utils23 = require("@7365admin1/node-server-utils");
|
|
5527
5790
|
var import_core13 = require("@7365admin1/core");
|
|
5528
5791
|
function useSupplyController() {
|
|
@@ -5553,12 +5816,12 @@ function useSupplyController() {
|
|
|
5553
5816
|
}
|
|
5554
5817
|
async function getSupplies(req, res, next) {
|
|
5555
5818
|
const query = { ...req.query, ...req.params };
|
|
5556
|
-
const validation =
|
|
5557
|
-
page:
|
|
5558
|
-
limit:
|
|
5559
|
-
search:
|
|
5560
|
-
site:
|
|
5561
|
-
serviceType:
|
|
5819
|
+
const validation = import_joi13.default.object({
|
|
5820
|
+
page: import_joi13.default.number().min(1).optional().allow("", null),
|
|
5821
|
+
limit: import_joi13.default.number().min(1).optional().allow("", null),
|
|
5822
|
+
search: import_joi13.default.string().optional().allow("", null),
|
|
5823
|
+
site: import_joi13.default.string().hex().required(),
|
|
5824
|
+
serviceType: import_joi13.default.string().valid(...Object.values(import_core13.AppServiceType)).required()
|
|
5562
5825
|
});
|
|
5563
5826
|
const { error } = validation.validate(query);
|
|
5564
5827
|
if (error) {
|
|
@@ -5588,7 +5851,7 @@ function useSupplyController() {
|
|
|
5588
5851
|
}
|
|
5589
5852
|
}
|
|
5590
5853
|
async function getSupplyById(req, res, next) {
|
|
5591
|
-
const validation =
|
|
5854
|
+
const validation = import_joi13.default.string().hex().required();
|
|
5592
5855
|
const _id = req.params.id;
|
|
5593
5856
|
const { error, value } = validation.validate(_id);
|
|
5594
5857
|
if (error) {
|
|
@@ -5608,12 +5871,12 @@ function useSupplyController() {
|
|
|
5608
5871
|
}
|
|
5609
5872
|
async function updateSupply(req, res, next) {
|
|
5610
5873
|
const payload = { id: req.params.id, ...req.body };
|
|
5611
|
-
const validation =
|
|
5612
|
-
id:
|
|
5613
|
-
name:
|
|
5614
|
-
unitOfMeasurement:
|
|
5615
|
-
qty:
|
|
5616
|
-
attachment:
|
|
5874
|
+
const validation = import_joi13.default.object({
|
|
5875
|
+
id: import_joi13.default.string().hex().required(),
|
|
5876
|
+
name: import_joi13.default.string().optional().allow("", null),
|
|
5877
|
+
unitOfMeasurement: import_joi13.default.string().optional().allow("", null),
|
|
5878
|
+
qty: import_joi13.default.number().min(0).optional().allow("", null),
|
|
5879
|
+
attachment: import_joi13.default.string().optional().allow("", null)
|
|
5617
5880
|
});
|
|
5618
5881
|
const { error } = validation.validate(payload);
|
|
5619
5882
|
if (error) {
|
|
@@ -5634,7 +5897,7 @@ function useSupplyController() {
|
|
|
5634
5897
|
}
|
|
5635
5898
|
async function deleteSupply(req, res, next) {
|
|
5636
5899
|
const id = req.params.id;
|
|
5637
|
-
const validation =
|
|
5900
|
+
const validation = import_joi13.default.string().hex().required();
|
|
5638
5901
|
const { error, value } = validation.validate(id);
|
|
5639
5902
|
if (error) {
|
|
5640
5903
|
import_node_server_utils23.logger.log({ level: "error", message: error.message });
|
|
@@ -5661,18 +5924,18 @@ function useSupplyController() {
|
|
|
5661
5924
|
}
|
|
5662
5925
|
|
|
5663
5926
|
// src/models/hygiene-stock.model.ts
|
|
5664
|
-
var
|
|
5927
|
+
var import_joi14 = __toESM(require("joi"));
|
|
5665
5928
|
var import_mongodb14 = require("mongodb");
|
|
5666
5929
|
var import_node_server_utils24 = require("@7365admin1/node-server-utils");
|
|
5667
5930
|
var import_core14 = require("@7365admin1/core");
|
|
5668
|
-
var stockSchema =
|
|
5669
|
-
site:
|
|
5670
|
-
serviceType:
|
|
5671
|
-
supply:
|
|
5672
|
-
in:
|
|
5673
|
-
out:
|
|
5674
|
-
balance:
|
|
5675
|
-
remarks:
|
|
5931
|
+
var stockSchema = import_joi14.default.object({
|
|
5932
|
+
site: import_joi14.default.string().hex().required(),
|
|
5933
|
+
serviceType: import_joi14.default.string().valid(...Object.values(import_core14.AppServiceType)).required(),
|
|
5934
|
+
supply: import_joi14.default.string().hex().required(),
|
|
5935
|
+
in: import_joi14.default.number().min(0).optional(),
|
|
5936
|
+
out: import_joi14.default.number().min(0).optional(),
|
|
5937
|
+
balance: import_joi14.default.number().min(0).required(),
|
|
5938
|
+
remarks: import_joi14.default.string().optional().allow("", null)
|
|
5676
5939
|
});
|
|
5677
5940
|
function MStock(value) {
|
|
5678
5941
|
const { error } = stockSchema.validate(value);
|
|
@@ -5887,7 +6150,7 @@ function useStockService() {
|
|
|
5887
6150
|
}
|
|
5888
6151
|
|
|
5889
6152
|
// src/controllers/hygiene-stock.controller.ts
|
|
5890
|
-
var
|
|
6153
|
+
var import_joi15 = __toESM(require("joi"));
|
|
5891
6154
|
var import_node_server_utils27 = require("@7365admin1/node-server-utils");
|
|
5892
6155
|
var import_core15 = require("@7365admin1/core");
|
|
5893
6156
|
function useStockController() {
|
|
@@ -5895,12 +6158,12 @@ function useStockController() {
|
|
|
5895
6158
|
const { createStock: _createStock } = useStockService();
|
|
5896
6159
|
async function createStock(req, res, next) {
|
|
5897
6160
|
const payload = { ...req.body, ...req.params };
|
|
5898
|
-
const validation =
|
|
5899
|
-
site:
|
|
5900
|
-
serviceType:
|
|
5901
|
-
supply:
|
|
5902
|
-
qty:
|
|
5903
|
-
remarks:
|
|
6161
|
+
const validation = import_joi15.default.object({
|
|
6162
|
+
site: import_joi15.default.string().hex().required(),
|
|
6163
|
+
serviceType: import_joi15.default.string().valid(...Object.values(import_core15.AppServiceType)).required(),
|
|
6164
|
+
supply: import_joi15.default.string().hex().required(),
|
|
6165
|
+
qty: import_joi15.default.number().min(0).required(),
|
|
6166
|
+
remarks: import_joi15.default.string().optional().allow("", null)
|
|
5904
6167
|
});
|
|
5905
6168
|
const { error } = validation.validate(payload);
|
|
5906
6169
|
if (error) {
|
|
@@ -5920,13 +6183,13 @@ function useStockController() {
|
|
|
5920
6183
|
}
|
|
5921
6184
|
async function getStocksBySupplyId(req, res, next) {
|
|
5922
6185
|
const query = { ...req.query, ...req.params };
|
|
5923
|
-
const validation =
|
|
5924
|
-
page:
|
|
5925
|
-
limit:
|
|
5926
|
-
search:
|
|
5927
|
-
site:
|
|
5928
|
-
serviceType:
|
|
5929
|
-
supply:
|
|
6186
|
+
const validation = import_joi15.default.object({
|
|
6187
|
+
page: import_joi15.default.number().min(1).optional().allow("", null),
|
|
6188
|
+
limit: import_joi15.default.number().min(1).optional().allow("", null),
|
|
6189
|
+
search: import_joi15.default.string().optional().allow("", null),
|
|
6190
|
+
site: import_joi15.default.string().hex().required(),
|
|
6191
|
+
serviceType: import_joi15.default.string().valid(...Object.values(import_core15.AppServiceType)).required(),
|
|
6192
|
+
supply: import_joi15.default.string().hex().required()
|
|
5930
6193
|
});
|
|
5931
6194
|
const { error } = validation.validate(query);
|
|
5932
6195
|
if (error) {
|
|
@@ -5988,21 +6251,21 @@ async function applyCheckOutDecision(decision, decide, returnStock) {
|
|
|
5988
6251
|
}
|
|
5989
6252
|
|
|
5990
6253
|
// src/models/hygiene-checkout-item.model.ts
|
|
5991
|
-
var
|
|
6254
|
+
var import_joi16 = __toESM(require("joi"));
|
|
5992
6255
|
var import_mongodb16 = require("mongodb");
|
|
5993
6256
|
var import_node_server_utils28 = require("@7365admin1/node-server-utils");
|
|
5994
6257
|
var import_core16 = require("@7365admin1/core");
|
|
5995
6258
|
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:
|
|
6259
|
+
var checkOutItemSchema = import_joi16.default.object({
|
|
6260
|
+
site: import_joi16.default.string().hex().required(),
|
|
6261
|
+
serviceType: import_joi16.default.string().valid(...Object.values(import_core16.AppServiceType)).required(),
|
|
6262
|
+
supply: import_joi16.default.string().hex().required(),
|
|
6263
|
+
supplyName: import_joi16.default.string().required(),
|
|
6264
|
+
supplyQty: import_joi16.default.number().min(0).required(),
|
|
6265
|
+
qty: import_joi16.default.number().min(0).required(),
|
|
6266
|
+
attachment: import_joi16.default.array().items(import_joi16.default.string()).optional().allow(null),
|
|
6267
|
+
createdBy: import_joi16.default.string().hex().required(),
|
|
6268
|
+
createdByName: import_joi16.default.string().required()
|
|
6006
6269
|
});
|
|
6007
6270
|
function MCheckOutItem(value) {
|
|
6008
6271
|
const { error } = checkOutItemSchema.validate(value);
|
|
@@ -6126,7 +6389,7 @@ function useCheckOutItemRepository() {
|
|
|
6126
6389
|
throw new import_node_server_utils29.BadRequestError("Invalid site ID format.");
|
|
6127
6390
|
}
|
|
6128
6391
|
if (search) {
|
|
6129
|
-
query.supplyName = { $regex: search, $options: "i" };
|
|
6392
|
+
query.supplyName = { $regex: escapeRegex(search), $options: "i" };
|
|
6130
6393
|
cacheOptions.search = search;
|
|
6131
6394
|
}
|
|
6132
6395
|
const cacheKey = (0, import_node_server_utils29.makeCacheKey)(namespace_collection, cacheOptions);
|
|
@@ -6482,7 +6745,7 @@ function useCheckOutItemService() {
|
|
|
6482
6745
|
}
|
|
6483
6746
|
|
|
6484
6747
|
// src/controllers/hygiene-checkout-item.controller.ts
|
|
6485
|
-
var
|
|
6748
|
+
var import_joi17 = __toESM(require("joi"));
|
|
6486
6749
|
var import_node_server_utils31 = require("@7365admin1/node-server-utils");
|
|
6487
6750
|
var import_core18 = require("@7365admin1/core");
|
|
6488
6751
|
function useCheckOutItemController() {
|
|
@@ -6506,13 +6769,13 @@ function useCheckOutItemController() {
|
|
|
6506
6769
|
...req.params,
|
|
6507
6770
|
createdBy
|
|
6508
6771
|
};
|
|
6509
|
-
const validation =
|
|
6510
|
-
site:
|
|
6511
|
-
serviceType:
|
|
6512
|
-
supply:
|
|
6513
|
-
qty:
|
|
6514
|
-
attachment:
|
|
6515
|
-
createdBy:
|
|
6772
|
+
const validation = import_joi17.default.object({
|
|
6773
|
+
site: import_joi17.default.string().hex().required(),
|
|
6774
|
+
serviceType: import_joi17.default.string().valid(...Object.values(import_core18.AppServiceType)).required(),
|
|
6775
|
+
supply: import_joi17.default.string().hex().required(),
|
|
6776
|
+
qty: import_joi17.default.number().min(0).required(),
|
|
6777
|
+
attachment: import_joi17.default.array().items(import_joi17.default.string()).optional().allow(null),
|
|
6778
|
+
createdBy: import_joi17.default.string().hex().required()
|
|
6516
6779
|
});
|
|
6517
6780
|
const { error } = validation.validate(payload);
|
|
6518
6781
|
if (error) {
|
|
@@ -6541,15 +6804,15 @@ function useCheckOutItemController() {
|
|
|
6541
6804
|
...req.params,
|
|
6542
6805
|
createdBy
|
|
6543
6806
|
};
|
|
6544
|
-
const validation =
|
|
6545
|
-
site:
|
|
6546
|
-
serviceType:
|
|
6547
|
-
createdBy:
|
|
6548
|
-
items:
|
|
6549
|
-
|
|
6550
|
-
supply:
|
|
6551
|
-
qty:
|
|
6552
|
-
attachment:
|
|
6807
|
+
const validation = import_joi17.default.object({
|
|
6808
|
+
site: import_joi17.default.string().hex().required(),
|
|
6809
|
+
serviceType: import_joi17.default.string().valid(...Object.values(import_core18.AppServiceType)).required(),
|
|
6810
|
+
createdBy: import_joi17.default.string().hex().required(),
|
|
6811
|
+
items: import_joi17.default.array().items(
|
|
6812
|
+
import_joi17.default.object({
|
|
6813
|
+
supply: import_joi17.default.string().hex().required(),
|
|
6814
|
+
qty: import_joi17.default.number().min(0).required(),
|
|
6815
|
+
attachment: import_joi17.default.array().items(import_joi17.default.string()).optional().allow(null)
|
|
6553
6816
|
})
|
|
6554
6817
|
).min(1).required()
|
|
6555
6818
|
});
|
|
@@ -6571,12 +6834,12 @@ function useCheckOutItemController() {
|
|
|
6571
6834
|
}
|
|
6572
6835
|
async function getCheckOutItems(req, res, next) {
|
|
6573
6836
|
const query = { ...req.query, ...req.params };
|
|
6574
|
-
const validation =
|
|
6575
|
-
page:
|
|
6576
|
-
limit:
|
|
6577
|
-
search:
|
|
6578
|
-
site:
|
|
6579
|
-
serviceType:
|
|
6837
|
+
const validation = import_joi17.default.object({
|
|
6838
|
+
page: import_joi17.default.number().min(1).optional().allow("", null),
|
|
6839
|
+
limit: import_joi17.default.number().min(1).optional().allow("", null),
|
|
6840
|
+
search: import_joi17.default.string().optional().allow("", null),
|
|
6841
|
+
site: import_joi17.default.string().hex().required(),
|
|
6842
|
+
serviceType: import_joi17.default.string().valid(...Object.values(import_core18.AppServiceType)).required()
|
|
6580
6843
|
});
|
|
6581
6844
|
const { error } = validation.validate(query);
|
|
6582
6845
|
if (error) {
|
|
@@ -6606,7 +6869,7 @@ function useCheckOutItemController() {
|
|
|
6606
6869
|
}
|
|
6607
6870
|
}
|
|
6608
6871
|
async function getCheckOutItemById(req, res, next) {
|
|
6609
|
-
const validation =
|
|
6872
|
+
const validation = import_joi17.default.string().hex().required();
|
|
6610
6873
|
const _id = req.params.id;
|
|
6611
6874
|
const { error, value } = validation.validate(_id);
|
|
6612
6875
|
if (error) {
|
|
@@ -6645,13 +6908,13 @@ function useCheckOutItemController() {
|
|
|
6645
6908
|
...decisionValues,
|
|
6646
6909
|
completedBy
|
|
6647
6910
|
};
|
|
6648
|
-
const validation =
|
|
6649
|
-
id:
|
|
6650
|
-
decision:
|
|
6651
|
-
approve:
|
|
6652
|
-
reject:
|
|
6653
|
-
remarks:
|
|
6654
|
-
completedBy:
|
|
6911
|
+
const validation = import_joi17.default.object({
|
|
6912
|
+
id: import_joi17.default.string().hex().required(),
|
|
6913
|
+
decision: import_joi17.default.string().valid("approve", "disapprove").required(),
|
|
6914
|
+
approve: import_joi17.default.boolean().required(),
|
|
6915
|
+
reject: import_joi17.default.boolean().required(),
|
|
6916
|
+
remarks: import_joi17.default.string().optional().allow("", null),
|
|
6917
|
+
completedBy: import_joi17.default.string().hex().required()
|
|
6655
6918
|
});
|
|
6656
6919
|
const { error } = validation.validate(payload);
|
|
6657
6920
|
if (error) {
|
|
@@ -6684,23 +6947,15 @@ function useCheckOutItemController() {
|
|
|
6684
6947
|
// src/models/hygiene-schedule-task.model.ts
|
|
6685
6948
|
var import_node_server_utils32 = require("@7365admin1/node-server-utils");
|
|
6686
6949
|
var import_core19 = require("@7365admin1/core");
|
|
6687
|
-
var
|
|
6950
|
+
var import_joi18 = __toESM(require("joi"));
|
|
6688
6951
|
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()
|
|
6952
|
+
var scheduleTaskSchema = import_joi18.default.object({
|
|
6953
|
+
site: import_joi18.default.string().hex().required(),
|
|
6954
|
+
serviceType: import_joi18.default.string().valid(...Object.values(import_core19.AppServiceType)).required(),
|
|
6955
|
+
...scheduleTaskFields
|
|
6956
|
+
}).custom((value, helpers) => {
|
|
6957
|
+
const message = validateScheduleRules(cleanScheduleInput(value));
|
|
6958
|
+
return message ? helpers.message({ custom: message }) : value;
|
|
6704
6959
|
});
|
|
6705
6960
|
function MScheduleTask(value) {
|
|
6706
6961
|
const { error } = scheduleTaskSchema.validate(value);
|
|
@@ -6727,14 +6982,26 @@ function MScheduleTask(value) {
|
|
|
6727
6982
|
}
|
|
6728
6983
|
});
|
|
6729
6984
|
}
|
|
6985
|
+
const extra = cleanScheduleInput({
|
|
6986
|
+
repeat: value.repeat,
|
|
6987
|
+
daysOfWeek: value.daysOfWeek,
|
|
6988
|
+
dayOfMonth: value.dayOfMonth,
|
|
6989
|
+
startDate: value.startDate,
|
|
6990
|
+
endDate: value.endDate,
|
|
6991
|
+
paused: value.paused
|
|
6992
|
+
});
|
|
6993
|
+
for (const key of Object.keys(extra))
|
|
6994
|
+
if (extra[key] === void 0)
|
|
6995
|
+
delete extra[key];
|
|
6730
6996
|
return {
|
|
6731
6997
|
site: value.site,
|
|
6732
6998
|
serviceType: value.serviceType,
|
|
6733
6999
|
title: value.title,
|
|
6734
7000
|
time: value.time,
|
|
6735
|
-
dates: value.dates,
|
|
7001
|
+
dates: value.dates ?? [],
|
|
6736
7002
|
description: value.description,
|
|
6737
7003
|
areas: value.areas,
|
|
7004
|
+
...extra,
|
|
6738
7005
|
status: "active",
|
|
6739
7006
|
createdAt: /* @__PURE__ */ new Date(),
|
|
6740
7007
|
updatedAt: "",
|
|
@@ -6745,11 +7012,23 @@ function MScheduleTask(value) {
|
|
|
6745
7012
|
// src/repositories/hygiene-schedule-task.repository.ts
|
|
6746
7013
|
var import_mongodb19 = require("mongodb");
|
|
6747
7014
|
var import_node_server_utils33 = require("@7365admin1/node-server-utils");
|
|
7015
|
+
var SCHEDULE_FIELDS = {
|
|
7016
|
+
time: 1,
|
|
7017
|
+
dates: 1,
|
|
7018
|
+
repeat: 1,
|
|
7019
|
+
daysOfWeek: 1,
|
|
7020
|
+
dayOfMonth: 1,
|
|
7021
|
+
startDate: 1,
|
|
7022
|
+
endDate: 1,
|
|
7023
|
+
paused: 1,
|
|
7024
|
+
lastRunDate: 1
|
|
7025
|
+
};
|
|
6748
7026
|
function useScheduleTaskRepository() {
|
|
6749
7027
|
const db = import_node_server_utils33.useAtlas.getDb();
|
|
6750
7028
|
if (!db) {
|
|
6751
7029
|
throw new import_node_server_utils33.InternalServerError("Unable to connect to server.");
|
|
6752
7030
|
}
|
|
7031
|
+
const database = db;
|
|
6753
7032
|
const namespace_collection = "site.schedule-tasks";
|
|
6754
7033
|
const collection = db.collection(namespace_collection);
|
|
6755
7034
|
const { delNamespace, setCache, getCache } = (0, import_node_server_utils33.useCache)(namespace_collection);
|
|
@@ -6817,14 +7096,14 @@ function useScheduleTaskRepository() {
|
|
|
6817
7096
|
throw new import_node_server_utils33.BadRequestError("Invalid site ID format.");
|
|
6818
7097
|
}
|
|
6819
7098
|
if (search) {
|
|
6820
|
-
query.$or = [{ title: { $regex: search, $options: "i" } }];
|
|
7099
|
+
query.$or = [{ title: { $regex: escapeRegex(search), $options: "i" } }];
|
|
6821
7100
|
cacheOptions.search = search;
|
|
6822
7101
|
}
|
|
6823
7102
|
const cacheKey = (0, import_node_server_utils33.makeCacheKey)(namespace_collection, cacheOptions);
|
|
6824
7103
|
const cachedData = await getCache(cacheKey);
|
|
6825
7104
|
if (cachedData) {
|
|
6826
7105
|
import_node_server_utils33.logger.info(`Cache hit for key: ${cacheKey}`);
|
|
6827
|
-
return cachedData;
|
|
7106
|
+
return enrichList(cachedData, site, serviceType);
|
|
6828
7107
|
}
|
|
6829
7108
|
try {
|
|
6830
7109
|
const items = await collection.aggregate([
|
|
@@ -6833,7 +7112,8 @@ function useScheduleTaskRepository() {
|
|
|
6833
7112
|
$project: {
|
|
6834
7113
|
title: 1,
|
|
6835
7114
|
areas: 1,
|
|
6836
|
-
status: 1
|
|
7115
|
+
status: 1,
|
|
7116
|
+
...SCHEDULE_FIELDS
|
|
6837
7117
|
}
|
|
6838
7118
|
},
|
|
6839
7119
|
{ $sort: { _id: -1 } },
|
|
@@ -6847,15 +7127,104 @@ function useScheduleTaskRepository() {
|
|
|
6847
7127
|
}).catch((err) => {
|
|
6848
7128
|
import_node_server_utils33.logger.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
6849
7129
|
});
|
|
6850
|
-
return data;
|
|
7130
|
+
return enrichList(data, site, serviceType);
|
|
6851
7131
|
} catch (error) {
|
|
6852
7132
|
throw error;
|
|
6853
7133
|
}
|
|
6854
7134
|
}
|
|
6855
|
-
async function
|
|
7135
|
+
async function enrichList(data, site, serviceType) {
|
|
7136
|
+
const items = data?.items ?? [];
|
|
7137
|
+
const now = /* @__PURE__ */ new Date();
|
|
7138
|
+
let statuses = /* @__PURE__ */ new Map();
|
|
7139
|
+
try {
|
|
7140
|
+
statuses = await getRunStatuses(items, site, serviceType);
|
|
7141
|
+
} catch (error) {
|
|
7142
|
+
import_node_server_utils33.logger.error("Could not read schedule task run status", error);
|
|
7143
|
+
}
|
|
7144
|
+
return {
|
|
7145
|
+
...data,
|
|
7146
|
+
items: items.map((item) => {
|
|
7147
|
+
try {
|
|
7148
|
+
return {
|
|
7149
|
+
...item,
|
|
7150
|
+
...scheduleDisplay(item, now),
|
|
7151
|
+
lastRunStatus: statuses.get(String(item._id)) ?? null
|
|
7152
|
+
};
|
|
7153
|
+
} catch (error) {
|
|
7154
|
+
import_node_server_utils33.logger.error(`Could not enrich schedule task ${item?._id}`, error);
|
|
7155
|
+
return { ...item, nextRun: null, lastRun: null, scheduleStatus: null, lastRunStatus: null };
|
|
7156
|
+
}
|
|
7157
|
+
})
|
|
7158
|
+
};
|
|
7159
|
+
}
|
|
7160
|
+
async function getRunStatuses(items, site, serviceType) {
|
|
7161
|
+
const result = /* @__PURE__ */ new Map();
|
|
7162
|
+
const ran = items.filter((i) => i.lastRunDate);
|
|
7163
|
+
if (!ran.length)
|
|
7164
|
+
return result;
|
|
7165
|
+
const parents = await database.collection("site.cleaning.schedules").find(
|
|
7166
|
+
{
|
|
7167
|
+
site,
|
|
7168
|
+
serviceType,
|
|
7169
|
+
dateStr: { $in: [...new Set(ran.map((i) => i.lastRunDate))] }
|
|
7170
|
+
},
|
|
7171
|
+
{ projection: { dateStr: 1 } }
|
|
7172
|
+
).toArray();
|
|
7173
|
+
if (!parents.length)
|
|
7174
|
+
return result;
|
|
7175
|
+
const dateOf = new Map(parents.map((p) => [String(p._id), p.dateStr]));
|
|
7176
|
+
const taskIds = ran.map((i) => new import_mongodb19.ObjectId(i._id));
|
|
7177
|
+
const rows = await database.collection("site.cleaning.schedule.areas").aggregate([
|
|
7178
|
+
{
|
|
7179
|
+
$match: {
|
|
7180
|
+
schedule: { $in: parents.map((p) => p._id) },
|
|
7181
|
+
"checklist.scheduleTaskId": { $in: taskIds }
|
|
7182
|
+
}
|
|
7183
|
+
},
|
|
7184
|
+
{ $unwind: "$checklist" },
|
|
7185
|
+
{ $match: { "checklist.scheduleTaskId": { $in: taskIds } } },
|
|
7186
|
+
{ $unwind: "$checklist.units" },
|
|
7187
|
+
{
|
|
7188
|
+
$group: {
|
|
7189
|
+
_id: { task: "$checklist.scheduleTaskId", schedule: "$schedule" },
|
|
7190
|
+
total: { $sum: 1 },
|
|
7191
|
+
completed: {
|
|
7192
|
+
$sum: { $cond: [{ $eq: ["$checklist.units.status", "completed"] }, 1, 0] }
|
|
7193
|
+
},
|
|
7194
|
+
closed: {
|
|
7195
|
+
$sum: { $cond: [{ $eq: ["$checklist.units.status", "closed"] }, 1, 0] }
|
|
7196
|
+
}
|
|
7197
|
+
}
|
|
7198
|
+
}
|
|
7199
|
+
]).toArray();
|
|
7200
|
+
const sums = /* @__PURE__ */ new Map();
|
|
7201
|
+
for (const row of rows) {
|
|
7202
|
+
const task = ran.find((i) => String(i._id) === String(row._id.task));
|
|
7203
|
+
if (!task || dateOf.get(String(row._id.schedule)) !== task.lastRunDate)
|
|
7204
|
+
continue;
|
|
7205
|
+
const sum = sums.get(String(task._id)) ?? { total: 0, completed: 0, closed: 0 };
|
|
7206
|
+
sum.total += row.total;
|
|
7207
|
+
sum.completed += row.completed;
|
|
7208
|
+
sum.closed += row.closed;
|
|
7209
|
+
sums.set(String(task._id), sum);
|
|
7210
|
+
}
|
|
7211
|
+
for (const [id, sum] of sums) {
|
|
7212
|
+
result.set(id, sum.completed === sum.total ? "done" : sum.closed > 0 ? "missed" : "pending");
|
|
7213
|
+
}
|
|
7214
|
+
return result;
|
|
7215
|
+
}
|
|
7216
|
+
async function getAllScheduleTask(today) {
|
|
6856
7217
|
const query = {
|
|
6857
7218
|
status: { $ne: "deleted" }
|
|
6858
7219
|
};
|
|
7220
|
+
if (today) {
|
|
7221
|
+
query.paused = { $ne: true };
|
|
7222
|
+
query.lastRunDate = { $ne: today };
|
|
7223
|
+
query.$or = [
|
|
7224
|
+
{ dates: today },
|
|
7225
|
+
{ repeat: { $in: ["daily", "weekdays", "weekly", "monthly"] } }
|
|
7226
|
+
];
|
|
7227
|
+
}
|
|
6859
7228
|
try {
|
|
6860
7229
|
const items = await collection.aggregate([{ $match: query }, { $sort: { _id: -1 } }]).toArray();
|
|
6861
7230
|
return items;
|
|
@@ -6863,6 +7232,9 @@ function useScheduleTaskRepository() {
|
|
|
6863
7232
|
throw error;
|
|
6864
7233
|
}
|
|
6865
7234
|
}
|
|
7235
|
+
function withScheduleDisplay(row) {
|
|
7236
|
+
return { ...row, ...scheduleDisplay(row, /* @__PURE__ */ new Date()) };
|
|
7237
|
+
}
|
|
6866
7238
|
async function getScheduleTaskById(_id, session) {
|
|
6867
7239
|
try {
|
|
6868
7240
|
_id = new import_mongodb19.ObjectId(_id);
|
|
@@ -6880,7 +7252,7 @@ function useScheduleTaskRepository() {
|
|
|
6880
7252
|
const cachedData = await getCache(cacheKey);
|
|
6881
7253
|
if (cachedData) {
|
|
6882
7254
|
import_node_server_utils33.logger.info(`Cache hit for key: ${cacheKey}`);
|
|
6883
|
-
return cachedData;
|
|
7255
|
+
return withScheduleDisplay(cachedData);
|
|
6884
7256
|
}
|
|
6885
7257
|
} else {
|
|
6886
7258
|
import_node_server_utils33.logger.info(`Skipping cache during transaction for key: ${cacheKey}`);
|
|
@@ -6891,12 +7263,13 @@ function useScheduleTaskRepository() {
|
|
|
6891
7263
|
{
|
|
6892
7264
|
$project: {
|
|
6893
7265
|
title: 1,
|
|
6894
|
-
time: 1,
|
|
6895
|
-
dates: 1,
|
|
6896
7266
|
description: 1,
|
|
6897
7267
|
areas: 1,
|
|
6898
7268
|
status: 1,
|
|
6899
|
-
createdAt: 1
|
|
7269
|
+
createdAt: 1,
|
|
7270
|
+
site: 1,
|
|
7271
|
+
serviceType: 1,
|
|
7272
|
+
...SCHEDULE_FIELDS
|
|
6900
7273
|
}
|
|
6901
7274
|
}
|
|
6902
7275
|
]).toArray();
|
|
@@ -6908,7 +7281,7 @@ function useScheduleTaskRepository() {
|
|
|
6908
7281
|
}).catch((err) => {
|
|
6909
7282
|
import_node_server_utils33.logger.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
6910
7283
|
});
|
|
6911
|
-
return data[0];
|
|
7284
|
+
return withScheduleDisplay(data[0]);
|
|
6912
7285
|
} catch (error) {
|
|
6913
7286
|
throw error;
|
|
6914
7287
|
}
|
|
@@ -6932,16 +7305,20 @@ function useScheduleTaskRepository() {
|
|
|
6932
7305
|
});
|
|
6933
7306
|
}
|
|
6934
7307
|
try {
|
|
6935
|
-
const
|
|
7308
|
+
const { set, unset } = splitScheduleUpdate(value);
|
|
7309
|
+
const update = {
|
|
7310
|
+
$set: { ...set, updatedAt: /* @__PURE__ */ new Date() }
|
|
7311
|
+
};
|
|
7312
|
+
if (unset.length) {
|
|
7313
|
+
update.$unset = Object.fromEntries(unset.map((key) => [key, ""]));
|
|
7314
|
+
}
|
|
6936
7315
|
const res = await collection.updateOne(
|
|
6937
|
-
{ _id },
|
|
6938
|
-
|
|
7316
|
+
{ _id, status: { $ne: "deleted" } },
|
|
7317
|
+
update,
|
|
6939
7318
|
{ session }
|
|
6940
7319
|
);
|
|
6941
|
-
if (res.
|
|
6942
|
-
throw new import_node_server_utils33.
|
|
6943
|
-
"Unable to update hygiene schedule task."
|
|
6944
|
-
);
|
|
7320
|
+
if (res.matchedCount === 0) {
|
|
7321
|
+
throw new import_node_server_utils33.NotFoundError("Schedule task not found.");
|
|
6945
7322
|
}
|
|
6946
7323
|
delNamespace().then(() => {
|
|
6947
7324
|
import_node_server_utils33.logger.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
@@ -6956,6 +7333,18 @@ function useScheduleTaskRepository() {
|
|
|
6956
7333
|
throw error;
|
|
6957
7334
|
}
|
|
6958
7335
|
}
|
|
7336
|
+
async function markScheduleTaskRan(_id, date) {
|
|
7337
|
+
await collection.updateOne(
|
|
7338
|
+
{ _id: new import_mongodb19.ObjectId(_id) },
|
|
7339
|
+
{ $set: { lastRunDate: date, lastRunAt: /* @__PURE__ */ new Date() } }
|
|
7340
|
+
);
|
|
7341
|
+
delNamespace().catch((err) => {
|
|
7342
|
+
import_node_server_utils33.logger.error(
|
|
7343
|
+
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
7344
|
+
err
|
|
7345
|
+
);
|
|
7346
|
+
});
|
|
7347
|
+
}
|
|
6959
7348
|
async function deleteScheduleTask(_id, session) {
|
|
6960
7349
|
try {
|
|
6961
7350
|
_id = new import_mongodb19.ObjectId(_id);
|
|
@@ -6995,6 +7384,7 @@ function useScheduleTaskRepository() {
|
|
|
6995
7384
|
createScheduleTask,
|
|
6996
7385
|
getScheduleTasks,
|
|
6997
7386
|
getAllScheduleTask,
|
|
7387
|
+
markScheduleTaskRan,
|
|
6998
7388
|
getScheduleTaskById,
|
|
6999
7389
|
updateScheduleTask,
|
|
7000
7390
|
deleteScheduleTask
|
|
@@ -7006,7 +7396,7 @@ var import_mongodb20 = require("mongodb");
|
|
|
7006
7396
|
var import_node_server_utils34 = require("@7365admin1/node-server-utils");
|
|
7007
7397
|
function useScheduleTaskService() {
|
|
7008
7398
|
const { createParentChecklist } = useParentChecklistRepo();
|
|
7009
|
-
const { getAllScheduleTask } = useScheduleTaskRepository();
|
|
7399
|
+
const { getAllScheduleTask, markScheduleTaskRan } = useScheduleTaskRepository();
|
|
7010
7400
|
const {
|
|
7011
7401
|
createAreaChecklist,
|
|
7012
7402
|
getAreaChecklistByAreaAndSchedule,
|
|
@@ -7015,73 +7405,7 @@ function useScheduleTaskService() {
|
|
|
7015
7405
|
const { getAreaById } = useAreaRepo();
|
|
7016
7406
|
function checkScheduleConditions(schedule, serviceType, currentDate = /* @__PURE__ */ new Date()) {
|
|
7017
7407
|
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;
|
|
7408
|
+
return shouldRunNow(schedule, currentDate);
|
|
7085
7409
|
} catch (error) {
|
|
7086
7410
|
import_node_server_utils34.logger.error(
|
|
7087
7411
|
`Error checking schedule conditions for ${schedule._id}:`,
|
|
@@ -7090,18 +7414,22 @@ function useScheduleTaskService() {
|
|
|
7090
7414
|
return false;
|
|
7091
7415
|
}
|
|
7092
7416
|
}
|
|
7417
|
+
const MAX_ATTEMPTS = 5;
|
|
7418
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
7093
7419
|
async function processScheduledTasks(currentDate) {
|
|
7094
7420
|
try {
|
|
7095
7421
|
import_node_server_utils34.logger.info("Starting scheduled task processing...");
|
|
7096
|
-
const
|
|
7422
|
+
const today = sgtParts(currentDate ?? /* @__PURE__ */ new Date()).date;
|
|
7423
|
+
const scheduleTasks = await getAllScheduleTask(today);
|
|
7097
7424
|
if (!scheduleTasks || scheduleTasks.length === 0) {
|
|
7098
7425
|
import_node_server_utils34.logger.info("No schedule tasks found to process");
|
|
7099
|
-
return { processed: 0, validated: 0 };
|
|
7426
|
+
return { processed: 0, validated: 0, fired: [] };
|
|
7100
7427
|
}
|
|
7101
7428
|
import_node_server_utils34.logger.info(`Found ${scheduleTasks.length} schedule tasks to check`);
|
|
7102
7429
|
let processedCount = 0;
|
|
7103
7430
|
let validatedCount = 0;
|
|
7104
7431
|
const validatedTasks = [];
|
|
7432
|
+
const fired = [];
|
|
7105
7433
|
for (const scheduleTask of scheduleTasks) {
|
|
7106
7434
|
try {
|
|
7107
7435
|
const serviceType = scheduleTask.serviceType;
|
|
@@ -7144,6 +7472,8 @@ function useScheduleTaskService() {
|
|
|
7144
7472
|
import_node_server_utils34.logger.info(
|
|
7145
7473
|
`Using parent checklist ${parentChecklistId}, now creating/updating area checklists`
|
|
7146
7474
|
);
|
|
7475
|
+
let failedAreas = 0;
|
|
7476
|
+
const addedAreas = [];
|
|
7147
7477
|
for (const area of scheduleTask.areas) {
|
|
7148
7478
|
try {
|
|
7149
7479
|
const areaId = area.value.toString();
|
|
@@ -7223,6 +7553,7 @@ function useScheduleTaskService() {
|
|
|
7223
7553
|
newSets
|
|
7224
7554
|
);
|
|
7225
7555
|
if (modified > 0) {
|
|
7556
|
+
addedAreas.push(area.name);
|
|
7226
7557
|
import_node_server_utils34.logger.info(
|
|
7227
7558
|
`Appended ${areaSetConfig} set(s) starting from ${startSetNumber} to area checklist for area ${area.name}`
|
|
7228
7559
|
);
|
|
@@ -7255,13 +7586,32 @@ function useScheduleTaskService() {
|
|
|
7255
7586
|
`Area ${area.name} (${areaId}): Creating new area checklist with schedule-task sets only`
|
|
7256
7587
|
);
|
|
7257
7588
|
await createAreaChecklist(checklistData);
|
|
7589
|
+
addedAreas.push(area.name);
|
|
7258
7590
|
import_node_server_utils34.logger.info(`Created new area checklist for area ${area.name}`);
|
|
7259
7591
|
}
|
|
7260
7592
|
} catch (error) {
|
|
7261
7593
|
import_node_server_utils34.logger.error(`Error processing area ${area.name}:`, error);
|
|
7594
|
+
failedAreas++;
|
|
7262
7595
|
continue;
|
|
7263
7596
|
}
|
|
7264
7597
|
}
|
|
7598
|
+
const attemptKey = `${scheduleTask._id}:${today}`;
|
|
7599
|
+
const tried = (attempts.get(attemptKey) ?? 0) + 1;
|
|
7600
|
+
attempts.set(attemptKey, tried);
|
|
7601
|
+
if (failedAreas === 0 || tried >= MAX_ATTEMPTS) {
|
|
7602
|
+
await markScheduleTaskRan(scheduleTask._id, today);
|
|
7603
|
+
attempts.delete(attemptKey);
|
|
7604
|
+
}
|
|
7605
|
+
if (addedAreas.length > 0) {
|
|
7606
|
+
fired.push({
|
|
7607
|
+
_id: scheduleTask._id,
|
|
7608
|
+
site: scheduleTask.site,
|
|
7609
|
+
serviceType,
|
|
7610
|
+
title: scheduleTask.title,
|
|
7611
|
+
time: scheduleTask.time,
|
|
7612
|
+
areas: addedAreas
|
|
7613
|
+
});
|
|
7614
|
+
}
|
|
7265
7615
|
processedCount++;
|
|
7266
7616
|
validatedCount++;
|
|
7267
7617
|
validatedTasks.push(scheduleTask);
|
|
@@ -7273,6 +7623,17 @@ function useScheduleTaskService() {
|
|
|
7273
7623
|
`Error processing schedule task ${scheduleTask._id}:`,
|
|
7274
7624
|
error
|
|
7275
7625
|
);
|
|
7626
|
+
try {
|
|
7627
|
+
const key = `${scheduleTask._id}:${today}`;
|
|
7628
|
+
const tried = (attempts.get(key) ?? 0) + 1;
|
|
7629
|
+
attempts.set(key, tried);
|
|
7630
|
+
if (tried >= MAX_ATTEMPTS && scheduleTask._id) {
|
|
7631
|
+
await markScheduleTaskRan(scheduleTask._id, today);
|
|
7632
|
+
attempts.delete(key);
|
|
7633
|
+
}
|
|
7634
|
+
} catch (markError) {
|
|
7635
|
+
import_node_server_utils34.logger.error("Could not mark failing schedule task", markError);
|
|
7636
|
+
}
|
|
7276
7637
|
continue;
|
|
7277
7638
|
}
|
|
7278
7639
|
}
|
|
@@ -7282,7 +7643,8 @@ function useScheduleTaskService() {
|
|
|
7282
7643
|
return {
|
|
7283
7644
|
processed: processedCount,
|
|
7284
7645
|
validated: validatedCount,
|
|
7285
|
-
tasks: validatedTasks
|
|
7646
|
+
tasks: validatedTasks,
|
|
7647
|
+
fired
|
|
7286
7648
|
};
|
|
7287
7649
|
} catch (error) {
|
|
7288
7650
|
import_node_server_utils34.logger.error("Error processing scheduled tasks:", error);
|
|
@@ -7293,7 +7655,7 @@ function useScheduleTaskService() {
|
|
|
7293
7655
|
}
|
|
7294
7656
|
|
|
7295
7657
|
// src/controllers/hygiene-schedule-task.controller.ts
|
|
7296
|
-
var
|
|
7658
|
+
var import_joi19 = __toESM(require("joi"));
|
|
7297
7659
|
var import_node_server_utils35 = require("@7365admin1/node-server-utils");
|
|
7298
7660
|
var import_core20 = require("@7365admin1/core");
|
|
7299
7661
|
function useScheduleTaskController() {
|
|
@@ -7324,12 +7686,12 @@ function useScheduleTaskController() {
|
|
|
7324
7686
|
}
|
|
7325
7687
|
async function getScheduleTasks(req, res, next) {
|
|
7326
7688
|
const query = { ...req.query, ...req.params };
|
|
7327
|
-
const validation =
|
|
7328
|
-
page:
|
|
7329
|
-
limit:
|
|
7330
|
-
search:
|
|
7331
|
-
site:
|
|
7332
|
-
serviceType:
|
|
7689
|
+
const validation = import_joi19.default.object({
|
|
7690
|
+
page: import_joi19.default.number().min(1).optional().allow("", null),
|
|
7691
|
+
limit: import_joi19.default.number().min(1).optional().allow("", null),
|
|
7692
|
+
search: import_joi19.default.string().optional().allow("", null),
|
|
7693
|
+
site: import_joi19.default.string().hex().required(),
|
|
7694
|
+
serviceType: import_joi19.default.string().valid(...Object.values(import_core20.AppServiceType)).required()
|
|
7333
7695
|
});
|
|
7334
7696
|
const { error } = validation.validate(query);
|
|
7335
7697
|
if (error) {
|
|
@@ -7337,8 +7699,8 @@ function useScheduleTaskController() {
|
|
|
7337
7699
|
next(new import_node_server_utils35.BadRequestError(error.message));
|
|
7338
7700
|
return;
|
|
7339
7701
|
}
|
|
7340
|
-
const page = parseInt(req.query.page)
|
|
7341
|
-
const limit = parseInt(req.query.limit)
|
|
7702
|
+
const page = parseInt(req.query.page) || 1;
|
|
7703
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
7342
7704
|
const search = req.query.search ?? "";
|
|
7343
7705
|
const site = req.params.site ?? "";
|
|
7344
7706
|
const serviceType = req.query.serviceType ?? "";
|
|
@@ -7359,7 +7721,7 @@ function useScheduleTaskController() {
|
|
|
7359
7721
|
}
|
|
7360
7722
|
}
|
|
7361
7723
|
async function getScheduleTaskById(req, res, next) {
|
|
7362
|
-
const validation =
|
|
7724
|
+
const validation = import_joi19.default.string().hex().required();
|
|
7363
7725
|
const _id = req.params.id;
|
|
7364
7726
|
const { error, value } = validation.validate(_id);
|
|
7365
7727
|
if (error) {
|
|
@@ -7379,20 +7741,17 @@ function useScheduleTaskController() {
|
|
|
7379
7741
|
}
|
|
7380
7742
|
async function updateScheduleTask(req, res, next) {
|
|
7381
7743
|
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()
|
|
7744
|
+
const optional = Object.fromEntries(
|
|
7745
|
+
Object.entries(scheduleTaskFields).map(([key, rule]) => [
|
|
7746
|
+
key,
|
|
7747
|
+
key === "dates" ? import_joi19.default.array().min(1).items(scheduleDate).optional().messages({
|
|
7748
|
+
"array.min": "Pick at least one date, or choose a repeat."
|
|
7749
|
+
}) : rule.optional()
|
|
7750
|
+
])
|
|
7751
|
+
);
|
|
7752
|
+
const validation = import_joi19.default.object({
|
|
7753
|
+
id: import_joi19.default.string().hex().required(),
|
|
7754
|
+
...optional
|
|
7396
7755
|
});
|
|
7397
7756
|
const { error } = validation.validate(payload);
|
|
7398
7757
|
if (error) {
|
|
@@ -7400,6 +7759,24 @@ function useScheduleTaskController() {
|
|
|
7400
7759
|
next(new import_node_server_utils35.BadRequestError(error.message));
|
|
7401
7760
|
return;
|
|
7402
7761
|
}
|
|
7762
|
+
try {
|
|
7763
|
+
const { set, unset } = splitScheduleUpdate(req.body ?? {});
|
|
7764
|
+
if (TIMING_KEYS.some((key) => key in set || unset.includes(key))) {
|
|
7765
|
+
const stored = await _getScheduleTaskById(req.params.id);
|
|
7766
|
+
const merged = { ...stored, ...set };
|
|
7767
|
+
for (const key of unset)
|
|
7768
|
+
delete merged[key];
|
|
7769
|
+
const problem = timingChanged(stored, merged) ? validateScheduleRules(cleanScheduleInput(merged)) : null;
|
|
7770
|
+
if (problem) {
|
|
7771
|
+
next(new import_node_server_utils35.BadRequestError(problem));
|
|
7772
|
+
return;
|
|
7773
|
+
}
|
|
7774
|
+
}
|
|
7775
|
+
} catch (error2) {
|
|
7776
|
+
import_node_server_utils35.logger.log({ level: "error", message: error2.message });
|
|
7777
|
+
next(error2);
|
|
7778
|
+
return;
|
|
7779
|
+
}
|
|
7403
7780
|
try {
|
|
7404
7781
|
const { id, ...value } = payload;
|
|
7405
7782
|
await _updateScheduleTask(id, value);
|
|
@@ -7412,7 +7789,7 @@ function useScheduleTaskController() {
|
|
|
7412
7789
|
}
|
|
7413
7790
|
}
|
|
7414
7791
|
async function deleteScheduleTask(req, res, next) {
|
|
7415
|
-
const validation =
|
|
7792
|
+
const validation = import_joi19.default.string().hex().required();
|
|
7416
7793
|
const _id = req.params.id;
|
|
7417
7794
|
const { error, value } = validation.validate(_id);
|
|
7418
7795
|
if (error) {
|
|
@@ -7656,17 +8033,17 @@ function useQRService() {
|
|
|
7656
8033
|
}
|
|
7657
8034
|
|
|
7658
8035
|
// src/controllers/hygiene-qr.controller.ts
|
|
7659
|
-
var
|
|
8036
|
+
var import_joi20 = __toESM(require("joi"));
|
|
7660
8037
|
var import_node_server_utils37 = require("@7365admin1/node-server-utils");
|
|
7661
8038
|
function useQRController() {
|
|
7662
8039
|
const { generateQRImage: _generateQRImage, generateQRPDF: _generateQRPDF } = useQRService();
|
|
7663
8040
|
async function generateQR(req, res, next) {
|
|
7664
|
-
const validation =
|
|
7665
|
-
url:
|
|
7666
|
-
filename:
|
|
7667
|
-
title:
|
|
7668
|
-
subtitle:
|
|
7669
|
-
download:
|
|
8041
|
+
const validation = import_joi20.default.object({
|
|
8042
|
+
url: import_joi20.default.string().uri().required(),
|
|
8043
|
+
filename: import_joi20.default.string().optional().allow("", null),
|
|
8044
|
+
title: import_joi20.default.string().optional().allow("", null),
|
|
8045
|
+
subtitle: import_joi20.default.string().optional().allow("", null),
|
|
8046
|
+
download: import_joi20.default.boolean().optional().default(false)
|
|
7670
8047
|
});
|
|
7671
8048
|
const query = { ...req.query };
|
|
7672
8049
|
const { error, value } = validation.validate(query);
|