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

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).
@@ -0,0 +1,5 @@
1
+ ---
2
+ "@7365admin1/module-hygiene": minor
3
+ ---
4
+
5
+ Disapproving a hygiene check out item returns the drawn-down quantity to stock, in the same transaction as the decision.
package/dist/index.d.ts CHANGED
@@ -512,6 +512,51 @@ 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
+ /**
545
+ * Sequence a decision: the guarded update first, the stock return only if that
546
+ * update actually decided the row.
547
+ *
548
+ * Idempotency comes from the update's `{ _id, status: "pending" }` match, not
549
+ * from anything here. `decide` reports how many rows it changed (and the
550
+ * repository throws on a match of none), so a row someone already decided
551
+ * moves no stock. That is what stops a second disapprove crediting stock
552
+ * twice, and stops an approve after a disapprove deducting it again — the
553
+ * approve is refused outright, so it never reaches a stock call at all.
554
+ *
555
+ * Kept here rather than in the service so the ordering can be tested with
556
+ * fakes, the same way the rest of this file is tested without a database.
557
+ */
558
+ declare function applyCheckOutDecision(decision: TCheckOutDecision, decide: () => Promise<number>, returnStock: () => Promise<unknown>): Promise<number>;
559
+
515
560
  declare const allowedCheckOutItemStatus: string[];
516
561
  type TCheckOutItem = {
517
562
  _id?: ObjectId;
@@ -525,6 +570,10 @@ type TCheckOutItem = {
525
570
  createdBy: string | ObjectId;
526
571
  createdByName: string;
527
572
  status?: (typeof allowedCheckOutItemStatus)[number];
573
+ approve?: boolean;
574
+ reject?: boolean;
575
+ remarks?: string;
576
+ completedBy?: string | ObjectId;
528
577
  createdAt?: string;
529
578
  updatedAt?: string;
530
579
  deletedAt?: string;
@@ -534,12 +583,13 @@ type TCheckOutItemCreateService = Pick<TCheckOutItem, "site" | "serviceType" | "
534
583
  type TCheckOutItemCreateByBatchService = Pick<TCheckOutItem, "site" | "serviceType" | "createdBy"> & {
535
584
  items: Pick<TCheckOutItem, "supply" | "qty" | "attachment">[];
536
585
  };
586
+ type TCheckOutItemDecision = Pick<TCheckOutItem, "approve" | "reject" | "remarks" | "completedBy">;
537
587
  type TCheckOutItemGetQuery = {
538
588
  page?: number;
539
589
  limit?: number;
540
590
  search?: string;
541
591
  } & Pick<TCheckOutItem, "site" | "serviceType">;
542
- type TCheckOutItemGetById = Pick<TCheckOutItem, "_id" | "site" | "serviceType" | "supply" | "supplyName" | "supplyQty" | "qty" | "status"> & {
592
+ type TCheckOutItemGetById = Pick<TCheckOutItem, "_id" | "site" | "serviceType" | "supply" | "supplyName" | "supplyQty" | "qty" | "status" | "approve" | "reject" | "remarks"> & {
543
593
  unitOfMeasurement?: string;
544
594
  };
545
595
  declare const checkOutItemSchema: Joi.ObjectSchema<any>;
@@ -554,6 +604,10 @@ declare function MCheckOutItem(value: TCheckOutItemCreate): {
554
604
  createdBy: string | ObjectId;
555
605
  createdByName: string;
556
606
  status: string;
607
+ approve: boolean;
608
+ reject: boolean;
609
+ remarks: string;
610
+ completedBy: string;
557
611
  createdAt: string;
558
612
  updatedAt: string;
559
613
  deletedAt: string;
@@ -566,11 +620,13 @@ declare function useCheckOutItemRepository(): {
566
620
  getCheckOutItems: ({ page, limit, search, site, serviceType, }: TCheckOutItemGetQuery) => Promise<{}>;
567
621
  getCheckOutItemById: (_id: string | ObjectId, session?: ClientSession) => Promise<TCheckOutItemGetById>;
568
622
  completeCheckOutItem: (_id: string | ObjectId, session?: ClientSession) => Promise<number>;
623
+ decideCheckOutItem: (_id: string | ObjectId, value: TCheckOutItemDecision, session?: ClientSession) => Promise<number>;
569
624
  };
570
625
 
571
626
  declare function useCheckOutItemService(): {
572
627
  createCheckOutItem: (value: TCheckOutItemCreateService) => Promise<bson.ObjectId>;
573
628
  createCheckOutItemByBatch: (value: TCheckOutItemCreateByBatchService) => Promise<bson.ObjectId[]>;
629
+ decideCheckOutItem: (_id: string, value: TCheckOutItemDecision) => Promise<number>;
574
630
  };
575
631
 
576
632
  declare function useCheckOutItemController(): {
@@ -578,6 +634,7 @@ declare function useCheckOutItemController(): {
578
634
  createCheckOutItemByBatch: (req: Request, res: Response, next: NextFunction) => Promise<void>;
579
635
  getCheckOutItems: (req: Request, res: Response, next: NextFunction) => Promise<void>;
580
636
  getCheckOutItemById: (req: Request, res: Response, next: NextFunction) => Promise<void>;
637
+ decideCheckOutItem: (req: Request, res: Response, next: NextFunction) => Promise<void>;
581
638
  };
582
639
 
583
640
  type TScheduleTask = {
@@ -664,4 +721,4 @@ declare function useQRController(): {
664
721
  generateQR: (req: Request, res: Response, next: NextFunction) => Promise<void>;
665
722
  };
666
723
 
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 };
724
+ 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, applyCheckOutDecision, 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,
@@ -43,9 +45,12 @@ __export(src_exports, {
43
45
  allowedPeriods: () => allowedPeriods,
44
46
  allowedStatus: () => allowedStatus,
45
47
  allowedTypes: () => allowedTypes,
48
+ applyCheckOutDecision: () => applyCheckOutDecision,
46
49
  areaChecklistSchema: () => areaChecklistSchema,
47
50
  areaSchema: () => areaSchema,
51
+ buildCheckOutDecision: () => buildCheckOutDecision,
48
52
  checkOutItemSchema: () => checkOutItemSchema,
53
+ isCheckOutDecision: () => isCheckOutDecision,
49
54
  parentChecklistSchema: () => parentChecklistSchema,
50
55
  scheduleTaskSchema: () => scheduleTaskSchema,
51
56
  stockSchema: () => stockSchema,
@@ -5876,6 +5881,30 @@ function useStockController() {
5876
5881
  };
5877
5882
  }
5878
5883
 
5884
+ // src/utils/hygiene-checkout-decision.util.ts
5885
+ var CHECKOUT_DECISIONS = ["approve", "disapprove"];
5886
+ function isCheckOutDecision(value) {
5887
+ return CHECKOUT_DECISIONS.includes(value);
5888
+ }
5889
+ var CHECKOUT_DECIDABLE_STATUS = "pending";
5890
+ function buildCheckOutDecision(decision, remarks, now = (/* @__PURE__ */ new Date()).toISOString()) {
5891
+ const approve = decision === "approve";
5892
+ return {
5893
+ approve,
5894
+ reject: !approve,
5895
+ status: "completed",
5896
+ remarks: remarks ?? "",
5897
+ updatedAt: now
5898
+ };
5899
+ }
5900
+ async function applyCheckOutDecision(decision, decide, returnStock) {
5901
+ const decided = await decide();
5902
+ if (decided > 0 && decision === "disapprove") {
5903
+ await returnStock();
5904
+ }
5905
+ return decided;
5906
+ }
5907
+
5879
5908
  // src/models/hygiene-checkout-item.model.ts
5880
5909
  var import_joi14 = __toESM(require("joi"));
5881
5910
  var import_mongodb16 = require("mongodb");
@@ -5924,6 +5953,10 @@ function MCheckOutItem(value) {
5924
5953
  createdBy: value.createdBy,
5925
5954
  createdByName: value.createdByName,
5926
5955
  status: "pending",
5956
+ approve: false,
5957
+ reject: false,
5958
+ remarks: "",
5959
+ completedBy: "",
5927
5960
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5928
5961
  updatedAt: "",
5929
5962
  deletedAt: ""
@@ -6054,7 +6087,10 @@ function useCheckOutItemRepository() {
6054
6087
  profile: "$createdByDoc.profile",
6055
6088
  checkOutQty: "$qty",
6056
6089
  createdAt: 1,
6057
- status: 1
6090
+ status: 1,
6091
+ approve: 1,
6092
+ reject: 1,
6093
+ remarks: 1
6058
6094
  }
6059
6095
  },
6060
6096
  { $sort: { _id: -1 } },
@@ -6120,7 +6156,10 @@ function useCheckOutItemRepository() {
6120
6156
  status: 1,
6121
6157
  unitOfMeasurement: "$supplyDetails.unitOfMeasurement",
6122
6158
  stockQty: "$supplyQty",
6123
- attachment: 1
6159
+ attachment: 1,
6160
+ approve: 1,
6161
+ reject: 1,
6162
+ remarks: 1
6124
6163
  }
6125
6164
  }
6126
6165
  ],
@@ -6171,13 +6210,55 @@ function useCheckOutItemRepository() {
6171
6210
  throw error;
6172
6211
  }
6173
6212
  }
6213
+ async function decideCheckOutItem(_id, value, session) {
6214
+ try {
6215
+ _id = new import_mongodb17.ObjectId(_id);
6216
+ } catch (error) {
6217
+ throw new import_node_server_utils29.BadRequestError("Invalid check out item ID format.");
6218
+ }
6219
+ const updateValue = buildCheckOutDecision(
6220
+ value.approve === true ? "approve" : "disapprove",
6221
+ value.remarks
6222
+ );
6223
+ if (value.completedBy) {
6224
+ try {
6225
+ updateValue.completedBy = new import_mongodb17.ObjectId(value.completedBy);
6226
+ } catch (error) {
6227
+ throw new import_node_server_utils29.BadRequestError("Invalid user ID format.");
6228
+ }
6229
+ }
6230
+ try {
6231
+ const res = await collection.updateOne(
6232
+ { _id, status: CHECKOUT_DECIDABLE_STATUS },
6233
+ { $set: updateValue },
6234
+ { session }
6235
+ );
6236
+ if (res.matchedCount === 0) {
6237
+ throw new import_node_server_utils29.NotFoundError(
6238
+ "Check out item not found or is no longer pending approval."
6239
+ );
6240
+ }
6241
+ delNamespace().then(() => {
6242
+ import_node_server_utils29.logger.info(`Cache cleared for namespace: ${namespace_collection}`);
6243
+ }).catch((err) => {
6244
+ import_node_server_utils29.logger.error(
6245
+ `Failed to clear cache for namespace: ${namespace_collection}`,
6246
+ err
6247
+ );
6248
+ });
6249
+ return res.modifiedCount;
6250
+ } catch (error) {
6251
+ throw error;
6252
+ }
6253
+ }
6174
6254
  return {
6175
6255
  createIndex,
6176
6256
  createTextIndex,
6177
6257
  createCheckOutItem,
6178
6258
  getCheckOutItems,
6179
6259
  getCheckOutItemById,
6180
- completeCheckOutItem
6260
+ completeCheckOutItem,
6261
+ decideCheckOutItem
6181
6262
  };
6182
6263
  }
6183
6264
 
@@ -6188,7 +6269,8 @@ function useCheckOutItemService() {
6188
6269
  const {
6189
6270
  createCheckOutItem: _createCheckOutItem,
6190
6271
  getCheckOutItemById: _getCheckOutItemById,
6191
- completeCheckOutItem
6272
+ completeCheckOutItem,
6273
+ decideCheckOutItem: _decideCheckOutItem
6192
6274
  } = useCheckOutItemRepository();
6193
6275
  const { getSupplyById } = useSupplyRepository();
6194
6276
  const { getUserById } = (0, import_core17.useUserRepo)();
@@ -6279,9 +6361,41 @@ function useCheckOutItemService() {
6279
6361
  await session?.endSession();
6280
6362
  }
6281
6363
  }
6364
+ async function decideCheckOutItem(_id, value) {
6365
+ const session = import_node_server_utils30.useAtlas.getClient()?.startSession();
6366
+ try {
6367
+ session?.startTransaction();
6368
+ const checkOutItem = await _getCheckOutItemById(_id, session);
6369
+ if (!checkOutItem) {
6370
+ throw new import_node_server_utils30.BadRequestError("Check out item not found.");
6371
+ }
6372
+ const decided = await applyCheckOutDecision(
6373
+ value.approve === true ? "approve" : "disapprove",
6374
+ () => _decideCheckOutItem(_id, value, session),
6375
+ () => createStock(
6376
+ {
6377
+ site: checkOutItem.site.toString(),
6378
+ serviceType: checkOutItem.serviceType,
6379
+ supply: checkOutItem.supply.toString(),
6380
+ qty: checkOutItem.qty
6381
+ },
6382
+ false,
6383
+ session
6384
+ )
6385
+ );
6386
+ await session?.commitTransaction();
6387
+ return decided;
6388
+ } catch (error) {
6389
+ await session?.abortTransaction();
6390
+ throw error;
6391
+ } finally {
6392
+ await session?.endSession();
6393
+ }
6394
+ }
6282
6395
  return {
6283
6396
  createCheckOutItem,
6284
- createCheckOutItemByBatch
6397
+ createCheckOutItemByBatch,
6398
+ decideCheckOutItem
6285
6399
  };
6286
6400
  }
6287
6401
 
@@ -6296,7 +6410,8 @@ function useCheckOutItemController() {
6296
6410
  } = useCheckOutItemRepository();
6297
6411
  const {
6298
6412
  createCheckOutItem: _createCheckOutItem,
6299
- createCheckOutItemByBatch: _createCheckOutItemByBatch
6413
+ createCheckOutItemByBatch: _createCheckOutItemByBatch,
6414
+ decideCheckOutItem: _decideCheckOutItem
6300
6415
  } = useCheckOutItemService();
6301
6416
  async function createCheckOutItem(req, res, next) {
6302
6417
  const cookies = req.headers.cookie ? req.headers.cookie.split(";").map((cookie) => cookie.trim().split("=")).reduce(
@@ -6427,11 +6542,60 @@ function useCheckOutItemController() {
6427
6542
  return;
6428
6543
  }
6429
6544
  }
6545
+ async function decideCheckOutItem(req, res, next) {
6546
+ const cookies = req.headers.cookie ? req.headers.cookie.split(";").map((cookie) => cookie.trim().split("=")).reduce(
6547
+ (acc, [key, value]) => ({ ...acc, [key]: value }),
6548
+ {}
6549
+ ) : {};
6550
+ const completedBy = cookies["user"] || "";
6551
+ const decisionMap = {
6552
+ approve: { approve: true, reject: false },
6553
+ disapprove: { approve: false, reject: true }
6554
+ };
6555
+ const decision = req.params.decision;
6556
+ const decisionValues = decisionMap[decision] || {
6557
+ approve: void 0,
6558
+ reject: void 0
6559
+ };
6560
+ const payload = {
6561
+ ...req.params,
6562
+ ...req.body,
6563
+ ...decisionValues,
6564
+ completedBy
6565
+ };
6566
+ const validation = import_joi15.default.object({
6567
+ id: import_joi15.default.string().hex().required(),
6568
+ decision: import_joi15.default.string().valid("approve", "disapprove").required(),
6569
+ approve: import_joi15.default.boolean().required(),
6570
+ reject: import_joi15.default.boolean().required(),
6571
+ remarks: import_joi15.default.string().optional().allow("", null),
6572
+ completedBy: import_joi15.default.string().hex().required()
6573
+ });
6574
+ const { error } = validation.validate(payload);
6575
+ if (error) {
6576
+ import_node_server_utils31.logger.log({ level: "error", message: error.message });
6577
+ next(new import_node_server_utils31.BadRequestError(error.message));
6578
+ return;
6579
+ }
6580
+ try {
6581
+ const { id, decision: _d, ...value } = payload;
6582
+ await _decideCheckOutItem(id, value);
6583
+ res.json({
6584
+ message: `Check out item ${decision === "approve" ? "approved" : "disapproved"} successfully.`
6585
+ });
6586
+ return;
6587
+ } catch (error2) {
6588
+ import_node_server_utils31.logger.log({ level: "error", message: error2.message });
6589
+ next(error2);
6590
+ return;
6591
+ }
6592
+ }
6430
6593
  return {
6431
6594
  createCheckOutItem,
6432
6595
  createCheckOutItemByBatch,
6433
6596
  getCheckOutItems,
6434
- getCheckOutItemById
6597
+ getCheckOutItemById,
6598
+ decideCheckOutItem
6435
6599
  };
6436
6600
  }
6437
6601
 
@@ -7472,6 +7636,8 @@ function useQRController() {
7472
7636
  }
7473
7637
  // Annotate the CommonJS export names for ESM import in node:
7474
7638
  0 && (module.exports = {
7639
+ CHECKOUT_DECIDABLE_STATUS,
7640
+ CHECKOUT_DECISIONS,
7475
7641
  MArea,
7476
7642
  MAreaChecklist,
7477
7643
  MCheckOutItem,
@@ -7485,9 +7651,12 @@ function useQRController() {
7485
7651
  allowedPeriods,
7486
7652
  allowedStatus,
7487
7653
  allowedTypes,
7654
+ applyCheckOutDecision,
7488
7655
  areaChecklistSchema,
7489
7656
  areaSchema,
7657
+ buildCheckOutDecision,
7490
7658
  checkOutItemSchema,
7659
+ isCheckOutDecision,
7491
7660
  parentChecklistSchema,
7492
7661
  scheduleTaskSchema,
7493
7662
  stockSchema,