@7365admin1/module-hygiene 4.29.1-staging.15 → 4.29.1-staging.16
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/.changeset/schedule-task-repeat-pause.md +5 -0
- package/dist/index.d.ts +40 -10
- package/dist/index.js +637 -265
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +637 -265
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/schedule-task-rules.test.mjs +185 -0
package/dist/index.mjs
CHANGED
|
@@ -4436,6 +4436,265 @@ function useAreaChecklistRepo() {
|
|
|
4436
4436
|
};
|
|
4437
4437
|
}
|
|
4438
4438
|
|
|
4439
|
+
// src/utils/schedule-task-rules.util.ts
|
|
4440
|
+
import Joi10 from "joi";
|
|
4441
|
+
var REPEATS = ["none", "daily", "weekdays", "weekly", "monthly"];
|
|
4442
|
+
var SGT_MS = 8 * 60 * 60 * 1e3;
|
|
4443
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
4444
|
+
var TIME_RE = /^([0-1]\d|2[0-3]):([0-5]\d)$/;
|
|
4445
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
4446
|
+
function sgtParts(now) {
|
|
4447
|
+
const s = new Date(now.getTime() + SGT_MS);
|
|
4448
|
+
const date = `${s.getUTCFullYear()}-${pad(s.getUTCMonth() + 1)}-${pad(s.getUTCDate())}`;
|
|
4449
|
+
return { date, minutes: s.getUTCHours() * 60 + s.getUTCMinutes(), weekday: s.getUTCDay() };
|
|
4450
|
+
}
|
|
4451
|
+
function isRealDate(value) {
|
|
4452
|
+
if (typeof value !== "string" || !DATE_RE.test(value))
|
|
4453
|
+
return false;
|
|
4454
|
+
const [y, m, d] = value.split("-").map(Number);
|
|
4455
|
+
const t = new Date(Date.UTC(y, m - 1, d));
|
|
4456
|
+
return t.getUTCFullYear() === y && t.getUTCMonth() === m - 1 && t.getUTCDate() === d;
|
|
4457
|
+
}
|
|
4458
|
+
var toMinutes = (time) => {
|
|
4459
|
+
const [h, m] = time.split(":").map(Number);
|
|
4460
|
+
return h * 60 + m;
|
|
4461
|
+
};
|
|
4462
|
+
var weekdayOf = (date) => {
|
|
4463
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
4464
|
+
return new Date(Date.UTC(y, m - 1, d)).getUTCDay();
|
|
4465
|
+
};
|
|
4466
|
+
var daysInMonth = (date) => {
|
|
4467
|
+
const [y, m] = date.split("-").map(Number);
|
|
4468
|
+
return new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
4469
|
+
};
|
|
4470
|
+
var addDays = (date, n) => {
|
|
4471
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
4472
|
+
const t = new Date(Date.UTC(y, m - 1, d + n));
|
|
4473
|
+
return `${t.getUTCFullYear()}-${pad(t.getUTCMonth() + 1)}-${pad(t.getUTCDate())}`;
|
|
4474
|
+
};
|
|
4475
|
+
var isRepeating = (task) => Boolean(task.repeat) && task.repeat !== "none";
|
|
4476
|
+
function occursOn(task, date) {
|
|
4477
|
+
if (task.endDate && date > task.endDate)
|
|
4478
|
+
return false;
|
|
4479
|
+
if (!isRepeating(task))
|
|
4480
|
+
return Array.isArray(task.dates) && task.dates.includes(date);
|
|
4481
|
+
if (task.startDate && date < task.startDate)
|
|
4482
|
+
return false;
|
|
4483
|
+
const weekday = weekdayOf(date);
|
|
4484
|
+
switch (task.repeat) {
|
|
4485
|
+
case "daily":
|
|
4486
|
+
return true;
|
|
4487
|
+
case "weekdays":
|
|
4488
|
+
return weekday >= 1 && weekday <= 5;
|
|
4489
|
+
case "weekly":
|
|
4490
|
+
return Array.isArray(task.daysOfWeek) && task.daysOfWeek.includes(weekday);
|
|
4491
|
+
case "monthly": {
|
|
4492
|
+
const wanted = task.dayOfMonth ?? (task.startDate ? Number(task.startDate.slice(8, 10)) : 1);
|
|
4493
|
+
return Number(date.slice(8, 10)) === Math.min(wanted, daysInMonth(date));
|
|
4494
|
+
}
|
|
4495
|
+
default:
|
|
4496
|
+
return false;
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
function savedTodayAt(task, today) {
|
|
4500
|
+
const stamp = task.updatedAt || task.createdAt;
|
|
4501
|
+
if (!stamp)
|
|
4502
|
+
return null;
|
|
4503
|
+
const saved = new Date(stamp);
|
|
4504
|
+
if (Number.isNaN(saved.getTime()))
|
|
4505
|
+
return null;
|
|
4506
|
+
const p = sgtParts(saved);
|
|
4507
|
+
return p.date === today ? p.minutes : null;
|
|
4508
|
+
}
|
|
4509
|
+
function shouldRunNow(task, now = /* @__PURE__ */ new Date()) {
|
|
4510
|
+
if (task.paused || !TIME_RE.test(task.time ?? ""))
|
|
4511
|
+
return false;
|
|
4512
|
+
const { date, minutes } = sgtParts(now);
|
|
4513
|
+
if (task.lastRunDate === date || !occursOn(task, date))
|
|
4514
|
+
return false;
|
|
4515
|
+
const due = toMinutes(task.time);
|
|
4516
|
+
if (minutes < due)
|
|
4517
|
+
return false;
|
|
4518
|
+
const saved = savedTodayAt(task, date);
|
|
4519
|
+
return !(saved !== null && saved >= due);
|
|
4520
|
+
}
|
|
4521
|
+
var iso = (date, time) => `${date}T${time}:00+08:00`;
|
|
4522
|
+
function upcoming(task, now) {
|
|
4523
|
+
if (typeof task.time !== "string" || !TIME_RE.test(task.time))
|
|
4524
|
+
return null;
|
|
4525
|
+
const { date: today, minutes } = sgtParts(now);
|
|
4526
|
+
const due = toMinutes(task.time);
|
|
4527
|
+
if (occursOn(task, today) && (minutes < due || shouldRunNow({ ...task, paused: false }, now))) {
|
|
4528
|
+
return iso(today, task.time);
|
|
4529
|
+
}
|
|
4530
|
+
if (!isRepeating(task)) {
|
|
4531
|
+
const later = (task.dates ?? []).filter((d) => d > today && (!task.endDate || d <= task.endDate)).sort();
|
|
4532
|
+
return later.length ? iso(later[0], task.time) : null;
|
|
4533
|
+
}
|
|
4534
|
+
const from = task.startDate && task.startDate > today ? task.startDate : today;
|
|
4535
|
+
for (let i = from === today ? 1 : 0; i <= 400; i++) {
|
|
4536
|
+
const d = addDays(from, i);
|
|
4537
|
+
if (task.endDate && d > task.endDate)
|
|
4538
|
+
return null;
|
|
4539
|
+
if (occursOn(task, d))
|
|
4540
|
+
return iso(d, task.time);
|
|
4541
|
+
}
|
|
4542
|
+
return null;
|
|
4543
|
+
}
|
|
4544
|
+
function nextRun(task, now = /* @__PURE__ */ new Date()) {
|
|
4545
|
+
return task.paused ? null : upcoming(task, now);
|
|
4546
|
+
}
|
|
4547
|
+
function scheduleState(task, now = /* @__PURE__ */ new Date()) {
|
|
4548
|
+
if (task.paused)
|
|
4549
|
+
return "paused";
|
|
4550
|
+
return upcoming(task, now) ? "active" : "ended";
|
|
4551
|
+
}
|
|
4552
|
+
function lastRun(task) {
|
|
4553
|
+
return task.lastRunDate && TIME_RE.test(task.time ?? "") ? iso(task.lastRunDate, task.time) : null;
|
|
4554
|
+
}
|
|
4555
|
+
function scheduleDisplay(task, now = /* @__PURE__ */ new Date()) {
|
|
4556
|
+
try {
|
|
4557
|
+
return {
|
|
4558
|
+
nextRun: nextRun(task, now),
|
|
4559
|
+
lastRun: lastRun(task),
|
|
4560
|
+
scheduleStatus: scheduleState(task, now)
|
|
4561
|
+
};
|
|
4562
|
+
} catch {
|
|
4563
|
+
return { nextRun: null, lastRun: null, scheduleStatus: null };
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
function validateScheduleRules(task, now = /* @__PURE__ */ new Date()) {
|
|
4567
|
+
const repeating = isRepeating(task);
|
|
4568
|
+
const { date: today, minutes } = sgtParts(now);
|
|
4569
|
+
if (repeating) {
|
|
4570
|
+
if (!task.startDate)
|
|
4571
|
+
return "Please choose a start date for a repeating schedule.";
|
|
4572
|
+
if (task.repeat === "weekly" && !(task.daysOfWeek && task.daysOfWeek.length)) {
|
|
4573
|
+
return "Pick at least one weekday for a weekly repeat.";
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
const start = repeating ? task.startDate : [...task.dates ?? []].sort()[0];
|
|
4577
|
+
if (task.endDate && start && start > task.endDate) {
|
|
4578
|
+
return "The end date must be on or after the start date.";
|
|
4579
|
+
}
|
|
4580
|
+
if (task.endDate && task.endDate < today)
|
|
4581
|
+
return "The end date has already passed.";
|
|
4582
|
+
if (!repeating) {
|
|
4583
|
+
const dates = (task.dates ?? []).filter((d) => !task.endDate || d <= task.endDate);
|
|
4584
|
+
if (dates.length && dates.every((d) => d < today)) {
|
|
4585
|
+
return "All the dates you picked have already passed. Pick today or a later date.";
|
|
4586
|
+
}
|
|
4587
|
+
if (dates.length && dates.every((d) => d <= today) && dates.includes(today) && TIME_RE.test(task.time) && toMinutes(task.time) <= minutes) {
|
|
4588
|
+
return "That time has already passed today. Pick a later time or another date.";
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
if (!upcoming({ ...task, paused: false, updatedAt: now }, now)) {
|
|
4592
|
+
return "This schedule would never run. Check the dates, end date and time.";
|
|
4593
|
+
}
|
|
4594
|
+
return null;
|
|
4595
|
+
}
|
|
4596
|
+
var scheduleDate = Joi10.string().pattern(DATE_RE).custom((value, helpers) => isRealDate(value) ? value : helpers.error("date.invalid")).messages({
|
|
4597
|
+
"string.pattern.base": "Dates must look like 2026-10-21.",
|
|
4598
|
+
"string.empty": "Please choose a date.",
|
|
4599
|
+
"date.invalid": "That date does not exist. Dates must look like 2026-10-21.",
|
|
4600
|
+
"string.base": "Dates must look like 2026-10-21."
|
|
4601
|
+
});
|
|
4602
|
+
var REPEATING = Joi10.valid("daily", "weekdays", "weekly", "monthly").required();
|
|
4603
|
+
var scheduleTaskFields = {
|
|
4604
|
+
title: Joi10.string().trim().required().messages({
|
|
4605
|
+
"any.required": "Please enter a title.",
|
|
4606
|
+
"string.empty": "Please enter a title.",
|
|
4607
|
+
"string.base": "Please enter a title."
|
|
4608
|
+
}),
|
|
4609
|
+
time: Joi10.string().pattern(TIME_RE).required().messages({
|
|
4610
|
+
"any.required": "Please choose a time.",
|
|
4611
|
+
"string.empty": "Please choose a time.",
|
|
4612
|
+
"string.pattern.base": "Time must be in 24-hour format like 08:30.",
|
|
4613
|
+
"string.base": "Time must be in 24-hour format like 08:30."
|
|
4614
|
+
}),
|
|
4615
|
+
dates: Joi10.when("repeat", {
|
|
4616
|
+
is: REPEATING,
|
|
4617
|
+
then: Joi10.array().items(scheduleDate).optional().default([]),
|
|
4618
|
+
otherwise: Joi10.array().min(1).items(scheduleDate).required()
|
|
4619
|
+
}).messages({
|
|
4620
|
+
"any.required": "Pick at least one date, or choose a repeat.",
|
|
4621
|
+
"array.min": "Pick at least one date, or choose a repeat.",
|
|
4622
|
+
"array.base": "Dates must be a list of dates."
|
|
4623
|
+
}),
|
|
4624
|
+
description: Joi10.string().optional().allow("", null),
|
|
4625
|
+
areas: Joi10.array().min(1).items(
|
|
4626
|
+
Joi10.object({
|
|
4627
|
+
name: Joi10.string().required(),
|
|
4628
|
+
value: Joi10.any().required()
|
|
4629
|
+
})
|
|
4630
|
+
).required().messages({
|
|
4631
|
+
"any.required": "Pick at least one area.",
|
|
4632
|
+
"array.min": "Pick at least one area.",
|
|
4633
|
+
"array.base": "Pick at least one area."
|
|
4634
|
+
}),
|
|
4635
|
+
repeat: Joi10.string().valid(...REPEATS).optional().allow("", null).messages({ "any.only": "Repeat must be one of: none, daily, weekdays, weekly, monthly." }),
|
|
4636
|
+
daysOfWeek: Joi10.array().items(Joi10.number().integer().min(0).max(6)).unique().optional().allow(null).messages({
|
|
4637
|
+
"number.base": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4638
|
+
"number.min": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4639
|
+
"number.max": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4640
|
+
"number.integer": "Weekdays must be numbers from 0 (Sunday) to 6 (Saturday).",
|
|
4641
|
+
"array.unique": "Each weekday can only be picked once."
|
|
4642
|
+
}),
|
|
4643
|
+
dayOfMonth: Joi10.number().integer().min(1).max(31).optional().allow(null).messages({ "number.base": "Day of the month must be 1 to 31.", "number.min": "Day of the month must be 1 to 31.", "number.max": "Day of the month must be 1 to 31." }),
|
|
4644
|
+
startDate: scheduleDate.optional().allow("", null),
|
|
4645
|
+
endDate: scheduleDate.optional().allow("", null),
|
|
4646
|
+
paused: Joi10.boolean().optional().allow(null).messages({ "boolean.base": "Paused must be true or false." })
|
|
4647
|
+
};
|
|
4648
|
+
var OPTIONAL_KEYS = ["repeat", "daysOfWeek", "dayOfMonth", "startDate", "endDate", "paused"];
|
|
4649
|
+
var isBlank = (v) => v === "" || v === null;
|
|
4650
|
+
function cleanScheduleInput(value) {
|
|
4651
|
+
const out = { ...value };
|
|
4652
|
+
for (const key of OPTIONAL_KEYS)
|
|
4653
|
+
if (isBlank(out[key]))
|
|
4654
|
+
delete out[key];
|
|
4655
|
+
if (out.repeat === "none") {
|
|
4656
|
+
delete out.daysOfWeek;
|
|
4657
|
+
delete out.dayOfMonth;
|
|
4658
|
+
delete out.startDate;
|
|
4659
|
+
}
|
|
4660
|
+
return out;
|
|
4661
|
+
}
|
|
4662
|
+
var TIMING_KEYS = ["time", "dates", "repeat", "daysOfWeek", "dayOfMonth", "startDate", "endDate"];
|
|
4663
|
+
function splitScheduleUpdate(payload) {
|
|
4664
|
+
const set = {};
|
|
4665
|
+
const unset = [];
|
|
4666
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
4667
|
+
if (OPTIONAL_KEYS.includes(key) && isBlank(value))
|
|
4668
|
+
unset.push(key);
|
|
4669
|
+
else if (value !== void 0)
|
|
4670
|
+
set[key] = value;
|
|
4671
|
+
}
|
|
4672
|
+
if (set.repeat === "none") {
|
|
4673
|
+
for (const key of ["daysOfWeek", "dayOfMonth", "startDate"]) {
|
|
4674
|
+
if (!unset.includes(key))
|
|
4675
|
+
unset.push(key);
|
|
4676
|
+
delete set[key];
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
return { set, unset };
|
|
4680
|
+
}
|
|
4681
|
+
function regularSetsMissing(checklist, setCount) {
|
|
4682
|
+
const regular = (checklist ?? []).filter((c) => c.isScheduleTask !== true).length;
|
|
4683
|
+
return Math.max(0, setCount - regular);
|
|
4684
|
+
}
|
|
4685
|
+
var normTiming = (t) => ({
|
|
4686
|
+
time: t.time ?? null,
|
|
4687
|
+
dates: [...Array.isArray(t.dates) ? t.dates : []].sort(),
|
|
4688
|
+
repeat: !t.repeat || t.repeat === "none" ? "none" : t.repeat,
|
|
4689
|
+
daysOfWeek: [...Array.isArray(t.daysOfWeek) ? t.daysOfWeek : []].sort(),
|
|
4690
|
+
dayOfMonth: t.dayOfMonth ?? null,
|
|
4691
|
+
startDate: t.startDate || null,
|
|
4692
|
+
endDate: t.endDate || null
|
|
4693
|
+
});
|
|
4694
|
+
function timingChanged(stored, after) {
|
|
4695
|
+
return JSON.stringify(normTiming(stored)) !== JSON.stringify(normTiming(after));
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4439
4698
|
// src/services/hygiene-area-checklist.service.ts
|
|
4440
4699
|
function useAreaChecklistService() {
|
|
4441
4700
|
const { getAreasForChecklist } = useAreaRepo();
|
|
@@ -4503,10 +4762,10 @@ function useAreaChecklistService() {
|
|
|
4503
4762
|
`Area checklist for area ${area.name} exists but has no auto-gen sets. Inserting auto-gen sets.`
|
|
4504
4763
|
);
|
|
4505
4764
|
const setCount2 = Number(area.set) || 1;
|
|
4506
|
-
const
|
|
4507
|
-
if (
|
|
4765
|
+
const setsToAdd = regularSetsMissing(existing.checklist, setCount2);
|
|
4766
|
+
if (setsToAdd <= 0) {
|
|
4508
4767
|
logger18.info(
|
|
4509
|
-
`Area checklist for area ${area.name} already has ${
|
|
4768
|
+
`Area checklist for area ${area.name} already has its ${setCount2} regular set(s). Skipping auto-gen sets.`
|
|
4510
4769
|
);
|
|
4511
4770
|
return existing._id;
|
|
4512
4771
|
}
|
|
@@ -4514,7 +4773,6 @@ function useAreaChecklistService() {
|
|
|
4514
4773
|
(max, c) => Math.max(max, c.set ?? 0),
|
|
4515
4774
|
0
|
|
4516
4775
|
) ?? 0;
|
|
4517
|
-
const setsToAdd = setCount2 - totalExistingSets;
|
|
4518
4776
|
const autoGenSets = Array.from(
|
|
4519
4777
|
{ length: setsToAdd },
|
|
4520
4778
|
(_, index) => ({
|
|
@@ -4675,7 +4933,7 @@ function useAreaChecklistService() {
|
|
|
4675
4933
|
}
|
|
4676
4934
|
|
|
4677
4935
|
// src/controllers/hygiene-area-checklist.controller.ts
|
|
4678
|
-
import
|
|
4936
|
+
import Joi11 from "joi";
|
|
4679
4937
|
import {
|
|
4680
4938
|
BadRequestError as BadRequestError16,
|
|
4681
4939
|
InternalServerError as InternalServerError7,
|
|
@@ -4920,9 +5178,9 @@ function useAreaChecklistController() {
|
|
|
4920
5178
|
site: req.params.site,
|
|
4921
5179
|
schedule: req.params.schedule
|
|
4922
5180
|
};
|
|
4923
|
-
const validation =
|
|
4924
|
-
site:
|
|
4925
|
-
schedule:
|
|
5181
|
+
const validation = Joi11.object({
|
|
5182
|
+
site: Joi11.string().hex().required(),
|
|
5183
|
+
schedule: Joi11.string().hex().required()
|
|
4926
5184
|
});
|
|
4927
5185
|
const { error, value } = validation.validate(payload);
|
|
4928
5186
|
if (error) {
|
|
@@ -4942,14 +5200,14 @@ function useAreaChecklistController() {
|
|
|
4942
5200
|
}
|
|
4943
5201
|
async function getAllAreaChecklist(req, res, next) {
|
|
4944
5202
|
const query = { ...req.query, ...req.params };
|
|
4945
|
-
const validation =
|
|
4946
|
-
page:
|
|
4947
|
-
limit:
|
|
4948
|
-
search:
|
|
4949
|
-
type:
|
|
4950
|
-
status:
|
|
4951
|
-
schedule:
|
|
4952
|
-
serviceType:
|
|
5203
|
+
const validation = Joi11.object({
|
|
5204
|
+
page: Joi11.number().min(1).optional().allow("", null),
|
|
5205
|
+
limit: Joi11.number().min(1).optional().allow("", null),
|
|
5206
|
+
search: Joi11.string().optional().allow("", null),
|
|
5207
|
+
type: Joi11.string().valid(...allowedTypes, "all").optional().allow("", null),
|
|
5208
|
+
status: Joi11.string().valid(...allowedStatus, "all").optional().allow("", null),
|
|
5209
|
+
schedule: Joi11.string().hex().required(),
|
|
5210
|
+
serviceType: Joi11.string().valid(...Object.values(AppServiceType10)).required()
|
|
4953
5211
|
});
|
|
4954
5212
|
const { error } = validation.validate(query);
|
|
4955
5213
|
if (error) {
|
|
@@ -4984,14 +5242,14 @@ function useAreaChecklistController() {
|
|
|
4984
5242
|
}
|
|
4985
5243
|
async function getAreaChecklistHistory(req, res, next) {
|
|
4986
5244
|
const query = { ...req.query, ...req.params };
|
|
4987
|
-
const validation =
|
|
4988
|
-
page:
|
|
4989
|
-
limit:
|
|
4990
|
-
search:
|
|
4991
|
-
type:
|
|
4992
|
-
schedule:
|
|
4993
|
-
status:
|
|
4994
|
-
createdAt:
|
|
5245
|
+
const validation = Joi11.object({
|
|
5246
|
+
page: Joi11.number().min(1).optional().allow("", null),
|
|
5247
|
+
limit: Joi11.number().min(1).optional().allow("", null),
|
|
5248
|
+
search: Joi11.string().optional().allow("", null),
|
|
5249
|
+
type: Joi11.string().optional().allow("", ...allowedTypes),
|
|
5250
|
+
schedule: Joi11.string().hex().required(),
|
|
5251
|
+
status: Joi11.string().allow("", null, ...allowedStatus),
|
|
5252
|
+
createdAt: Joi11.alternatives().try(Joi11.date(), Joi11.string()).optional().allow("", null)
|
|
4995
5253
|
});
|
|
4996
5254
|
const { error } = validation.validate(query);
|
|
4997
5255
|
if (error) {
|
|
@@ -5025,7 +5283,7 @@ function useAreaChecklistController() {
|
|
|
5025
5283
|
}
|
|
5026
5284
|
}
|
|
5027
5285
|
async function getAreaChecklistHistoryDetails(req, res, next) {
|
|
5028
|
-
const validation =
|
|
5286
|
+
const validation = Joi11.string().hex().required();
|
|
5029
5287
|
const _id = req.params.id;
|
|
5030
5288
|
const { error, value } = validation.validate(_id);
|
|
5031
5289
|
if (error) {
|
|
@@ -5045,12 +5303,12 @@ function useAreaChecklistController() {
|
|
|
5045
5303
|
}
|
|
5046
5304
|
async function getAreaChecklistUnits(req, res, next) {
|
|
5047
5305
|
const query = { ...req.query, ...req.params };
|
|
5048
|
-
const validation =
|
|
5049
|
-
page:
|
|
5050
|
-
limit:
|
|
5051
|
-
search:
|
|
5052
|
-
serviceType:
|
|
5053
|
-
id:
|
|
5306
|
+
const validation = Joi11.object({
|
|
5307
|
+
page: Joi11.number().min(1).optional().allow("", null),
|
|
5308
|
+
limit: Joi11.number().min(1).optional().allow("", null),
|
|
5309
|
+
search: Joi11.string().optional().allow("", null),
|
|
5310
|
+
serviceType: Joi11.string().valid(...Object.values(AppServiceType10)).required(),
|
|
5311
|
+
id: Joi11.string().hex().required()
|
|
5054
5312
|
});
|
|
5055
5313
|
const { error } = validation.validate(query);
|
|
5056
5314
|
if (error) {
|
|
@@ -5100,21 +5358,21 @@ function useAreaChecklistController() {
|
|
|
5100
5358
|
...decisionValues,
|
|
5101
5359
|
completedBy
|
|
5102
5360
|
};
|
|
5103
|
-
const validation =
|
|
5104
|
-
id:
|
|
5105
|
-
set:
|
|
5106
|
-
unit:
|
|
5107
|
-
decision:
|
|
5108
|
-
approve:
|
|
5109
|
-
reject:
|
|
5110
|
-
remarks:
|
|
5361
|
+
const validation = Joi11.object({
|
|
5362
|
+
id: Joi11.string().hex().required(),
|
|
5363
|
+
set: Joi11.number().integer().min(1).required(),
|
|
5364
|
+
unit: Joi11.string().hex().required(),
|
|
5365
|
+
decision: Joi11.string().valid("approve", "reject").required(),
|
|
5366
|
+
approve: Joi11.boolean().optional(),
|
|
5367
|
+
reject: Joi11.boolean().optional(),
|
|
5368
|
+
remarks: Joi11.string().optional().allow("", null),
|
|
5111
5369
|
/*
|
|
5112
5370
|
* Shape only. WHETHER a photo is required depends on the set's current
|
|
5113
5371
|
* state in the database, which Joi cannot see — see the check below.
|
|
5114
5372
|
* Each entry must still be a real id: a blank string carries no evidence.
|
|
5115
5373
|
*/
|
|
5116
|
-
attachment:
|
|
5117
|
-
completedBy:
|
|
5374
|
+
attachment: Joi11.array().items(Joi11.string().trim().min(1)).optional(),
|
|
5375
|
+
completedBy: Joi11.string().hex().required()
|
|
5118
5376
|
});
|
|
5119
5377
|
const { error } = validation.validate(payload);
|
|
5120
5378
|
if (error) {
|
|
@@ -5151,7 +5409,7 @@ function useAreaChecklistController() {
|
|
|
5151
5409
|
}
|
|
5152
5410
|
}
|
|
5153
5411
|
async function downloadChecklistPdf(req, res, next) {
|
|
5154
|
-
const validation =
|
|
5412
|
+
const validation = Joi11.string().hex().required();
|
|
5155
5413
|
const schedule = req.params.schedule;
|
|
5156
5414
|
const { error, value } = validation.validate(schedule);
|
|
5157
5415
|
if (error) {
|
|
@@ -5197,16 +5455,16 @@ function useAreaChecklistController() {
|
|
|
5197
5455
|
}
|
|
5198
5456
|
|
|
5199
5457
|
// src/models/hygiene-supply.model.ts
|
|
5200
|
-
import
|
|
5458
|
+
import Joi12 from "joi";
|
|
5201
5459
|
import { ObjectId as ObjectId12 } from "mongodb";
|
|
5202
5460
|
import { BadRequestError as BadRequestError17, logger as logger21 } from "@7365admin1/node-server-utils";
|
|
5203
5461
|
import { AppServiceType as AppServiceType11 } from "@7365admin1/core";
|
|
5204
|
-
var supplySchema =
|
|
5205
|
-
site:
|
|
5206
|
-
serviceType:
|
|
5207
|
-
name:
|
|
5208
|
-
unitOfMeasurement:
|
|
5209
|
-
attachment:
|
|
5462
|
+
var supplySchema = Joi12.object({
|
|
5463
|
+
site: Joi12.string().hex().required(),
|
|
5464
|
+
serviceType: Joi12.string().valid(...Object.values(AppServiceType11)).required(),
|
|
5465
|
+
name: Joi12.string().required(),
|
|
5466
|
+
unitOfMeasurement: Joi12.string().required(),
|
|
5467
|
+
attachment: Joi12.string().allow(null, "").optional()
|
|
5210
5468
|
});
|
|
5211
5469
|
function MSupply(value) {
|
|
5212
5470
|
const { error } = supplySchema.validate(value);
|
|
@@ -5505,7 +5763,7 @@ function useSupplyRepository() {
|
|
|
5505
5763
|
}
|
|
5506
5764
|
|
|
5507
5765
|
// src/controllers/hygiene-supply.controller.ts
|
|
5508
|
-
import
|
|
5766
|
+
import Joi13 from "joi";
|
|
5509
5767
|
import { BadRequestError as BadRequestError19, logger as logger23 } from "@7365admin1/node-server-utils";
|
|
5510
5768
|
import { AppServiceType as AppServiceType12 } from "@7365admin1/core";
|
|
5511
5769
|
function useSupplyController() {
|
|
@@ -5536,12 +5794,12 @@ function useSupplyController() {
|
|
|
5536
5794
|
}
|
|
5537
5795
|
async function getSupplies(req, res, next) {
|
|
5538
5796
|
const query = { ...req.query, ...req.params };
|
|
5539
|
-
const validation =
|
|
5540
|
-
page:
|
|
5541
|
-
limit:
|
|
5542
|
-
search:
|
|
5543
|
-
site:
|
|
5544
|
-
serviceType:
|
|
5797
|
+
const validation = Joi13.object({
|
|
5798
|
+
page: Joi13.number().min(1).optional().allow("", null),
|
|
5799
|
+
limit: Joi13.number().min(1).optional().allow("", null),
|
|
5800
|
+
search: Joi13.string().optional().allow("", null),
|
|
5801
|
+
site: Joi13.string().hex().required(),
|
|
5802
|
+
serviceType: Joi13.string().valid(...Object.values(AppServiceType12)).required()
|
|
5545
5803
|
});
|
|
5546
5804
|
const { error } = validation.validate(query);
|
|
5547
5805
|
if (error) {
|
|
@@ -5571,7 +5829,7 @@ function useSupplyController() {
|
|
|
5571
5829
|
}
|
|
5572
5830
|
}
|
|
5573
5831
|
async function getSupplyById(req, res, next) {
|
|
5574
|
-
const validation =
|
|
5832
|
+
const validation = Joi13.string().hex().required();
|
|
5575
5833
|
const _id = req.params.id;
|
|
5576
5834
|
const { error, value } = validation.validate(_id);
|
|
5577
5835
|
if (error) {
|
|
@@ -5591,12 +5849,12 @@ function useSupplyController() {
|
|
|
5591
5849
|
}
|
|
5592
5850
|
async function updateSupply(req, res, next) {
|
|
5593
5851
|
const payload = { id: req.params.id, ...req.body };
|
|
5594
|
-
const validation =
|
|
5595
|
-
id:
|
|
5596
|
-
name:
|
|
5597
|
-
unitOfMeasurement:
|
|
5598
|
-
qty:
|
|
5599
|
-
attachment:
|
|
5852
|
+
const validation = Joi13.object({
|
|
5853
|
+
id: Joi13.string().hex().required(),
|
|
5854
|
+
name: Joi13.string().optional().allow("", null),
|
|
5855
|
+
unitOfMeasurement: Joi13.string().optional().allow("", null),
|
|
5856
|
+
qty: Joi13.number().min(0).optional().allow("", null),
|
|
5857
|
+
attachment: Joi13.string().optional().allow("", null)
|
|
5600
5858
|
});
|
|
5601
5859
|
const { error } = validation.validate(payload);
|
|
5602
5860
|
if (error) {
|
|
@@ -5617,7 +5875,7 @@ function useSupplyController() {
|
|
|
5617
5875
|
}
|
|
5618
5876
|
async function deleteSupply(req, res, next) {
|
|
5619
5877
|
const id = req.params.id;
|
|
5620
|
-
const validation =
|
|
5878
|
+
const validation = Joi13.string().hex().required();
|
|
5621
5879
|
const { error, value } = validation.validate(id);
|
|
5622
5880
|
if (error) {
|
|
5623
5881
|
logger23.log({ level: "error", message: error.message });
|
|
@@ -5644,18 +5902,18 @@ function useSupplyController() {
|
|
|
5644
5902
|
}
|
|
5645
5903
|
|
|
5646
5904
|
// src/models/hygiene-stock.model.ts
|
|
5647
|
-
import
|
|
5905
|
+
import Joi14 from "joi";
|
|
5648
5906
|
import { ObjectId as ObjectId14 } from "mongodb";
|
|
5649
5907
|
import { BadRequestError as BadRequestError20, logger as logger24 } from "@7365admin1/node-server-utils";
|
|
5650
5908
|
import { AppServiceType as AppServiceType13 } from "@7365admin1/core";
|
|
5651
|
-
var stockSchema =
|
|
5652
|
-
site:
|
|
5653
|
-
serviceType:
|
|
5654
|
-
supply:
|
|
5655
|
-
in:
|
|
5656
|
-
out:
|
|
5657
|
-
balance:
|
|
5658
|
-
remarks:
|
|
5909
|
+
var stockSchema = Joi14.object({
|
|
5910
|
+
site: Joi14.string().hex().required(),
|
|
5911
|
+
serviceType: Joi14.string().valid(...Object.values(AppServiceType13)).required(),
|
|
5912
|
+
supply: Joi14.string().hex().required(),
|
|
5913
|
+
in: Joi14.number().min(0).optional(),
|
|
5914
|
+
out: Joi14.number().min(0).optional(),
|
|
5915
|
+
balance: Joi14.number().min(0).required(),
|
|
5916
|
+
remarks: Joi14.string().optional().allow("", null)
|
|
5659
5917
|
});
|
|
5660
5918
|
function MStock(value) {
|
|
5661
5919
|
const { error } = stockSchema.validate(value);
|
|
@@ -5882,7 +6140,7 @@ function useStockService() {
|
|
|
5882
6140
|
}
|
|
5883
6141
|
|
|
5884
6142
|
// src/controllers/hygiene-stock.controller.ts
|
|
5885
|
-
import
|
|
6143
|
+
import Joi15 from "joi";
|
|
5886
6144
|
import { BadRequestError as BadRequestError23, logger as logger26 } from "@7365admin1/node-server-utils";
|
|
5887
6145
|
import { AppServiceType as AppServiceType14 } from "@7365admin1/core";
|
|
5888
6146
|
function useStockController() {
|
|
@@ -5890,12 +6148,12 @@ function useStockController() {
|
|
|
5890
6148
|
const { createStock: _createStock } = useStockService();
|
|
5891
6149
|
async function createStock(req, res, next) {
|
|
5892
6150
|
const payload = { ...req.body, ...req.params };
|
|
5893
|
-
const validation =
|
|
5894
|
-
site:
|
|
5895
|
-
serviceType:
|
|
5896
|
-
supply:
|
|
5897
|
-
qty:
|
|
5898
|
-
remarks:
|
|
6151
|
+
const validation = Joi15.object({
|
|
6152
|
+
site: Joi15.string().hex().required(),
|
|
6153
|
+
serviceType: Joi15.string().valid(...Object.values(AppServiceType14)).required(),
|
|
6154
|
+
supply: Joi15.string().hex().required(),
|
|
6155
|
+
qty: Joi15.number().min(0).required(),
|
|
6156
|
+
remarks: Joi15.string().optional().allow("", null)
|
|
5899
6157
|
});
|
|
5900
6158
|
const { error } = validation.validate(payload);
|
|
5901
6159
|
if (error) {
|
|
@@ -5915,13 +6173,13 @@ function useStockController() {
|
|
|
5915
6173
|
}
|
|
5916
6174
|
async function getStocksBySupplyId(req, res, next) {
|
|
5917
6175
|
const query = { ...req.query, ...req.params };
|
|
5918
|
-
const validation =
|
|
5919
|
-
page:
|
|
5920
|
-
limit:
|
|
5921
|
-
search:
|
|
5922
|
-
site:
|
|
5923
|
-
serviceType:
|
|
5924
|
-
supply:
|
|
6176
|
+
const validation = Joi15.object({
|
|
6177
|
+
page: Joi15.number().min(1).optional().allow("", null),
|
|
6178
|
+
limit: Joi15.number().min(1).optional().allow("", null),
|
|
6179
|
+
search: Joi15.string().optional().allow("", null),
|
|
6180
|
+
site: Joi15.string().hex().required(),
|
|
6181
|
+
serviceType: Joi15.string().valid(...Object.values(AppServiceType14)).required(),
|
|
6182
|
+
supply: Joi15.string().hex().required()
|
|
5925
6183
|
});
|
|
5926
6184
|
const { error } = validation.validate(query);
|
|
5927
6185
|
if (error) {
|
|
@@ -5983,21 +6241,21 @@ async function applyCheckOutDecision(decision, decide, returnStock) {
|
|
|
5983
6241
|
}
|
|
5984
6242
|
|
|
5985
6243
|
// src/models/hygiene-checkout-item.model.ts
|
|
5986
|
-
import
|
|
6244
|
+
import Joi16 from "joi";
|
|
5987
6245
|
import { ObjectId as ObjectId16 } from "mongodb";
|
|
5988
6246
|
import { BadRequestError as BadRequestError24, logger as logger27 } from "@7365admin1/node-server-utils";
|
|
5989
6247
|
import { AppServiceType as AppServiceType15 } from "@7365admin1/core";
|
|
5990
6248
|
var allowedCheckOutItemStatus = ["pending", "completed"];
|
|
5991
|
-
var checkOutItemSchema =
|
|
5992
|
-
site:
|
|
5993
|
-
serviceType:
|
|
5994
|
-
supply:
|
|
5995
|
-
supplyName:
|
|
5996
|
-
supplyQty:
|
|
5997
|
-
qty:
|
|
5998
|
-
attachment:
|
|
5999
|
-
createdBy:
|
|
6000
|
-
createdByName:
|
|
6249
|
+
var checkOutItemSchema = Joi16.object({
|
|
6250
|
+
site: Joi16.string().hex().required(),
|
|
6251
|
+
serviceType: Joi16.string().valid(...Object.values(AppServiceType15)).required(),
|
|
6252
|
+
supply: Joi16.string().hex().required(),
|
|
6253
|
+
supplyName: Joi16.string().required(),
|
|
6254
|
+
supplyQty: Joi16.number().min(0).required(),
|
|
6255
|
+
qty: Joi16.number().min(0).required(),
|
|
6256
|
+
attachment: Joi16.array().items(Joi16.string()).optional().allow(null),
|
|
6257
|
+
createdBy: Joi16.string().hex().required(),
|
|
6258
|
+
createdByName: Joi16.string().required()
|
|
6001
6259
|
});
|
|
6002
6260
|
function MCheckOutItem(value) {
|
|
6003
6261
|
const { error } = checkOutItemSchema.validate(value);
|
|
@@ -6486,7 +6744,7 @@ function useCheckOutItemService() {
|
|
|
6486
6744
|
}
|
|
6487
6745
|
|
|
6488
6746
|
// src/controllers/hygiene-checkout-item.controller.ts
|
|
6489
|
-
import
|
|
6747
|
+
import Joi17 from "joi";
|
|
6490
6748
|
import { BadRequestError as BadRequestError27, logger as logger29 } from "@7365admin1/node-server-utils";
|
|
6491
6749
|
import { AppServiceType as AppServiceType16 } from "@7365admin1/core";
|
|
6492
6750
|
function useCheckOutItemController() {
|
|
@@ -6510,13 +6768,13 @@ function useCheckOutItemController() {
|
|
|
6510
6768
|
...req.params,
|
|
6511
6769
|
createdBy
|
|
6512
6770
|
};
|
|
6513
|
-
const validation =
|
|
6514
|
-
site:
|
|
6515
|
-
serviceType:
|
|
6516
|
-
supply:
|
|
6517
|
-
qty:
|
|
6518
|
-
attachment:
|
|
6519
|
-
createdBy:
|
|
6771
|
+
const validation = Joi17.object({
|
|
6772
|
+
site: Joi17.string().hex().required(),
|
|
6773
|
+
serviceType: Joi17.string().valid(...Object.values(AppServiceType16)).required(),
|
|
6774
|
+
supply: Joi17.string().hex().required(),
|
|
6775
|
+
qty: Joi17.number().min(0).required(),
|
|
6776
|
+
attachment: Joi17.array().items(Joi17.string()).optional().allow(null),
|
|
6777
|
+
createdBy: Joi17.string().hex().required()
|
|
6520
6778
|
});
|
|
6521
6779
|
const { error } = validation.validate(payload);
|
|
6522
6780
|
if (error) {
|
|
@@ -6545,15 +6803,15 @@ function useCheckOutItemController() {
|
|
|
6545
6803
|
...req.params,
|
|
6546
6804
|
createdBy
|
|
6547
6805
|
};
|
|
6548
|
-
const validation =
|
|
6549
|
-
site:
|
|
6550
|
-
serviceType:
|
|
6551
|
-
createdBy:
|
|
6552
|
-
items:
|
|
6553
|
-
|
|
6554
|
-
supply:
|
|
6555
|
-
qty:
|
|
6556
|
-
attachment:
|
|
6806
|
+
const validation = Joi17.object({
|
|
6807
|
+
site: Joi17.string().hex().required(),
|
|
6808
|
+
serviceType: Joi17.string().valid(...Object.values(AppServiceType16)).required(),
|
|
6809
|
+
createdBy: Joi17.string().hex().required(),
|
|
6810
|
+
items: Joi17.array().items(
|
|
6811
|
+
Joi17.object({
|
|
6812
|
+
supply: Joi17.string().hex().required(),
|
|
6813
|
+
qty: Joi17.number().min(0).required(),
|
|
6814
|
+
attachment: Joi17.array().items(Joi17.string()).optional().allow(null)
|
|
6557
6815
|
})
|
|
6558
6816
|
).min(1).required()
|
|
6559
6817
|
});
|
|
@@ -6575,12 +6833,12 @@ function useCheckOutItemController() {
|
|
|
6575
6833
|
}
|
|
6576
6834
|
async function getCheckOutItems(req, res, next) {
|
|
6577
6835
|
const query = { ...req.query, ...req.params };
|
|
6578
|
-
const validation =
|
|
6579
|
-
page:
|
|
6580
|
-
limit:
|
|
6581
|
-
search:
|
|
6582
|
-
site:
|
|
6583
|
-
serviceType:
|
|
6836
|
+
const validation = Joi17.object({
|
|
6837
|
+
page: Joi17.number().min(1).optional().allow("", null),
|
|
6838
|
+
limit: Joi17.number().min(1).optional().allow("", null),
|
|
6839
|
+
search: Joi17.string().optional().allow("", null),
|
|
6840
|
+
site: Joi17.string().hex().required(),
|
|
6841
|
+
serviceType: Joi17.string().valid(...Object.values(AppServiceType16)).required()
|
|
6584
6842
|
});
|
|
6585
6843
|
const { error } = validation.validate(query);
|
|
6586
6844
|
if (error) {
|
|
@@ -6610,7 +6868,7 @@ function useCheckOutItemController() {
|
|
|
6610
6868
|
}
|
|
6611
6869
|
}
|
|
6612
6870
|
async function getCheckOutItemById(req, res, next) {
|
|
6613
|
-
const validation =
|
|
6871
|
+
const validation = Joi17.string().hex().required();
|
|
6614
6872
|
const _id = req.params.id;
|
|
6615
6873
|
const { error, value } = validation.validate(_id);
|
|
6616
6874
|
if (error) {
|
|
@@ -6649,13 +6907,13 @@ function useCheckOutItemController() {
|
|
|
6649
6907
|
...decisionValues,
|
|
6650
6908
|
completedBy
|
|
6651
6909
|
};
|
|
6652
|
-
const validation =
|
|
6653
|
-
id:
|
|
6654
|
-
decision:
|
|
6655
|
-
approve:
|
|
6656
|
-
reject:
|
|
6657
|
-
remarks:
|
|
6658
|
-
completedBy:
|
|
6910
|
+
const validation = Joi17.object({
|
|
6911
|
+
id: Joi17.string().hex().required(),
|
|
6912
|
+
decision: Joi17.string().valid("approve", "disapprove").required(),
|
|
6913
|
+
approve: Joi17.boolean().required(),
|
|
6914
|
+
reject: Joi17.boolean().required(),
|
|
6915
|
+
remarks: Joi17.string().optional().allow("", null),
|
|
6916
|
+
completedBy: Joi17.string().hex().required()
|
|
6659
6917
|
});
|
|
6660
6918
|
const { error } = validation.validate(payload);
|
|
6661
6919
|
if (error) {
|
|
@@ -6688,23 +6946,15 @@ function useCheckOutItemController() {
|
|
|
6688
6946
|
// src/models/hygiene-schedule-task.model.ts
|
|
6689
6947
|
import { BadRequestError as BadRequestError28, logger as logger30 } from "@7365admin1/node-server-utils";
|
|
6690
6948
|
import { AppServiceType as AppServiceType17 } from "@7365admin1/core";
|
|
6691
|
-
import
|
|
6949
|
+
import Joi18 from "joi";
|
|
6692
6950
|
import { ObjectId as ObjectId18 } from "mongodb";
|
|
6693
|
-
var scheduleTaskSchema =
|
|
6694
|
-
site:
|
|
6695
|
-
serviceType:
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
).required(),
|
|
6701
|
-
description: Joi17.string().optional().allow("", null),
|
|
6702
|
-
areas: Joi17.array().min(1).items(
|
|
6703
|
-
Joi17.object({
|
|
6704
|
-
name: Joi17.string().required(),
|
|
6705
|
-
value: Joi17.any().required()
|
|
6706
|
-
})
|
|
6707
|
-
).required()
|
|
6951
|
+
var scheduleTaskSchema = Joi18.object({
|
|
6952
|
+
site: Joi18.string().hex().required(),
|
|
6953
|
+
serviceType: Joi18.string().valid(...Object.values(AppServiceType17)).required(),
|
|
6954
|
+
...scheduleTaskFields
|
|
6955
|
+
}).custom((value, helpers) => {
|
|
6956
|
+
const message = validateScheduleRules(cleanScheduleInput(value));
|
|
6957
|
+
return message ? helpers.message({ custom: message }) : value;
|
|
6708
6958
|
});
|
|
6709
6959
|
function MScheduleTask(value) {
|
|
6710
6960
|
const { error } = scheduleTaskSchema.validate(value);
|
|
@@ -6731,14 +6981,26 @@ function MScheduleTask(value) {
|
|
|
6731
6981
|
}
|
|
6732
6982
|
});
|
|
6733
6983
|
}
|
|
6984
|
+
const extra = cleanScheduleInput({
|
|
6985
|
+
repeat: value.repeat,
|
|
6986
|
+
daysOfWeek: value.daysOfWeek,
|
|
6987
|
+
dayOfMonth: value.dayOfMonth,
|
|
6988
|
+
startDate: value.startDate,
|
|
6989
|
+
endDate: value.endDate,
|
|
6990
|
+
paused: value.paused
|
|
6991
|
+
});
|
|
6992
|
+
for (const key of Object.keys(extra))
|
|
6993
|
+
if (extra[key] === void 0)
|
|
6994
|
+
delete extra[key];
|
|
6734
6995
|
return {
|
|
6735
6996
|
site: value.site,
|
|
6736
6997
|
serviceType: value.serviceType,
|
|
6737
6998
|
title: value.title,
|
|
6738
6999
|
time: value.time,
|
|
6739
|
-
dates: value.dates,
|
|
7000
|
+
dates: value.dates ?? [],
|
|
6740
7001
|
description: value.description,
|
|
6741
7002
|
areas: value.areas,
|
|
7003
|
+
...extra,
|
|
6742
7004
|
status: "active",
|
|
6743
7005
|
createdAt: /* @__PURE__ */ new Date(),
|
|
6744
7006
|
updatedAt: "",
|
|
@@ -6758,11 +7020,23 @@ import {
|
|
|
6758
7020
|
makeCacheKey as makeCacheKey9,
|
|
6759
7021
|
NotFoundError as NotFoundError7
|
|
6760
7022
|
} from "@7365admin1/node-server-utils";
|
|
7023
|
+
var SCHEDULE_FIELDS = {
|
|
7024
|
+
time: 1,
|
|
7025
|
+
dates: 1,
|
|
7026
|
+
repeat: 1,
|
|
7027
|
+
daysOfWeek: 1,
|
|
7028
|
+
dayOfMonth: 1,
|
|
7029
|
+
startDate: 1,
|
|
7030
|
+
endDate: 1,
|
|
7031
|
+
paused: 1,
|
|
7032
|
+
lastRunDate: 1
|
|
7033
|
+
};
|
|
6761
7034
|
function useScheduleTaskRepository() {
|
|
6762
7035
|
const db = useAtlas14.getDb();
|
|
6763
7036
|
if (!db) {
|
|
6764
7037
|
throw new InternalServerError11("Unable to connect to server.");
|
|
6765
7038
|
}
|
|
7039
|
+
const database = db;
|
|
6766
7040
|
const namespace_collection = "site.schedule-tasks";
|
|
6767
7041
|
const collection = db.collection(namespace_collection);
|
|
6768
7042
|
const { delNamespace, setCache, getCache } = useCache9(namespace_collection);
|
|
@@ -6837,7 +7111,7 @@ function useScheduleTaskRepository() {
|
|
|
6837
7111
|
const cachedData = await getCache(cacheKey);
|
|
6838
7112
|
if (cachedData) {
|
|
6839
7113
|
logger31.info(`Cache hit for key: ${cacheKey}`);
|
|
6840
|
-
return cachedData;
|
|
7114
|
+
return enrichList(cachedData, site, serviceType);
|
|
6841
7115
|
}
|
|
6842
7116
|
try {
|
|
6843
7117
|
const items = await collection.aggregate([
|
|
@@ -6846,7 +7120,8 @@ function useScheduleTaskRepository() {
|
|
|
6846
7120
|
$project: {
|
|
6847
7121
|
title: 1,
|
|
6848
7122
|
areas: 1,
|
|
6849
|
-
status: 1
|
|
7123
|
+
status: 1,
|
|
7124
|
+
...SCHEDULE_FIELDS
|
|
6850
7125
|
}
|
|
6851
7126
|
},
|
|
6852
7127
|
{ $sort: { _id: -1 } },
|
|
@@ -6860,15 +7135,104 @@ function useScheduleTaskRepository() {
|
|
|
6860
7135
|
}).catch((err) => {
|
|
6861
7136
|
logger31.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
6862
7137
|
});
|
|
6863
|
-
return data;
|
|
7138
|
+
return enrichList(data, site, serviceType);
|
|
6864
7139
|
} catch (error) {
|
|
6865
7140
|
throw error;
|
|
6866
7141
|
}
|
|
6867
7142
|
}
|
|
6868
|
-
async function
|
|
7143
|
+
async function enrichList(data, site, serviceType) {
|
|
7144
|
+
const items = data?.items ?? [];
|
|
7145
|
+
const now = /* @__PURE__ */ new Date();
|
|
7146
|
+
let statuses = /* @__PURE__ */ new Map();
|
|
7147
|
+
try {
|
|
7148
|
+
statuses = await getRunStatuses(items, site, serviceType);
|
|
7149
|
+
} catch (error) {
|
|
7150
|
+
logger31.error("Could not read schedule task run status", error);
|
|
7151
|
+
}
|
|
7152
|
+
return {
|
|
7153
|
+
...data,
|
|
7154
|
+
items: items.map((item) => {
|
|
7155
|
+
try {
|
|
7156
|
+
return {
|
|
7157
|
+
...item,
|
|
7158
|
+
...scheduleDisplay(item, now),
|
|
7159
|
+
lastRunStatus: statuses.get(String(item._id)) ?? null
|
|
7160
|
+
};
|
|
7161
|
+
} catch (error) {
|
|
7162
|
+
logger31.error(`Could not enrich schedule task ${item?._id}`, error);
|
|
7163
|
+
return { ...item, nextRun: null, lastRun: null, scheduleStatus: null, lastRunStatus: null };
|
|
7164
|
+
}
|
|
7165
|
+
})
|
|
7166
|
+
};
|
|
7167
|
+
}
|
|
7168
|
+
async function getRunStatuses(items, site, serviceType) {
|
|
7169
|
+
const result = /* @__PURE__ */ new Map();
|
|
7170
|
+
const ran = items.filter((i) => i.lastRunDate);
|
|
7171
|
+
if (!ran.length)
|
|
7172
|
+
return result;
|
|
7173
|
+
const parents = await database.collection("site.cleaning.schedules").find(
|
|
7174
|
+
{
|
|
7175
|
+
site,
|
|
7176
|
+
serviceType,
|
|
7177
|
+
dateStr: { $in: [...new Set(ran.map((i) => i.lastRunDate))] }
|
|
7178
|
+
},
|
|
7179
|
+
{ projection: { dateStr: 1 } }
|
|
7180
|
+
).toArray();
|
|
7181
|
+
if (!parents.length)
|
|
7182
|
+
return result;
|
|
7183
|
+
const dateOf = new Map(parents.map((p) => [String(p._id), p.dateStr]));
|
|
7184
|
+
const taskIds = ran.map((i) => new ObjectId19(i._id));
|
|
7185
|
+
const rows = await database.collection("site.cleaning.schedule.areas").aggregate([
|
|
7186
|
+
{
|
|
7187
|
+
$match: {
|
|
7188
|
+
schedule: { $in: parents.map((p) => p._id) },
|
|
7189
|
+
"checklist.scheduleTaskId": { $in: taskIds }
|
|
7190
|
+
}
|
|
7191
|
+
},
|
|
7192
|
+
{ $unwind: "$checklist" },
|
|
7193
|
+
{ $match: { "checklist.scheduleTaskId": { $in: taskIds } } },
|
|
7194
|
+
{ $unwind: "$checklist.units" },
|
|
7195
|
+
{
|
|
7196
|
+
$group: {
|
|
7197
|
+
_id: { task: "$checklist.scheduleTaskId", schedule: "$schedule" },
|
|
7198
|
+
total: { $sum: 1 },
|
|
7199
|
+
completed: {
|
|
7200
|
+
$sum: { $cond: [{ $eq: ["$checklist.units.status", "completed"] }, 1, 0] }
|
|
7201
|
+
},
|
|
7202
|
+
closed: {
|
|
7203
|
+
$sum: { $cond: [{ $eq: ["$checklist.units.status", "closed"] }, 1, 0] }
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
]).toArray();
|
|
7208
|
+
const sums = /* @__PURE__ */ new Map();
|
|
7209
|
+
for (const row of rows) {
|
|
7210
|
+
const task = ran.find((i) => String(i._id) === String(row._id.task));
|
|
7211
|
+
if (!task || dateOf.get(String(row._id.schedule)) !== task.lastRunDate)
|
|
7212
|
+
continue;
|
|
7213
|
+
const sum = sums.get(String(task._id)) ?? { total: 0, completed: 0, closed: 0 };
|
|
7214
|
+
sum.total += row.total;
|
|
7215
|
+
sum.completed += row.completed;
|
|
7216
|
+
sum.closed += row.closed;
|
|
7217
|
+
sums.set(String(task._id), sum);
|
|
7218
|
+
}
|
|
7219
|
+
for (const [id, sum] of sums) {
|
|
7220
|
+
result.set(id, sum.completed === sum.total ? "done" : sum.closed > 0 ? "missed" : "pending");
|
|
7221
|
+
}
|
|
7222
|
+
return result;
|
|
7223
|
+
}
|
|
7224
|
+
async function getAllScheduleTask(today) {
|
|
6869
7225
|
const query = {
|
|
6870
7226
|
status: { $ne: "deleted" }
|
|
6871
7227
|
};
|
|
7228
|
+
if (today) {
|
|
7229
|
+
query.paused = { $ne: true };
|
|
7230
|
+
query.lastRunDate = { $ne: today };
|
|
7231
|
+
query.$or = [
|
|
7232
|
+
{ dates: today },
|
|
7233
|
+
{ repeat: { $in: ["daily", "weekdays", "weekly", "monthly"] } }
|
|
7234
|
+
];
|
|
7235
|
+
}
|
|
6872
7236
|
try {
|
|
6873
7237
|
const items = await collection.aggregate([{ $match: query }, { $sort: { _id: -1 } }]).toArray();
|
|
6874
7238
|
return items;
|
|
@@ -6876,6 +7240,9 @@ function useScheduleTaskRepository() {
|
|
|
6876
7240
|
throw error;
|
|
6877
7241
|
}
|
|
6878
7242
|
}
|
|
7243
|
+
function withScheduleDisplay(row) {
|
|
7244
|
+
return { ...row, ...scheduleDisplay(row, /* @__PURE__ */ new Date()) };
|
|
7245
|
+
}
|
|
6879
7246
|
async function getScheduleTaskById(_id, session) {
|
|
6880
7247
|
try {
|
|
6881
7248
|
_id = new ObjectId19(_id);
|
|
@@ -6893,7 +7260,7 @@ function useScheduleTaskRepository() {
|
|
|
6893
7260
|
const cachedData = await getCache(cacheKey);
|
|
6894
7261
|
if (cachedData) {
|
|
6895
7262
|
logger31.info(`Cache hit for key: ${cacheKey}`);
|
|
6896
|
-
return cachedData;
|
|
7263
|
+
return withScheduleDisplay(cachedData);
|
|
6897
7264
|
}
|
|
6898
7265
|
} else {
|
|
6899
7266
|
logger31.info(`Skipping cache during transaction for key: ${cacheKey}`);
|
|
@@ -6904,12 +7271,13 @@ function useScheduleTaskRepository() {
|
|
|
6904
7271
|
{
|
|
6905
7272
|
$project: {
|
|
6906
7273
|
title: 1,
|
|
6907
|
-
time: 1,
|
|
6908
|
-
dates: 1,
|
|
6909
7274
|
description: 1,
|
|
6910
7275
|
areas: 1,
|
|
6911
7276
|
status: 1,
|
|
6912
|
-
createdAt: 1
|
|
7277
|
+
createdAt: 1,
|
|
7278
|
+
site: 1,
|
|
7279
|
+
serviceType: 1,
|
|
7280
|
+
...SCHEDULE_FIELDS
|
|
6913
7281
|
}
|
|
6914
7282
|
}
|
|
6915
7283
|
]).toArray();
|
|
@@ -6921,7 +7289,7 @@ function useScheduleTaskRepository() {
|
|
|
6921
7289
|
}).catch((err) => {
|
|
6922
7290
|
logger31.error(`Failed to set cache for key: ${cacheKey}`, err);
|
|
6923
7291
|
});
|
|
6924
|
-
return data[0];
|
|
7292
|
+
return withScheduleDisplay(data[0]);
|
|
6925
7293
|
} catch (error) {
|
|
6926
7294
|
throw error;
|
|
6927
7295
|
}
|
|
@@ -6945,16 +7313,20 @@ function useScheduleTaskRepository() {
|
|
|
6945
7313
|
});
|
|
6946
7314
|
}
|
|
6947
7315
|
try {
|
|
6948
|
-
const
|
|
7316
|
+
const { set, unset } = splitScheduleUpdate(value);
|
|
7317
|
+
const update = {
|
|
7318
|
+
$set: { ...set, updatedAt: /* @__PURE__ */ new Date() }
|
|
7319
|
+
};
|
|
7320
|
+
if (unset.length) {
|
|
7321
|
+
update.$unset = Object.fromEntries(unset.map((key) => [key, ""]));
|
|
7322
|
+
}
|
|
6949
7323
|
const res = await collection.updateOne(
|
|
6950
|
-
{ _id },
|
|
6951
|
-
|
|
7324
|
+
{ _id, status: { $ne: "deleted" } },
|
|
7325
|
+
update,
|
|
6952
7326
|
{ session }
|
|
6953
7327
|
);
|
|
6954
|
-
if (res.
|
|
6955
|
-
throw new
|
|
6956
|
-
"Unable to update hygiene schedule task."
|
|
6957
|
-
);
|
|
7328
|
+
if (res.matchedCount === 0) {
|
|
7329
|
+
throw new NotFoundError7("Schedule task not found.");
|
|
6958
7330
|
}
|
|
6959
7331
|
delNamespace().then(() => {
|
|
6960
7332
|
logger31.info(`Cache cleared for namespace: ${namespace_collection}`);
|
|
@@ -6969,6 +7341,18 @@ function useScheduleTaskRepository() {
|
|
|
6969
7341
|
throw error;
|
|
6970
7342
|
}
|
|
6971
7343
|
}
|
|
7344
|
+
async function markScheduleTaskRan(_id, date) {
|
|
7345
|
+
await collection.updateOne(
|
|
7346
|
+
{ _id: new ObjectId19(_id) },
|
|
7347
|
+
{ $set: { lastRunDate: date, lastRunAt: /* @__PURE__ */ new Date() } }
|
|
7348
|
+
);
|
|
7349
|
+
delNamespace().catch((err) => {
|
|
7350
|
+
logger31.error(
|
|
7351
|
+
`Failed to clear cache for namespace: ${namespace_collection}`,
|
|
7352
|
+
err
|
|
7353
|
+
);
|
|
7354
|
+
});
|
|
7355
|
+
}
|
|
6972
7356
|
async function deleteScheduleTask(_id, session) {
|
|
6973
7357
|
try {
|
|
6974
7358
|
_id = new ObjectId19(_id);
|
|
@@ -7008,6 +7392,7 @@ function useScheduleTaskRepository() {
|
|
|
7008
7392
|
createScheduleTask,
|
|
7009
7393
|
getScheduleTasks,
|
|
7010
7394
|
getAllScheduleTask,
|
|
7395
|
+
markScheduleTaskRan,
|
|
7011
7396
|
getScheduleTaskById,
|
|
7012
7397
|
updateScheduleTask,
|
|
7013
7398
|
deleteScheduleTask
|
|
@@ -7019,7 +7404,7 @@ import { ObjectId as ObjectId20 } from "mongodb";
|
|
|
7019
7404
|
import { logger as logger32 } from "@7365admin1/node-server-utils";
|
|
7020
7405
|
function useScheduleTaskService() {
|
|
7021
7406
|
const { createParentChecklist } = useParentChecklistRepo();
|
|
7022
|
-
const { getAllScheduleTask } = useScheduleTaskRepository();
|
|
7407
|
+
const { getAllScheduleTask, markScheduleTaskRan } = useScheduleTaskRepository();
|
|
7023
7408
|
const {
|
|
7024
7409
|
createAreaChecklist,
|
|
7025
7410
|
getAreaChecklistByAreaAndSchedule,
|
|
@@ -7028,73 +7413,7 @@ function useScheduleTaskService() {
|
|
|
7028
7413
|
const { getAreaById } = useAreaRepo();
|
|
7029
7414
|
function checkScheduleConditions(schedule, serviceType, currentDate = /* @__PURE__ */ new Date()) {
|
|
7030
7415
|
try {
|
|
7031
|
-
|
|
7032
|
-
const timeString = now.toLocaleTimeString("en-US", {
|
|
7033
|
-
hour: "2-digit",
|
|
7034
|
-
minute: "2-digit",
|
|
7035
|
-
hour12: false,
|
|
7036
|
-
timeZone: "Asia/Singapore"
|
|
7037
|
-
});
|
|
7038
|
-
const [currentHour, currentMinute] = timeString.split(":").map(Number);
|
|
7039
|
-
logger32.info(
|
|
7040
|
-
`Checking schedule ${schedule._id}: Current time ${currentHour}:${currentMinute}, Schedule time ${schedule.time}, Dates ${JSON.stringify(schedule.dates)}`
|
|
7041
|
-
);
|
|
7042
|
-
const currentDateFormatted = now.toLocaleDateString("en-CA", {
|
|
7043
|
-
timeZone: "Asia/Singapore"
|
|
7044
|
-
});
|
|
7045
|
-
if (!schedule.dates || !Array.isArray(schedule.dates) || schedule.dates.length === 0) {
|
|
7046
|
-
logger32.info(`Schedule ${schedule._id}: No dates configured, skipping`);
|
|
7047
|
-
return false;
|
|
7048
|
-
}
|
|
7049
|
-
if (!schedule.dates.includes(currentDateFormatted)) {
|
|
7050
|
-
logger32.info(
|
|
7051
|
-
`Schedule ${schedule._id}: Current date ${currentDateFormatted} is not in scheduled dates [${schedule.dates.join(
|
|
7052
|
-
", "
|
|
7053
|
-
)}]`
|
|
7054
|
-
);
|
|
7055
|
-
return false;
|
|
7056
|
-
}
|
|
7057
|
-
const [scheduleHour, scheduleMinute] = schedule.time.split(":").map(Number);
|
|
7058
|
-
const timeMatches = currentHour === scheduleHour && currentMinute === scheduleMinute;
|
|
7059
|
-
if (!timeMatches) {
|
|
7060
|
-
logger32.info(
|
|
7061
|
-
`Schedule ${schedule._id}: Time does not match. Current: ${currentHour}:${currentMinute}, Expected: ${scheduleHour}:${scheduleMinute}`
|
|
7062
|
-
);
|
|
7063
|
-
return false;
|
|
7064
|
-
}
|
|
7065
|
-
const relevantTimestamp = schedule.updatedAt || schedule.createdAt;
|
|
7066
|
-
if (relevantTimestamp) {
|
|
7067
|
-
const savedAt = new Date(relevantTimestamp);
|
|
7068
|
-
const savedHour = parseInt(
|
|
7069
|
-
savedAt.toLocaleTimeString("en-US", {
|
|
7070
|
-
hour: "2-digit",
|
|
7071
|
-
hour12: false,
|
|
7072
|
-
timeZone: "Asia/Singapore"
|
|
7073
|
-
}),
|
|
7074
|
-
10
|
|
7075
|
-
);
|
|
7076
|
-
const savedMinute = savedAt.toLocaleString("en-US", {
|
|
7077
|
-
minute: "2-digit",
|
|
7078
|
-
timeZone: "Asia/Singapore"
|
|
7079
|
-
});
|
|
7080
|
-
const savedDateFormatted = savedAt.toLocaleDateString("en-CA", {
|
|
7081
|
-
timeZone: "Asia/Singapore"
|
|
7082
|
-
});
|
|
7083
|
-
const savedMinNum = parseInt(savedMinute, 10);
|
|
7084
|
-
const wasSavedThisMinute = savedDateFormatted === currentDateFormatted && savedHour === currentHour && savedMinNum === currentMinute;
|
|
7085
|
-
if (wasSavedThisMinute) {
|
|
7086
|
-
logger32.info(
|
|
7087
|
-
`Schedule ${schedule._id}: Skipping \u2014 task was saved this minute (${savedHour}:${String(
|
|
7088
|
-
savedMinNum
|
|
7089
|
-
).padStart(2, "0")}). Will fire on next occurrence.`
|
|
7090
|
-
);
|
|
7091
|
-
return false;
|
|
7092
|
-
}
|
|
7093
|
-
}
|
|
7094
|
-
logger32.info(
|
|
7095
|
-
`Schedule ${schedule._id}: All conditions matched - Date is within range and time matches`
|
|
7096
|
-
);
|
|
7097
|
-
return true;
|
|
7416
|
+
return shouldRunNow(schedule, currentDate);
|
|
7098
7417
|
} catch (error) {
|
|
7099
7418
|
logger32.error(
|
|
7100
7419
|
`Error checking schedule conditions for ${schedule._id}:`,
|
|
@@ -7103,18 +7422,22 @@ function useScheduleTaskService() {
|
|
|
7103
7422
|
return false;
|
|
7104
7423
|
}
|
|
7105
7424
|
}
|
|
7425
|
+
const MAX_ATTEMPTS = 5;
|
|
7426
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
7106
7427
|
async function processScheduledTasks(currentDate) {
|
|
7107
7428
|
try {
|
|
7108
7429
|
logger32.info("Starting scheduled task processing...");
|
|
7109
|
-
const
|
|
7430
|
+
const today = sgtParts(currentDate ?? /* @__PURE__ */ new Date()).date;
|
|
7431
|
+
const scheduleTasks = await getAllScheduleTask(today);
|
|
7110
7432
|
if (!scheduleTasks || scheduleTasks.length === 0) {
|
|
7111
7433
|
logger32.info("No schedule tasks found to process");
|
|
7112
|
-
return { processed: 0, validated: 0 };
|
|
7434
|
+
return { processed: 0, validated: 0, fired: [] };
|
|
7113
7435
|
}
|
|
7114
7436
|
logger32.info(`Found ${scheduleTasks.length} schedule tasks to check`);
|
|
7115
7437
|
let processedCount = 0;
|
|
7116
7438
|
let validatedCount = 0;
|
|
7117
7439
|
const validatedTasks = [];
|
|
7440
|
+
const fired = [];
|
|
7118
7441
|
for (const scheduleTask of scheduleTasks) {
|
|
7119
7442
|
try {
|
|
7120
7443
|
const serviceType = scheduleTask.serviceType;
|
|
@@ -7157,6 +7480,8 @@ function useScheduleTaskService() {
|
|
|
7157
7480
|
logger32.info(
|
|
7158
7481
|
`Using parent checklist ${parentChecklistId}, now creating/updating area checklists`
|
|
7159
7482
|
);
|
|
7483
|
+
let failedAreas = 0;
|
|
7484
|
+
const addedAreas = [];
|
|
7160
7485
|
for (const area of scheduleTask.areas) {
|
|
7161
7486
|
try {
|
|
7162
7487
|
const areaId = area.value.toString();
|
|
@@ -7236,6 +7561,7 @@ function useScheduleTaskService() {
|
|
|
7236
7561
|
newSets
|
|
7237
7562
|
);
|
|
7238
7563
|
if (modified > 0) {
|
|
7564
|
+
addedAreas.push(area.name);
|
|
7239
7565
|
logger32.info(
|
|
7240
7566
|
`Appended ${areaSetConfig} set(s) starting from ${startSetNumber} to area checklist for area ${area.name}`
|
|
7241
7567
|
);
|
|
@@ -7268,13 +7594,32 @@ function useScheduleTaskService() {
|
|
|
7268
7594
|
`Area ${area.name} (${areaId}): Creating new area checklist with schedule-task sets only`
|
|
7269
7595
|
);
|
|
7270
7596
|
await createAreaChecklist(checklistData);
|
|
7597
|
+
addedAreas.push(area.name);
|
|
7271
7598
|
logger32.info(`Created new area checklist for area ${area.name}`);
|
|
7272
7599
|
}
|
|
7273
7600
|
} catch (error) {
|
|
7274
7601
|
logger32.error(`Error processing area ${area.name}:`, error);
|
|
7602
|
+
failedAreas++;
|
|
7275
7603
|
continue;
|
|
7276
7604
|
}
|
|
7277
7605
|
}
|
|
7606
|
+
const attemptKey = `${scheduleTask._id}:${today}`;
|
|
7607
|
+
const tried = (attempts.get(attemptKey) ?? 0) + 1;
|
|
7608
|
+
attempts.set(attemptKey, tried);
|
|
7609
|
+
if (failedAreas === 0 || tried >= MAX_ATTEMPTS) {
|
|
7610
|
+
await markScheduleTaskRan(scheduleTask._id, today);
|
|
7611
|
+
attempts.delete(attemptKey);
|
|
7612
|
+
}
|
|
7613
|
+
if (addedAreas.length > 0) {
|
|
7614
|
+
fired.push({
|
|
7615
|
+
_id: scheduleTask._id,
|
|
7616
|
+
site: scheduleTask.site,
|
|
7617
|
+
serviceType,
|
|
7618
|
+
title: scheduleTask.title,
|
|
7619
|
+
time: scheduleTask.time,
|
|
7620
|
+
areas: addedAreas
|
|
7621
|
+
});
|
|
7622
|
+
}
|
|
7278
7623
|
processedCount++;
|
|
7279
7624
|
validatedCount++;
|
|
7280
7625
|
validatedTasks.push(scheduleTask);
|
|
@@ -7286,6 +7631,17 @@ function useScheduleTaskService() {
|
|
|
7286
7631
|
`Error processing schedule task ${scheduleTask._id}:`,
|
|
7287
7632
|
error
|
|
7288
7633
|
);
|
|
7634
|
+
try {
|
|
7635
|
+
const key = `${scheduleTask._id}:${today}`;
|
|
7636
|
+
const tried = (attempts.get(key) ?? 0) + 1;
|
|
7637
|
+
attempts.set(key, tried);
|
|
7638
|
+
if (tried >= MAX_ATTEMPTS && scheduleTask._id) {
|
|
7639
|
+
await markScheduleTaskRan(scheduleTask._id, today);
|
|
7640
|
+
attempts.delete(key);
|
|
7641
|
+
}
|
|
7642
|
+
} catch (markError) {
|
|
7643
|
+
logger32.error("Could not mark failing schedule task", markError);
|
|
7644
|
+
}
|
|
7289
7645
|
continue;
|
|
7290
7646
|
}
|
|
7291
7647
|
}
|
|
@@ -7295,7 +7651,8 @@ function useScheduleTaskService() {
|
|
|
7295
7651
|
return {
|
|
7296
7652
|
processed: processedCount,
|
|
7297
7653
|
validated: validatedCount,
|
|
7298
|
-
tasks: validatedTasks
|
|
7654
|
+
tasks: validatedTasks,
|
|
7655
|
+
fired
|
|
7299
7656
|
};
|
|
7300
7657
|
} catch (error) {
|
|
7301
7658
|
logger32.error("Error processing scheduled tasks:", error);
|
|
@@ -7306,7 +7663,7 @@ function useScheduleTaskService() {
|
|
|
7306
7663
|
}
|
|
7307
7664
|
|
|
7308
7665
|
// src/controllers/hygiene-schedule-task.controller.ts
|
|
7309
|
-
import
|
|
7666
|
+
import Joi19 from "joi";
|
|
7310
7667
|
import { BadRequestError as BadRequestError30, logger as logger33 } from "@7365admin1/node-server-utils";
|
|
7311
7668
|
import { AppServiceType as AppServiceType18 } from "@7365admin1/core";
|
|
7312
7669
|
function useScheduleTaskController() {
|
|
@@ -7337,12 +7694,12 @@ function useScheduleTaskController() {
|
|
|
7337
7694
|
}
|
|
7338
7695
|
async function getScheduleTasks(req, res, next) {
|
|
7339
7696
|
const query = { ...req.query, ...req.params };
|
|
7340
|
-
const validation =
|
|
7341
|
-
page:
|
|
7342
|
-
limit:
|
|
7343
|
-
search:
|
|
7344
|
-
site:
|
|
7345
|
-
serviceType:
|
|
7697
|
+
const validation = Joi19.object({
|
|
7698
|
+
page: Joi19.number().min(1).optional().allow("", null),
|
|
7699
|
+
limit: Joi19.number().min(1).optional().allow("", null),
|
|
7700
|
+
search: Joi19.string().optional().allow("", null),
|
|
7701
|
+
site: Joi19.string().hex().required(),
|
|
7702
|
+
serviceType: Joi19.string().valid(...Object.values(AppServiceType18)).required()
|
|
7346
7703
|
});
|
|
7347
7704
|
const { error } = validation.validate(query);
|
|
7348
7705
|
if (error) {
|
|
@@ -7350,8 +7707,8 @@ function useScheduleTaskController() {
|
|
|
7350
7707
|
next(new BadRequestError30(error.message));
|
|
7351
7708
|
return;
|
|
7352
7709
|
}
|
|
7353
|
-
const page = parseInt(req.query.page)
|
|
7354
|
-
const limit = parseInt(req.query.limit)
|
|
7710
|
+
const page = parseInt(req.query.page) || 1;
|
|
7711
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
7355
7712
|
const search = req.query.search ?? "";
|
|
7356
7713
|
const site = req.params.site ?? "";
|
|
7357
7714
|
const serviceType = req.query.serviceType ?? "";
|
|
@@ -7372,7 +7729,7 @@ function useScheduleTaskController() {
|
|
|
7372
7729
|
}
|
|
7373
7730
|
}
|
|
7374
7731
|
async function getScheduleTaskById(req, res, next) {
|
|
7375
|
-
const validation =
|
|
7732
|
+
const validation = Joi19.string().hex().required();
|
|
7376
7733
|
const _id = req.params.id;
|
|
7377
7734
|
const { error, value } = validation.validate(_id);
|
|
7378
7735
|
if (error) {
|
|
@@ -7392,20 +7749,17 @@ function useScheduleTaskController() {
|
|
|
7392
7749
|
}
|
|
7393
7750
|
async function updateScheduleTask(req, res, next) {
|
|
7394
7751
|
const payload = { id: req.params.id, ...req.body };
|
|
7395
|
-
const
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
)
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
value: Joi18.any().required()
|
|
7407
|
-
})
|
|
7408
|
-
).optional()
|
|
7752
|
+
const optional = Object.fromEntries(
|
|
7753
|
+
Object.entries(scheduleTaskFields).map(([key, rule]) => [
|
|
7754
|
+
key,
|
|
7755
|
+
key === "dates" ? Joi19.array().min(1).items(scheduleDate).optional().messages({
|
|
7756
|
+
"array.min": "Pick at least one date, or choose a repeat."
|
|
7757
|
+
}) : rule.optional()
|
|
7758
|
+
])
|
|
7759
|
+
);
|
|
7760
|
+
const validation = Joi19.object({
|
|
7761
|
+
id: Joi19.string().hex().required(),
|
|
7762
|
+
...optional
|
|
7409
7763
|
});
|
|
7410
7764
|
const { error } = validation.validate(payload);
|
|
7411
7765
|
if (error) {
|
|
@@ -7413,6 +7767,24 @@ function useScheduleTaskController() {
|
|
|
7413
7767
|
next(new BadRequestError30(error.message));
|
|
7414
7768
|
return;
|
|
7415
7769
|
}
|
|
7770
|
+
try {
|
|
7771
|
+
const { set, unset } = splitScheduleUpdate(req.body ?? {});
|
|
7772
|
+
if (TIMING_KEYS.some((key) => key in set || unset.includes(key))) {
|
|
7773
|
+
const stored = await _getScheduleTaskById(req.params.id);
|
|
7774
|
+
const merged = { ...stored, ...set };
|
|
7775
|
+
for (const key of unset)
|
|
7776
|
+
delete merged[key];
|
|
7777
|
+
const problem = timingChanged(stored, merged) ? validateScheduleRules(cleanScheduleInput(merged)) : null;
|
|
7778
|
+
if (problem) {
|
|
7779
|
+
next(new BadRequestError30(problem));
|
|
7780
|
+
return;
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
} catch (error2) {
|
|
7784
|
+
logger33.log({ level: "error", message: error2.message });
|
|
7785
|
+
next(error2);
|
|
7786
|
+
return;
|
|
7787
|
+
}
|
|
7416
7788
|
try {
|
|
7417
7789
|
const { id, ...value } = payload;
|
|
7418
7790
|
await _updateScheduleTask(id, value);
|
|
@@ -7425,7 +7797,7 @@ function useScheduleTaskController() {
|
|
|
7425
7797
|
}
|
|
7426
7798
|
}
|
|
7427
7799
|
async function deleteScheduleTask(req, res, next) {
|
|
7428
|
-
const validation =
|
|
7800
|
+
const validation = Joi19.string().hex().required();
|
|
7429
7801
|
const _id = req.params.id;
|
|
7430
7802
|
const { error, value } = validation.validate(_id);
|
|
7431
7803
|
if (error) {
|
|
@@ -7669,17 +8041,17 @@ function useQRService() {
|
|
|
7669
8041
|
}
|
|
7670
8042
|
|
|
7671
8043
|
// src/controllers/hygiene-qr.controller.ts
|
|
7672
|
-
import
|
|
8044
|
+
import Joi20 from "joi";
|
|
7673
8045
|
import { BadRequestError as BadRequestError31, logger as logger35 } from "@7365admin1/node-server-utils";
|
|
7674
8046
|
function useQRController() {
|
|
7675
8047
|
const { generateQRImage: _generateQRImage, generateQRPDF: _generateQRPDF } = useQRService();
|
|
7676
8048
|
async function generateQR(req, res, next) {
|
|
7677
|
-
const validation =
|
|
7678
|
-
url:
|
|
7679
|
-
filename:
|
|
7680
|
-
title:
|
|
7681
|
-
subtitle:
|
|
7682
|
-
download:
|
|
8049
|
+
const validation = Joi20.object({
|
|
8050
|
+
url: Joi20.string().uri().required(),
|
|
8051
|
+
filename: Joi20.string().optional().allow("", null),
|
|
8052
|
+
title: Joi20.string().optional().allow("", null),
|
|
8053
|
+
subtitle: Joi20.string().optional().allow("", null),
|
|
8054
|
+
download: Joi20.boolean().optional().default(false)
|
|
7683
8055
|
});
|
|
7684
8056
|
const query = { ...req.query };
|
|
7685
8057
|
const { error, value } = validation.validate(query);
|