@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/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 totalExistingSets = existing.checklist?.length ?? 0;
4542
- if (totalExistingSets >= setCount2) {
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 ${totalExistingSets} set(s) (configured: ${setCount2}). Skipping auto-gen sets.`
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 import_joi10 = __toESM(require("joi"));
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
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">&#10007;</span>' : ""}</td>
4735
5014
  <td class="icon-cell">${unit.approve ? '<span class="approve-icon">&#10003;</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 = import_joi10.default.object({
4955
- site: import_joi10.default.string().hex().required(),
4956
- schedule: import_joi10.default.string().hex().required()
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 = import_joi10.default.object({
4977
- page: import_joi10.default.number().min(1).optional().allow("", null),
4978
- limit: import_joi10.default.number().min(1).optional().allow("", null),
4979
- search: import_joi10.default.string().optional().allow("", null),
4980
- type: import_joi10.default.string().valid(...allowedTypes, "all").optional().allow("", null),
4981
- status: import_joi10.default.string().valid(...allowedStatus, "all").optional().allow("", null),
4982
- schedule: import_joi10.default.string().hex().required(),
4983
- serviceType: import_joi10.default.string().valid(...Object.values(import_core11.AppServiceType)).required()
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 = import_joi10.default.object({
5019
- page: import_joi10.default.number().min(1).optional().allow("", null),
5020
- limit: import_joi10.default.number().min(1).optional().allow("", null),
5021
- search: import_joi10.default.string().optional().allow("", null),
5022
- type: import_joi10.default.string().optional().allow("", ...allowedTypes),
5023
- schedule: import_joi10.default.string().hex().required(),
5024
- status: import_joi10.default.string().allow("", null, ...allowedStatus),
5025
- createdAt: import_joi10.default.alternatives().try(import_joi10.default.date(), import_joi10.default.string()).optional().allow("", null)
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 = import_joi10.default.string().hex().required();
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 = import_joi10.default.object({
5080
- page: import_joi10.default.number().min(1).optional().allow("", null),
5081
- limit: import_joi10.default.number().min(1).optional().allow("", null),
5082
- search: import_joi10.default.string().optional().allow("", null),
5083
- serviceType: import_joi10.default.string().valid(...Object.values(import_core11.AppServiceType)).required(),
5084
- id: import_joi10.default.string().hex().required()
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 = import_joi10.default.object({
5135
- id: import_joi10.default.string().hex().required(),
5136
- set: import_joi10.default.number().integer().min(1).required(),
5137
- unit: import_joi10.default.string().hex().required(),
5138
- decision: import_joi10.default.string().valid("approve", "reject").required(),
5139
- approve: import_joi10.default.boolean().optional(),
5140
- reject: import_joi10.default.boolean().optional(),
5141
- remarks: import_joi10.default.string().optional().allow("", null),
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: import_joi10.default.array().items(import_joi10.default.string().trim().min(1)).optional(),
5148
- completedBy: import_joi10.default.string().hex().required()
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 = import_joi10.default.string().hex().required();
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 import_joi11 = __toESM(require("joi"));
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 = import_joi11.default.object({
5236
- site: import_joi11.default.string().hex().required(),
5237
- serviceType: import_joi11.default.string().valid(...Object.values(import_core12.AppServiceType)).required(),
5238
- name: import_joi11.default.string().required(),
5239
- unitOfMeasurement: import_joi11.default.string().required(),
5240
- attachment: import_joi11.default.string().allow(null, "").optional()
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 import_joi12 = __toESM(require("joi"));
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 = import_joi12.default.object({
5562
- page: import_joi12.default.number().min(1).optional().allow("", null),
5563
- limit: import_joi12.default.number().min(1).optional().allow("", null),
5564
- search: import_joi12.default.string().optional().allow("", null),
5565
- site: import_joi12.default.string().hex().required(),
5566
- serviceType: import_joi12.default.string().valid(...Object.values(import_core13.AppServiceType)).required()
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 = import_joi12.default.string().hex().required();
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 = import_joi12.default.object({
5617
- id: import_joi12.default.string().hex().required(),
5618
- name: import_joi12.default.string().optional().allow("", null),
5619
- unitOfMeasurement: import_joi12.default.string().optional().allow("", null),
5620
- qty: import_joi12.default.number().min(0).optional().allow("", null),
5621
- attachment: import_joi12.default.string().optional().allow("", null)
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 = import_joi12.default.string().hex().required();
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 import_joi13 = __toESM(require("joi"));
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 = import_joi13.default.object({
5674
- site: import_joi13.default.string().hex().required(),
5675
- serviceType: import_joi13.default.string().valid(...Object.values(import_core14.AppServiceType)).required(),
5676
- supply: import_joi13.default.string().hex().required(),
5677
- in: import_joi13.default.number().min(0).optional(),
5678
- out: import_joi13.default.number().min(0).optional(),
5679
- balance: import_joi13.default.number().min(0).required(),
5680
- remarks: import_joi13.default.string().optional().allow("", null)
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 import_joi14 = __toESM(require("joi"));
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 = import_joi14.default.object({
5904
- site: import_joi14.default.string().hex().required(),
5905
- serviceType: import_joi14.default.string().valid(...Object.values(import_core15.AppServiceType)).required(),
5906
- supply: import_joi14.default.string().hex().required(),
5907
- qty: import_joi14.default.number().min(0).required(),
5908
- remarks: import_joi14.default.string().optional().allow("", null)
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 = import_joi14.default.object({
5929
- page: import_joi14.default.number().min(1).optional().allow("", null),
5930
- limit: import_joi14.default.number().min(1).optional().allow("", null),
5931
- search: import_joi14.default.string().optional().allow("", null),
5932
- site: import_joi14.default.string().hex().required(),
5933
- serviceType: import_joi14.default.string().valid(...Object.values(import_core15.AppServiceType)).required(),
5934
- supply: import_joi14.default.string().hex().required()
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 import_joi15 = __toESM(require("joi"));
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 = import_joi15.default.object({
6002
- site: import_joi15.default.string().hex().required(),
6003
- serviceType: import_joi15.default.string().valid(...Object.values(import_core16.AppServiceType)).required(),
6004
- supply: import_joi15.default.string().hex().required(),
6005
- supplyName: import_joi15.default.string().required(),
6006
- supplyQty: import_joi15.default.number().min(0).required(),
6007
- qty: import_joi15.default.number().min(0).required(),
6008
- attachment: import_joi15.default.array().items(import_joi15.default.string()).optional().allow(null),
6009
- createdBy: import_joi15.default.string().hex().required(),
6010
- createdByName: import_joi15.default.string().required()
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 import_joi16 = __toESM(require("joi"));
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 = import_joi16.default.object({
6515
- site: import_joi16.default.string().hex().required(),
6516
- serviceType: import_joi16.default.string().valid(...Object.values(import_core18.AppServiceType)).required(),
6517
- supply: import_joi16.default.string().hex().required(),
6518
- qty: import_joi16.default.number().min(0).required(),
6519
- attachment: import_joi16.default.array().items(import_joi16.default.string()).optional().allow(null),
6520
- createdBy: import_joi16.default.string().hex().required()
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 = import_joi16.default.object({
6550
- site: import_joi16.default.string().hex().required(),
6551
- serviceType: import_joi16.default.string().valid(...Object.values(import_core18.AppServiceType)).required(),
6552
- createdBy: import_joi16.default.string().hex().required(),
6553
- items: import_joi16.default.array().items(
6554
- import_joi16.default.object({
6555
- supply: import_joi16.default.string().hex().required(),
6556
- qty: import_joi16.default.number().min(0).required(),
6557
- attachment: import_joi16.default.array().items(import_joi16.default.string()).optional().allow(null)
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 = import_joi16.default.object({
6580
- page: import_joi16.default.number().min(1).optional().allow("", null),
6581
- limit: import_joi16.default.number().min(1).optional().allow("", null),
6582
- search: import_joi16.default.string().optional().allow("", null),
6583
- site: import_joi16.default.string().hex().required(),
6584
- serviceType: import_joi16.default.string().valid(...Object.values(import_core18.AppServiceType)).required()
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 = import_joi16.default.string().hex().required();
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 = import_joi16.default.object({
6654
- id: import_joi16.default.string().hex().required(),
6655
- decision: import_joi16.default.string().valid("approve", "disapprove").required(),
6656
- approve: import_joi16.default.boolean().required(),
6657
- reject: import_joi16.default.boolean().required(),
6658
- remarks: import_joi16.default.string().optional().allow("", null),
6659
- completedBy: import_joi16.default.string().hex().required()
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 import_joi17 = __toESM(require("joi"));
6979
+ var import_joi18 = __toESM(require("joi"));
6693
6980
  var import_mongodb18 = require("mongodb");
6694
- var scheduleTaskSchema = import_joi17.default.object({
6695
- site: import_joi17.default.string().hex().required(),
6696
- serviceType: import_joi17.default.string().valid(...Object.values(import_core19.AppServiceType)).required(),
6697
- title: import_joi17.default.string().required(),
6698
- time: import_joi17.default.string().pattern(/^([0-1]\d|2[0-3]):([0-5]\d)$/).required(),
6699
- dates: import_joi17.default.array().min(1).items(
6700
- import_joi17.default.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
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 getAllScheduleTask() {
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 updateValue = { ...value, updatedAt: /* @__PURE__ */ new Date() };
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
- { $set: updateValue },
7345
+ { _id, status: { $ne: "deleted" } },
7346
+ update,
6944
7347
  { session }
6945
7348
  );
6946
- if (res.modifiedCount === 0) {
6947
- throw new import_node_server_utils33.InternalServerError(
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
- const now = currentDate;
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 scheduleTasks = await getAllScheduleTask();
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 = Array.from(
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 schedTaskId = scheduleTask._id;
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 import_joi18 = __toESM(require("joi"));
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 = import_joi18.default.object({
7333
- page: import_joi18.default.number().min(1).optional().allow("", null),
7334
- limit: import_joi18.default.number().min(1).optional().allow("", null),
7335
- search: import_joi18.default.string().optional().allow("", null),
7336
- site: import_joi18.default.string().hex().required(),
7337
- serviceType: import_joi18.default.string().valid(...Object.values(import_core20.AppServiceType)).required()
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) ?? 1;
7346
- const limit = parseInt(req.query.limit) ?? 10;
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 = import_joi18.default.string().hex().required();
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 validation = import_joi18.default.object({
7388
- id: import_joi18.default.string().hex().required(),
7389
- title: import_joi18.default.string().optional().allow("", null),
7390
- time: import_joi18.default.string().pattern(/^([0-1]\d|2[0-3]):([0-5]\d)$/).optional().allow("", null),
7391
- dates: import_joi18.default.array().min(1).items(
7392
- import_joi18.default.string().pattern(/^\d{4}-\d{2}-\d{2}$/).required()
7393
- ).optional(),
7394
- description: import_joi18.default.string().optional().allow("", null),
7395
- areas: import_joi18.default.array().min(1).items(
7396
- import_joi18.default.object({
7397
- name: import_joi18.default.string().required(),
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 = import_joi18.default.string().hex().required();
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 import_joi19 = __toESM(require("joi"));
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 = import_joi19.default.object({
7670
- url: import_joi19.default.string().uri().required(),
7671
- filename: import_joi19.default.string().optional().allow("", null),
7672
- title: import_joi19.default.string().optional().allow("", null),
7673
- subtitle: import_joi19.default.string().optional().allow("", null),
7674
- download: import_joi19.default.boolean().optional().default(false)
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);