@7365admin1/module-hygiene 4.28.1-staging.9 → 4.28.2-staging.10

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.
@@ -0,0 +1,5 @@
1
+ ---
2
+ "@7365admin1/module-hygiene": minor
3
+ ---
4
+
5
+ Add approve / disapprove to hygiene check out items (repository, controller and decision util).
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @7365admin1/module-hygiene
2
2
 
3
+ ## 4.28.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 0166aeb: Hygiene dashboard metrics: report the selected period honestly instead of fabricating figures
8
+
9
+ The dashboard's metric calculations were computed inline inside
10
+ `hygiene-dashboard.repository.ts` and were wrong in four ways. They now live in
11
+ `src/utils/hygiene-dashboard-metrics.util.ts`, covered by unit tests (`yarn test`),
12
+ so a change to a metric fails a test rather than a screen. What a consumer of the
13
+ dashboard endpoints sees change:
14
+
15
+ - **Trend comparisons follow the selected period.** The "vs previous" figure was
16
+ always computed against yesterday, whatever range the user picked, so a
17
+ month-to-month comparison was really a day-to-day one. It now compares the
18
+ selected period against the period immediately before it.
19
+ - **Day boundaries are Singapore time.** Period start/end were taken from the
20
+ server host's midnight, so a host on UTC put work into the wrong day. All
21
+ boundaries are now computed in `Asia/Singapore`.
22
+ - **No invented percentages on an empty prior period.** When the previous period
23
+ had no data, the change was reported as `100%` (or `0`) as if measured. It now
24
+ returns `null`, so the UI can show "no comparison available" rather than a
25
+ number nobody can reproduce.
26
+ - **The supply alert percentage is no longer hard-coded.** It was emitted as a
27
+ fixed value regardless of stock; it is now derived, and reports its reason when
28
+ there is no prior period to compare against.
29
+
30
+ No API surface changes and no consumer code change is required — the same fields
31
+ are returned, with `null` now possible where a comparison genuinely cannot be made.
32
+
3
33
  ## 4.28.0
4
34
 
5
35
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -512,6 +512,36 @@ declare function useStockController(): {
512
512
  getStocksBySupplyId: (req: Request, res: Response, next: NextFunction) => Promise<void>;
513
513
  };
514
514
 
515
+ /**
516
+ * The approval decision on a hygiene check out item, as data.
517
+ *
518
+ * Kept out of the repository so it can be tested without a database, the same
519
+ * way `hygiene-dashboard-metrics.util.ts` is. The repository wraps the ids in
520
+ * `ObjectId` and hands the rest of this straight to `$set`.
521
+ *
522
+ * Shape follows `hygiene-area-checklist`: an approve/reject pair plus a
523
+ * terminal `status: "completed"`, so the `allowedCheckOutItemStatus`
524
+ * vocabulary (`pending` | `completed`) gains no new value. Rows written before
525
+ * this feature carry no `approve`/`reject` at all, which reads as "not
526
+ * decided" — nothing is backfilled.
527
+ */
528
+ declare const CHECKOUT_DECISIONS: readonly ["approve", "disapprove"];
529
+ type TCheckOutDecision = (typeof CHECKOUT_DECISIONS)[number];
530
+ declare function isCheckOutDecision(value: string): value is TCheckOutDecision;
531
+ /**
532
+ * Only a `pending` row may be decided. Returning the status the update must
533
+ * match — rather than matching on `_id` alone — is what makes a second
534
+ * approval a refusal instead of a silent overwrite of the first one.
535
+ */
536
+ declare const CHECKOUT_DECIDABLE_STATUS = "pending";
537
+ declare function buildCheckOutDecision(decision: TCheckOutDecision, remarks?: string | null, now?: string): {
538
+ approve: boolean;
539
+ reject: boolean;
540
+ status: string;
541
+ remarks: string;
542
+ updatedAt: string;
543
+ };
544
+
515
545
  declare const allowedCheckOutItemStatus: string[];
516
546
  type TCheckOutItem = {
517
547
  _id?: ObjectId;
@@ -525,6 +555,10 @@ type TCheckOutItem = {
525
555
  createdBy: string | ObjectId;
526
556
  createdByName: string;
527
557
  status?: (typeof allowedCheckOutItemStatus)[number];
558
+ approve?: boolean;
559
+ reject?: boolean;
560
+ remarks?: string;
561
+ completedBy?: string | ObjectId;
528
562
  createdAt?: string;
529
563
  updatedAt?: string;
530
564
  deletedAt?: string;
@@ -534,12 +568,13 @@ type TCheckOutItemCreateService = Pick<TCheckOutItem, "site" | "serviceType" | "
534
568
  type TCheckOutItemCreateByBatchService = Pick<TCheckOutItem, "site" | "serviceType" | "createdBy"> & {
535
569
  items: Pick<TCheckOutItem, "supply" | "qty" | "attachment">[];
536
570
  };
571
+ type TCheckOutItemDecision = Pick<TCheckOutItem, "approve" | "reject" | "remarks" | "completedBy">;
537
572
  type TCheckOutItemGetQuery = {
538
573
  page?: number;
539
574
  limit?: number;
540
575
  search?: string;
541
576
  } & Pick<TCheckOutItem, "site" | "serviceType">;
542
- type TCheckOutItemGetById = Pick<TCheckOutItem, "_id" | "site" | "serviceType" | "supply" | "supplyName" | "supplyQty" | "qty" | "status"> & {
577
+ type TCheckOutItemGetById = Pick<TCheckOutItem, "_id" | "site" | "serviceType" | "supply" | "supplyName" | "supplyQty" | "qty" | "status" | "approve" | "reject" | "remarks"> & {
543
578
  unitOfMeasurement?: string;
544
579
  };
545
580
  declare const checkOutItemSchema: Joi.ObjectSchema<any>;
@@ -554,6 +589,10 @@ declare function MCheckOutItem(value: TCheckOutItemCreate): {
554
589
  createdBy: string | ObjectId;
555
590
  createdByName: string;
556
591
  status: string;
592
+ approve: boolean;
593
+ reject: boolean;
594
+ remarks: string;
595
+ completedBy: string;
557
596
  createdAt: string;
558
597
  updatedAt: string;
559
598
  deletedAt: string;
@@ -566,6 +605,7 @@ declare function useCheckOutItemRepository(): {
566
605
  getCheckOutItems: ({ page, limit, search, site, serviceType, }: TCheckOutItemGetQuery) => Promise<{}>;
567
606
  getCheckOutItemById: (_id: string | ObjectId, session?: ClientSession) => Promise<TCheckOutItemGetById>;
568
607
  completeCheckOutItem: (_id: string | ObjectId, session?: ClientSession) => Promise<number>;
608
+ decideCheckOutItem: (_id: string | ObjectId, value: TCheckOutItemDecision, session?: ClientSession) => Promise<number>;
569
609
  };
570
610
 
571
611
  declare function useCheckOutItemService(): {
@@ -578,6 +618,7 @@ declare function useCheckOutItemController(): {
578
618
  createCheckOutItemByBatch: (req: Request, res: Response, next: NextFunction) => Promise<void>;
579
619
  getCheckOutItems: (req: Request, res: Response, next: NextFunction) => Promise<void>;
580
620
  getCheckOutItemById: (req: Request, res: Response, next: NextFunction) => Promise<void>;
621
+ decideCheckOutItem: (req: Request, res: Response, next: NextFunction) => Promise<void>;
581
622
  };
582
623
 
583
624
  type TScheduleTask = {
@@ -664,4 +705,4 @@ declare function useQRController(): {
664
705
  generateQR: (req: Request, res: Response, next: NextFunction) => Promise<void>;
665
706
  };
666
707
 
667
- export { MArea, MAreaChecklist, MCheckOutItem, MParentChecklist, MScheduleTask, MStock, MSupply, MUnit, TArea, TAreaChecklist, TAreaChecklistBatchCreate, TAreaChecklistCreate, TAreaChecklistUnits, TAreaChecklistUnitsUpdate, TAreaChecklistUpdate, TAreaCreate, TAreaGetQuery, TAreaUnits, TAreaUpdate, TAreaUpdateChecklist, TCheckOutItem, TCheckOutItemCreate, TCheckOutItemCreateByBatchService, TCheckOutItemCreateService, TCheckOutItemGetById, TCheckOutItemGetQuery, TCleaningScheduleArea, TCleaningScheduleAreaGetQuery, TGetStocksQuery, TParentChecklist, TParentChecklistCreate, TParentChecklistGetQuery, TScheduleTask, TScheduleTaskCreate, TScheduleTaskGetById, TScheduleTaskGetQuery, TScheduleTaskUpdate, TStock, TStockCreate, TStockCreateService, TSupply, TSupplyCreate, TSupplyGetById, TSupplyGetQuery, TSupplyUpdate, TUnit, TUnitCreate, TUnitGetQuery, TUnitUpdate, allowedCheckOutItemStatus, allowedChecklistStatus, allowedPeriods, allowedStatus, allowedTypes, areaChecklistSchema, areaSchema, checkOutItemSchema, parentChecklistSchema, scheduleTaskSchema, stockSchema, supplySchema, unitSchema, useAreaChecklistController, useAreaChecklistRepo, useAreaChecklistService, useAreaController, useAreaExportService, useAreaRepo, useAreaService, useCheckOutItemController, useCheckOutItemRepository, useCheckOutItemService, useHygieneDashboardController, useHygieneDashboardRepository, useParentChecklistController, useParentChecklistRepo, useQRController, useQRService, useScheduleTaskController, useScheduleTaskRepository, useScheduleTaskService, useStockController, useStockRepository, useStockService, useSupplyController, useSupplyRepository, useUnitController, useUnitExportService, useUnitRepository, useUnitService };
708
+ export { CHECKOUT_DECIDABLE_STATUS, CHECKOUT_DECISIONS, MArea, MAreaChecklist, MCheckOutItem, MParentChecklist, MScheduleTask, MStock, MSupply, MUnit, TArea, TAreaChecklist, TAreaChecklistBatchCreate, TAreaChecklistCreate, TAreaChecklistUnits, TAreaChecklistUnitsUpdate, TAreaChecklistUpdate, TAreaCreate, TAreaGetQuery, TAreaUnits, TAreaUpdate, TAreaUpdateChecklist, TCheckOutDecision, TCheckOutItem, TCheckOutItemCreate, TCheckOutItemCreateByBatchService, TCheckOutItemCreateService, TCheckOutItemDecision, TCheckOutItemGetById, TCheckOutItemGetQuery, TCleaningScheduleArea, TCleaningScheduleAreaGetQuery, TGetStocksQuery, TParentChecklist, TParentChecklistCreate, TParentChecklistGetQuery, TScheduleTask, TScheduleTaskCreate, TScheduleTaskGetById, TScheduleTaskGetQuery, TScheduleTaskUpdate, TStock, TStockCreate, TStockCreateService, TSupply, TSupplyCreate, TSupplyGetById, TSupplyGetQuery, TSupplyUpdate, TUnit, TUnitCreate, TUnitGetQuery, TUnitUpdate, allowedCheckOutItemStatus, allowedChecklistStatus, allowedPeriods, allowedStatus, allowedTypes, areaChecklistSchema, areaSchema, buildCheckOutDecision, checkOutItemSchema, isCheckOutDecision, parentChecklistSchema, scheduleTaskSchema, stockSchema, supplySchema, unitSchema, useAreaChecklistController, useAreaChecklistRepo, useAreaChecklistService, useAreaController, useAreaExportService, useAreaRepo, useAreaService, useCheckOutItemController, useCheckOutItemRepository, useCheckOutItemService, useHygieneDashboardController, useHygieneDashboardRepository, useParentChecklistController, useParentChecklistRepo, useQRController, useQRService, useScheduleTaskController, useScheduleTaskRepository, useScheduleTaskService, useStockController, useStockRepository, useStockService, useSupplyController, useSupplyRepository, useUnitController, useUnitExportService, useUnitRepository, useUnitService };
package/dist/index.js CHANGED
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ CHECKOUT_DECIDABLE_STATUS: () => CHECKOUT_DECIDABLE_STATUS,
34
+ CHECKOUT_DECISIONS: () => CHECKOUT_DECISIONS,
33
35
  MArea: () => MArea,
34
36
  MAreaChecklist: () => MAreaChecklist,
35
37
  MCheckOutItem: () => MCheckOutItem,
@@ -45,7 +47,9 @@ __export(src_exports, {
45
47
  allowedTypes: () => allowedTypes,
46
48
  areaChecklistSchema: () => areaChecklistSchema,
47
49
  areaSchema: () => areaSchema,
50
+ buildCheckOutDecision: () => buildCheckOutDecision,
48
51
  checkOutItemSchema: () => checkOutItemSchema,
52
+ isCheckOutDecision: () => isCheckOutDecision,
49
53
  parentChecklistSchema: () => parentChecklistSchema,
50
54
  scheduleTaskSchema: () => scheduleTaskSchema,
51
55
  stockSchema: () => stockSchema,
@@ -5876,6 +5880,23 @@ function useStockController() {
5876
5880
  };
5877
5881
  }
5878
5882
 
5883
+ // src/utils/hygiene-checkout-decision.util.ts
5884
+ var CHECKOUT_DECISIONS = ["approve", "disapprove"];
5885
+ function isCheckOutDecision(value) {
5886
+ return CHECKOUT_DECISIONS.includes(value);
5887
+ }
5888
+ var CHECKOUT_DECIDABLE_STATUS = "pending";
5889
+ function buildCheckOutDecision(decision, remarks, now = (/* @__PURE__ */ new Date()).toISOString()) {
5890
+ const approve = decision === "approve";
5891
+ return {
5892
+ approve,
5893
+ reject: !approve,
5894
+ status: "completed",
5895
+ remarks: remarks ?? "",
5896
+ updatedAt: now
5897
+ };
5898
+ }
5899
+
5879
5900
  // src/models/hygiene-checkout-item.model.ts
5880
5901
  var import_joi14 = __toESM(require("joi"));
5881
5902
  var import_mongodb16 = require("mongodb");
@@ -5924,6 +5945,10 @@ function MCheckOutItem(value) {
5924
5945
  createdBy: value.createdBy,
5925
5946
  createdByName: value.createdByName,
5926
5947
  status: "pending",
5948
+ approve: false,
5949
+ reject: false,
5950
+ remarks: "",
5951
+ completedBy: "",
5927
5952
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5928
5953
  updatedAt: "",
5929
5954
  deletedAt: ""
@@ -6054,7 +6079,10 @@ function useCheckOutItemRepository() {
6054
6079
  profile: "$createdByDoc.profile",
6055
6080
  checkOutQty: "$qty",
6056
6081
  createdAt: 1,
6057
- status: 1
6082
+ status: 1,
6083
+ approve: 1,
6084
+ reject: 1,
6085
+ remarks: 1
6058
6086
  }
6059
6087
  },
6060
6088
  { $sort: { _id: -1 } },
@@ -6120,7 +6148,10 @@ function useCheckOutItemRepository() {
6120
6148
  status: 1,
6121
6149
  unitOfMeasurement: "$supplyDetails.unitOfMeasurement",
6122
6150
  stockQty: "$supplyQty",
6123
- attachment: 1
6151
+ attachment: 1,
6152
+ approve: 1,
6153
+ reject: 1,
6154
+ remarks: 1
6124
6155
  }
6125
6156
  }
6126
6157
  ],
@@ -6171,13 +6202,55 @@ function useCheckOutItemRepository() {
6171
6202
  throw error;
6172
6203
  }
6173
6204
  }
6205
+ async function decideCheckOutItem(_id, value, session) {
6206
+ try {
6207
+ _id = new import_mongodb17.ObjectId(_id);
6208
+ } catch (error) {
6209
+ throw new import_node_server_utils29.BadRequestError("Invalid check out item ID format.");
6210
+ }
6211
+ const updateValue = buildCheckOutDecision(
6212
+ value.approve === true ? "approve" : "disapprove",
6213
+ value.remarks
6214
+ );
6215
+ if (value.completedBy) {
6216
+ try {
6217
+ updateValue.completedBy = new import_mongodb17.ObjectId(value.completedBy);
6218
+ } catch (error) {
6219
+ throw new import_node_server_utils29.BadRequestError("Invalid user ID format.");
6220
+ }
6221
+ }
6222
+ try {
6223
+ const res = await collection.updateOne(
6224
+ { _id, status: CHECKOUT_DECIDABLE_STATUS },
6225
+ { $set: updateValue },
6226
+ { session }
6227
+ );
6228
+ if (res.matchedCount === 0) {
6229
+ throw new import_node_server_utils29.NotFoundError(
6230
+ "Check out item not found or is no longer pending approval."
6231
+ );
6232
+ }
6233
+ delNamespace().then(() => {
6234
+ import_node_server_utils29.logger.info(`Cache cleared for namespace: ${namespace_collection}`);
6235
+ }).catch((err) => {
6236
+ import_node_server_utils29.logger.error(
6237
+ `Failed to clear cache for namespace: ${namespace_collection}`,
6238
+ err
6239
+ );
6240
+ });
6241
+ return res.modifiedCount;
6242
+ } catch (error) {
6243
+ throw error;
6244
+ }
6245
+ }
6174
6246
  return {
6175
6247
  createIndex,
6176
6248
  createTextIndex,
6177
6249
  createCheckOutItem,
6178
6250
  getCheckOutItems,
6179
6251
  getCheckOutItemById,
6180
- completeCheckOutItem
6252
+ completeCheckOutItem,
6253
+ decideCheckOutItem
6181
6254
  };
6182
6255
  }
6183
6256
 
@@ -6292,7 +6365,8 @@ var import_core18 = require("@7365admin1/core");
6292
6365
  function useCheckOutItemController() {
6293
6366
  const {
6294
6367
  getCheckOutItems: _getCheckOutItems,
6295
- getCheckOutItemById: _getCheckOutItemById
6368
+ getCheckOutItemById: _getCheckOutItemById,
6369
+ decideCheckOutItem: _decideCheckOutItem
6296
6370
  } = useCheckOutItemRepository();
6297
6371
  const {
6298
6372
  createCheckOutItem: _createCheckOutItem,
@@ -6427,11 +6501,60 @@ function useCheckOutItemController() {
6427
6501
  return;
6428
6502
  }
6429
6503
  }
6504
+ async function decideCheckOutItem(req, res, next) {
6505
+ const cookies = req.headers.cookie ? req.headers.cookie.split(";").map((cookie) => cookie.trim().split("=")).reduce(
6506
+ (acc, [key, value]) => ({ ...acc, [key]: value }),
6507
+ {}
6508
+ ) : {};
6509
+ const completedBy = cookies["user"] || "";
6510
+ const decisionMap = {
6511
+ approve: { approve: true, reject: false },
6512
+ disapprove: { approve: false, reject: true }
6513
+ };
6514
+ const decision = req.params.decision;
6515
+ const decisionValues = decisionMap[decision] || {
6516
+ approve: void 0,
6517
+ reject: void 0
6518
+ };
6519
+ const payload = {
6520
+ ...req.params,
6521
+ ...req.body,
6522
+ ...decisionValues,
6523
+ completedBy
6524
+ };
6525
+ const validation = import_joi15.default.object({
6526
+ id: import_joi15.default.string().hex().required(),
6527
+ decision: import_joi15.default.string().valid("approve", "disapprove").required(),
6528
+ approve: import_joi15.default.boolean().required(),
6529
+ reject: import_joi15.default.boolean().required(),
6530
+ remarks: import_joi15.default.string().optional().allow("", null),
6531
+ completedBy: import_joi15.default.string().hex().required()
6532
+ });
6533
+ const { error } = validation.validate(payload);
6534
+ if (error) {
6535
+ import_node_server_utils31.logger.log({ level: "error", message: error.message });
6536
+ next(new import_node_server_utils31.BadRequestError(error.message));
6537
+ return;
6538
+ }
6539
+ try {
6540
+ const { id, decision: _d, ...value } = payload;
6541
+ await _decideCheckOutItem(id, value);
6542
+ res.json({
6543
+ message: `Check out item ${decision === "approve" ? "approved" : "disapproved"} successfully.`
6544
+ });
6545
+ return;
6546
+ } catch (error2) {
6547
+ import_node_server_utils31.logger.log({ level: "error", message: error2.message });
6548
+ next(error2);
6549
+ return;
6550
+ }
6551
+ }
6430
6552
  return {
6431
6553
  createCheckOutItem,
6432
6554
  createCheckOutItemByBatch,
6433
6555
  getCheckOutItems,
6434
- getCheckOutItemById
6556
+ getCheckOutItemById,
6557
+ decideCheckOutItem
6435
6558
  };
6436
6559
  }
6437
6560
 
@@ -7472,6 +7595,8 @@ function useQRController() {
7472
7595
  }
7473
7596
  // Annotate the CommonJS export names for ESM import in node:
7474
7597
  0 && (module.exports = {
7598
+ CHECKOUT_DECIDABLE_STATUS,
7599
+ CHECKOUT_DECISIONS,
7475
7600
  MArea,
7476
7601
  MAreaChecklist,
7477
7602
  MCheckOutItem,
@@ -7487,7 +7612,9 @@ function useQRController() {
7487
7612
  allowedTypes,
7488
7613
  areaChecklistSchema,
7489
7614
  areaSchema,
7615
+ buildCheckOutDecision,
7490
7616
  checkOutItemSchema,
7617
+ isCheckOutDecision,
7491
7618
  parentChecklistSchema,
7492
7619
  scheduleTaskSchema,
7493
7620
  stockSchema,