@7365admin1/module-hygiene 4.28.1-staging.7 → 4.28.1-staging.8

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.mjs CHANGED
@@ -9,7 +9,7 @@ var allowedStatus = [
9
9
  var allowedPeriods = ["today", "thisWeek", "thisMonth"];
10
10
 
11
11
  // src/repositories/hygiene-dashboard.repository.ts
12
- import moment from "moment-timezone";
12
+ import moment2 from "moment-timezone";
13
13
  import { ObjectId } from "mongodb";
14
14
  import {
15
15
  useAtlas,
@@ -20,6 +20,54 @@ import {
20
20
  paginate,
21
21
  logger
22
22
  } from "@7365admin1/node-server-utils";
23
+
24
+ // src/utils/hygiene-dashboard-metrics.util.ts
25
+ import moment from "moment-timezone";
26
+ var DASHBOARD_TIMEZONE = "Asia/Singapore";
27
+ var PERIOD_UNITS = {
28
+ thisWeek: { boundary: "isoWeek", step: "week" },
29
+ thisMonth: { boundary: "month", step: "month" }
30
+ };
31
+ var DAY_UNIT = {
32
+ boundary: "day",
33
+ step: "day"
34
+ };
35
+ function unitsOf(period) {
36
+ return PERIOD_UNITS[period] ?? DAY_UNIT;
37
+ }
38
+ function at(now) {
39
+ return now === void 0 || now === null ? moment.tz(DASHBOARD_TIMEZONE) : moment.tz(now, DASHBOARD_TIMEZONE);
40
+ }
41
+ function getPeriodRange(period, now) {
42
+ const { boundary } = unitsOf(period);
43
+ const point = at(now);
44
+ return {
45
+ $gte: point.clone().startOf(boundary).toDate(),
46
+ $lte: point.clone().endOf(boundary).toDate()
47
+ };
48
+ }
49
+ function getPreviousPeriodRange(period, now) {
50
+ const { boundary, step } = unitsOf(period);
51
+ const point = at(now).subtract(1, step);
52
+ return {
53
+ $gte: point.clone().startOf(boundary).toDate(),
54
+ $lte: point.clone().endOf(boundary).toDate()
55
+ };
56
+ }
57
+ function calculatePercentageChange(currentCount, previousCount) {
58
+ if (typeof currentCount !== "number" || !Number.isFinite(currentCount)) {
59
+ return null;
60
+ }
61
+ if (typeof previousCount !== "number" || !Number.isFinite(previousCount)) {
62
+ return null;
63
+ }
64
+ if (previousCount === 0)
65
+ return null;
66
+ return Math.round((currentCount - previousCount) / previousCount * 100 * 100) / 100;
67
+ }
68
+ var SUPPLY_ALERT_HAS_NO_PRIOR_PERIOD = "Supply alerts count what is out of stock right now, and the supply record keeps no history, so there is no earlier period to compare against.";
69
+
70
+ // src/repositories/hygiene-dashboard.repository.ts
23
71
  function useHygieneDashboardRepository() {
24
72
  const db = useAtlas.getDb();
25
73
  if (!db) {
@@ -90,59 +138,16 @@ function useHygieneDashboardRepository() {
90
138
  logger.info(`Cache hit for dashboard: ${dashboardCacheKey}`);
91
139
  return cachedDashboard;
92
140
  }
93
- function formatEndDate(date) {
94
- if (typeof date === "string") {
95
- const dateWithoutTimezone = date.replace(/\s*\+\d{2}:\d{2}$/, "");
96
- date = new Date(dateWithoutTimezone);
97
- }
98
- const singaporeMoment = moment.tz(date, "Asia/Singapore");
99
- singaporeMoment.set({
100
- hour: 23,
101
- minute: 59,
102
- second: 59,
103
- millisecond: 999
104
- });
105
- return singaporeMoment.toDate();
106
- }
107
- function getDateRange(p) {
108
- const start = /* @__PURE__ */ new Date();
109
- start.setHours(0, 0, 0, 0);
110
- if (p === "today") {
111
- return { $gte: start, $lte: formatEndDate(start) };
112
- }
113
- const days = p === "thisWeek" ? 7 : 30;
114
- const rangeStart = /* @__PURE__ */ new Date();
115
- rangeStart.setDate(start.getDate() - days);
116
- rangeStart.setHours(0, 0, 0, 0);
117
- return { $gte: rangeStart, $lte: formatEndDate(/* @__PURE__ */ new Date()) };
118
- }
119
- const calculatePercentageChange = (currentCount, previousCount) => {
120
- if (previousCount === 0) {
121
- return currentCount > 0 ? 100 : 0;
122
- }
123
- return Math.round(
124
- (currentCount - previousCount) / previousCount * 100 * 100
125
- ) / 100;
126
- };
127
141
  const workOrderService = workOrderServiceMap[serviceType] ?? serviceType;
128
142
  try {
129
- const today = /* @__PURE__ */ new Date();
130
- today.setHours(0, 0, 0, 0);
131
- const yesterday = /* @__PURE__ */ new Date();
132
- yesterday.setDate(today.getDate() - 1);
133
- yesterday.setHours(0, 0, 0, 0);
134
- const yesterdayEnd = new Date(yesterday);
135
- yesterdayEnd.setHours(23, 59, 59, 999);
136
- const todayEnd = formatEndDate(/* @__PURE__ */ new Date());
137
- const periodRange = getDateRange(period);
143
+ const periodRange = getPeriodRange(period);
144
+ const priorPeriodRange = getPreviousPeriodRange(period);
138
145
  const [
139
146
  workOrderReport,
140
147
  supplyAlertReport,
141
148
  taskCompletedReport,
142
- yesterdayWorkOrderReport,
143
- todayWorkOrderReport,
144
- yesterdayTaskCompletedReport,
145
- todayTaskCompletedReport
149
+ priorWorkOrderReport,
150
+ priorTaskCompletedReport
146
151
  ] = await Promise.all([
147
152
  // 1. Open work orders (period-filtered) with inProgress breakdown
148
153
  workOrderCollection.aggregate([
@@ -186,45 +191,28 @@ function useHygieneDashboardRepository() {
186
191
  }
187
192
  }
188
193
  ]).toArray(),
189
- // 4. Yesterday open work orders (for % change)
194
+ /*
195
+ 4. Open work orders in the PREVIOUS period (for % change).
196
+
197
+ The current side of the comparison is no longer queried separately:
198
+ it is now the same range as tile 1, so `wFacet.total` below IS the
199
+ current count. Two aggregations dropped, not by cutting a measure but
200
+ because the fix made them duplicates.
201
+ */
190
202
  workOrderCollection.aggregate([
191
203
  {
192
204
  $match: {
193
205
  site,
194
206
  service: workOrderService,
195
- createdAt: { $gte: yesterday, $lte: yesterdayEnd },
207
+ createdAt: priorPeriodRange,
196
208
  status: { $nin: ["completed"] }
197
209
  }
198
210
  },
199
211
  { $count: "count" }
200
212
  ]).toArray(),
201
- // 5. Today open work orders (for % change)
202
- workOrderCollection.aggregate([
203
- {
204
- $match: {
205
- site,
206
- service: workOrderService,
207
- createdAt: { $gte: today, $lte: todayEnd },
208
- status: { $nin: ["completed"] }
209
- }
210
- },
211
- { $count: "count" }
212
- ]).toArray(),
213
- // 6. Yesterday task completed — area checklists (for % change)
214
- areaChecklistCollection.aggregate([
215
- ...areaChecklistScheduleLookup(site, serviceType, {
216
- $gte: yesterday,
217
- $lte: yesterdayEnd
218
- }),
219
- { $match: { status: "completed" } },
220
- { $count: "count" }
221
- ]).toArray(),
222
- // 7. Today task completed — area checklists (for % change)
213
+ // 5. Task completed in the PREVIOUS period — area checklists (for % change)
223
214
  areaChecklistCollection.aggregate([
224
- ...areaChecklistScheduleLookup(site, serviceType, {
225
- $gte: today,
226
- $lte: todayEnd
227
- }),
215
+ ...areaChecklistScheduleLookup(site, serviceType, priorPeriodRange),
228
216
  { $match: { status: "completed" } },
229
217
  { $count: "count" }
230
218
  ]).toArray()
@@ -240,21 +228,26 @@ function useHygieneDashboardRepository() {
240
228
  count: wFacet.total[0]?.count ?? 0,
241
229
  inProgress: wFacet.inProgress[0]?.count ?? 0,
242
230
  percentage: calculatePercentageChange(
243
- todayWorkOrderReport[0]?.count ?? 0,
244
- yesterdayWorkOrderReport[0]?.count ?? 0
231
+ wFacet.total[0]?.count ?? 0,
232
+ priorWorkOrderReport[0]?.count ?? 0
245
233
  )
246
234
  },
247
235
  supplyAlert: {
236
+ // The count is real: active supplies at qty 0 for this site and
237
+ // service. The percentage beside it was the literal 0 and had never
238
+ // been computed from anything — see the util for why there is no
239
+ // prior period to compute one from.
248
240
  count: supplyAlertReport[0]?.count ?? 0,
249
- percentage: 0
241
+ percentage: null,
242
+ percentageUnavailableReason: SUPPLY_ALERT_HAS_NO_PRIOR_PERIOD
250
243
  },
251
244
  taskCompleted: {
252
245
  completed: tFacet.completed[0]?.count ?? 0,
253
246
  total: tFacet.total[0]?.count ?? 0,
254
247
  inProgress: tFacet.inProgress[0]?.count ?? 0,
255
248
  percentage: calculatePercentageChange(
256
- todayTaskCompletedReport[0]?.count ?? 0,
257
- yesterdayTaskCompletedReport[0]?.count ?? 0
249
+ tFacet.completed[0]?.count ?? 0,
250
+ priorTaskCompletedReport[0]?.count ?? 0
258
251
  )
259
252
  }
260
253
  };
@@ -292,8 +285,8 @@ function useHygieneDashboardRepository() {
292
285
  return cachedData;
293
286
  }
294
287
  const workOrderService = workOrderServiceMap[serviceType] ?? serviceType;
295
- const weekStart = moment.tz("Asia/Singapore").startOf("isoWeek").toDate();
296
- const weekEnd = moment.tz("Asia/Singapore").endOf("isoWeek").toDate();
288
+ const weekStart = moment2.tz("Asia/Singapore").startOf("isoWeek").toDate();
289
+ const weekEnd = moment2.tz("Asia/Singapore").endOf("isoWeek").toDate();
297
290
  const weekRange = { $gte: weekStart, $lte: weekEnd };
298
291
  const dayLabels = {
299
292
  1: "Sun",
@@ -373,8 +366,8 @@ function useHygieneDashboardRepository() {
373
366
  logger.info(`Cache hit for todayTaskSchedule: ${cacheKey}`);
374
367
  return cachedData;
375
368
  }
376
- const todayStart = moment.tz("Asia/Singapore").startOf("day").toDate();
377
- const todayEnd = moment.tz("Asia/Singapore").endOf("day").toDate();
369
+ const todayStart = moment2.tz("Asia/Singapore").startOf("day").toDate();
370
+ const todayEnd = moment2.tz("Asia/Singapore").endOf("day").toDate();
378
371
  const todayRange = { $gte: todayStart, $lte: todayEnd };
379
372
  try {
380
373
  const [items, countResult] = await Promise.all([
@@ -478,8 +471,8 @@ function useHygieneDashboardRepository() {
478
471
  logger.info(`Cache hit for staffAttendance: ${cacheKey}`);
479
472
  return cachedData;
480
473
  }
481
- const todayStart = moment.tz("Asia/Singapore").startOf("day").toDate();
482
- const todayEnd = moment.tz("Asia/Singapore").endOf("day").toDate();
474
+ const todayStart = moment2.tz("Asia/Singapore").startOf("day").toDate();
475
+ const todayEnd = moment2.tz("Asia/Singapore").endOf("day").toDate();
483
476
  const todayStartStr = todayStart.toISOString();
484
477
  const todayEndStr = todayEnd.toISOString();
485
478
  try {
@@ -2528,7 +2521,7 @@ function MParentChecklist(value) {
2528
2521
 
2529
2522
  // src/repositories/hygiene-parent-checklist.repository.ts
2530
2523
  import { ObjectId as ObjectId7 } from "mongodb";
2531
- import moment2 from "moment-timezone";
2524
+ import moment3 from "moment-timezone";
2532
2525
  import {
2533
2526
  useAtlas as useAtlas5,
2534
2527
  InternalServerError as InternalServerError4,
@@ -2832,11 +2825,11 @@ function useParentChecklistRepo() {
2832
2825
  cacheOptions.search = search;
2833
2826
  }
2834
2827
  const singaporeTz = "Asia/Singapore";
2835
- const normalizedStartDate = typeof startDate === "string" ? startDate : moment2(startDate).tz(singaporeTz).format("YYYY-MM-DD");
2836
- const normalizedEndDate = typeof endDate === "string" ? endDate : moment2(endDate).tz(singaporeTz).format("YYYY-MM-DD");
2828
+ const normalizedStartDate = typeof startDate === "string" ? startDate : moment3(startDate).tz(singaporeTz).format("YYYY-MM-DD");
2829
+ const normalizedEndDate = typeof endDate === "string" ? endDate : moment3(endDate).tz(singaporeTz).format("YYYY-MM-DD");
2837
2830
  if (startDate && endDate) {
2838
- const startOfDay = moment2.tz(normalizedStartDate, "YYYY-MM-DD", singaporeTz).startOf("day").toDate();
2839
- const endOfDay = moment2.tz(normalizedEndDate, "YYYY-MM-DD", singaporeTz).endOf("day").toDate();
2831
+ const startOfDay = moment3.tz(normalizedStartDate, "YYYY-MM-DD", singaporeTz).startOf("day").toDate();
2832
+ const endOfDay = moment3.tz(normalizedEndDate, "YYYY-MM-DD", singaporeTz).endOf("day").toDate();
2840
2833
  query.createdAt = {
2841
2834
  $gte: startOfDay,
2842
2835
  $lte: endOfDay
@@ -2845,12 +2838,12 @@ function useParentChecklistRepo() {
2845
2838
  cacheOptions.endDate = normalizedEndDate;
2846
2839
  } else if (startDate) {
2847
2840
  query.createdAt = {
2848
- $gte: moment2.tz(normalizedStartDate, "YYYY-MM-DD", singaporeTz).startOf("day").toDate()
2841
+ $gte: moment3.tz(normalizedStartDate, "YYYY-MM-DD", singaporeTz).startOf("day").toDate()
2849
2842
  };
2850
2843
  cacheOptions.startDate = normalizedStartDate;
2851
2844
  } else if (endDate) {
2852
2845
  query.createdAt = {
2853
- $lte: moment2.tz(normalizedEndDate, "YYYY-MM-DD", singaporeTz).endOf("day").toDate()
2846
+ $lte: moment3.tz(normalizedEndDate, "YYYY-MM-DD", singaporeTz).endOf("day").toDate()
2854
2847
  };
2855
2848
  cacheOptions.endDate = normalizedEndDate;
2856
2849
  }