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