@7365admin1/core 3.29.0 → 3.30.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/core
2
2
 
3
+ ## 3.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fea767a: release changes in billing api, nfc api, etc.
8
+
3
9
  ## 3.29.0
4
10
 
5
11
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -4076,7 +4076,7 @@ declare function useBulletinBoardRepo(): {
4076
4076
  };
4077
4077
 
4078
4078
  declare function useBulletinBoardService(): {
4079
- add: (value: TBulletinBoard) => Promise<string>;
4079
+ add: (value: TBulletinBoard) => Promise<"Successfully added bulletin board." | undefined>;
4080
4080
  updateBulletinBoardById: (id: string | ObjectId, value: Partial<TBulletinBoard>) => Promise<string>;
4081
4081
  processExpiredBulletinBoards: () => Promise<void>;
4082
4082
  processUpcomingBulletinBoards: () => Promise<void>;
@@ -4149,7 +4149,6 @@ declare function MBillingItem(value: TBillingItem): {
4149
4149
  };
4150
4150
 
4151
4151
  declare function useSiteBillingItemRepo(): {
4152
- createTextIndex: () => Promise<string>;
4153
4152
  add: (value: TBillingItem, session?: ClientSession) => Promise<ObjectId>;
4154
4153
  getAll: ({ search, page, limit, sort, status, site, }: {
4155
4154
  search?: string | undefined;
@@ -4852,9 +4851,7 @@ declare function UseAccessManagementRepo(): {
4852
4851
  siteId: string;
4853
4852
  cardId: string;
4854
4853
  }) => Promise<{
4855
- status: string;
4856
- site: mongodb.WithId<bson.Document> | null;
4857
- user: mongodb.WithId<bson.Document> | null;
4854
+ history: mongodb.WithId<bson.Document>[];
4858
4855
  _id: ObjectId;
4859
4856
  } | null>;
4860
4857
  addQrTagRepo: (params: {
@@ -4906,8 +4903,13 @@ declare function UseAccessManagementRepo(): {
4906
4903
  purpose: string;
4907
4904
  }) => Promise<string>;
4908
4905
  checkoutVisitorRepo: (params: {
4909
- userId: string;
4910
- }) => Promise<mongodb.UpdateResult<bson.Document>>;
4906
+ cards: {
4907
+ userId: string;
4908
+ cardId: string;
4909
+ status: string;
4910
+ remarks: string;
4911
+ }[];
4912
+ }) => Promise<mongodb.BulkWriteResult>;
4911
4913
  getBlockLevelAndUnitListRepo: (params: {
4912
4914
  site: string;
4913
4915
  }) => Promise<bson.Document[]>;
@@ -6060,7 +6062,7 @@ declare function useNfcPatrolLogRepo(): {
6060
6062
  items: any[];
6061
6063
  pages: number;
6062
6064
  pageRange: string;
6063
- } | TNfcPatrolLog>;
6065
+ }>;
6064
6066
  getById: (id: string | ObjectId, session?: ClientSession) => Promise<mongodb.WithId<bson.Document> | null>;
6065
6067
  updateCheckpoint: (id: string | ObjectId, checkpointIndex: number, payload: any, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
6066
6068
  };
@@ -6070,6 +6072,7 @@ declare function useNfcPatrolLogService(): {
6070
6072
  site: string;
6071
6073
  routeId: string;
6072
6074
  date: string;
6075
+ startTime: string;
6073
6076
  createdBy?: string;
6074
6077
  }) => Promise<string>;
6075
6078
  completeCheckpoint: (logId: string, payload: {
package/dist/index.js CHANGED
@@ -34660,6 +34660,10 @@ var PushTokenRepo = class {
34660
34660
  const docs = await this.collection().find({ userId: { $in: userIds }, appSlug: { $eq: appSlug } }).toArray();
34661
34661
  return docs.map((d) => d.token);
34662
34662
  }
34663
+ static async findUserIdsBySiteIdAndAppSlug(siteId, appSlug) {
34664
+ const docs = await this.collection().find({ siteId, appSlug }).toArray();
34665
+ return docs.map((d) => d.userId);
34666
+ }
34663
34667
  };
34664
34668
 
34665
34669
  // src/services/notification.service.ts
@@ -34805,6 +34809,24 @@ var NotificationService = class {
34805
34809
  "iservice365-ma-mobile-app"
34806
34810
  );
34807
34811
  }
34812
+ static async bulletinBoardCreatedForServiceApps(payload, appSlug) {
34813
+ await send(
34814
+ toStringArray(payload.to),
34815
+ "Bulletin Board",
34816
+ `There is new ${payload.status} bulletin board.`,
34817
+ {
34818
+ bulletinId: payload.bulletinId.toString(),
34819
+ status: payload.status,
34820
+ module: "bulletinBoard",
34821
+ screen: "/(user)/bulletinInfo",
34822
+ params: {
34823
+ id: payload.bulletinId.toString()
34824
+ }
34825
+ },
34826
+ false,
34827
+ appSlug
34828
+ );
34829
+ }
34808
34830
  };
34809
34831
 
34810
34832
  // src/utils/valid-values.ts
@@ -42709,6 +42731,101 @@ function useBulletinBoardService() {
42709
42731
  status: bulletinBoardStatus
42710
42732
  });
42711
42733
  }
42734
+ if (recipients?.find((item) => item === "mechanical_electrical")) {
42735
+ if (!siteId)
42736
+ return;
42737
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42738
+ siteId.toString(),
42739
+ "iservice365-m-and-e-mobile-app"
42740
+ );
42741
+ if (users2.length <= 0)
42742
+ return;
42743
+ NotificationService.bulletinBoardCreatedForServiceApps(
42744
+ {
42745
+ to: users2,
42746
+ bulletinId: bulletinBoardId,
42747
+ subject: "Bulletin Board",
42748
+ status: bulletinBoardStatus
42749
+ },
42750
+ "iservice365-m-and-e-mobile-app"
42751
+ );
42752
+ }
42753
+ if (recipients?.find((item) => item === "pest_control_services")) {
42754
+ if (!siteId)
42755
+ return;
42756
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42757
+ siteId.toString(),
42758
+ "iservice365-pest-control-mobile-app"
42759
+ );
42760
+ if (users2.length <= 0)
42761
+ return;
42762
+ NotificationService.bulletinBoardCreatedForServiceApps(
42763
+ {
42764
+ to: users2,
42765
+ bulletinId: bulletinBoardId,
42766
+ subject: "Bulletin Board",
42767
+ status: bulletinBoardStatus
42768
+ },
42769
+ "iservice365-pest-control-mobile-app"
42770
+ );
42771
+ }
42772
+ if (recipients?.find((item) => item === "pool_maintenance_services")) {
42773
+ if (!siteId)
42774
+ return;
42775
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42776
+ siteId.toString(),
42777
+ "iservice365-pool-maintenance-mobile-app"
42778
+ );
42779
+ if (users2.length <= 0)
42780
+ return;
42781
+ NotificationService.bulletinBoardCreatedForServiceApps(
42782
+ {
42783
+ to: users2,
42784
+ bulletinId: bulletinBoardId,
42785
+ subject: "Bulletin Board",
42786
+ status: bulletinBoardStatus
42787
+ },
42788
+ "iservice365-pool-maintenance-mobile-app"
42789
+ );
42790
+ }
42791
+ if (recipients?.find((item) => item === "landscaping_services")) {
42792
+ if (!siteId)
42793
+ return;
42794
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42795
+ siteId.toString(),
42796
+ "iservice365-landscaping-mobile-app"
42797
+ );
42798
+ if (users2.length <= 0)
42799
+ return;
42800
+ NotificationService.bulletinBoardCreatedForServiceApps(
42801
+ {
42802
+ to: users2,
42803
+ bulletinId: bulletinBoardId,
42804
+ subject: "Bulletin Board",
42805
+ status: bulletinBoardStatus
42806
+ },
42807
+ "iservice365-landscaping-mobile-app"
42808
+ );
42809
+ }
42810
+ if (recipients?.find((item) => item === "cleaning_services")) {
42811
+ if (!siteId)
42812
+ return;
42813
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42814
+ siteId.toString(),
42815
+ "iservice365-hygiene-mobile-app"
42816
+ );
42817
+ if (users2.length <= 0)
42818
+ return;
42819
+ NotificationService.bulletinBoardCreatedForServiceApps(
42820
+ {
42821
+ to: users2,
42822
+ bulletinId: bulletinBoardId,
42823
+ subject: "Bulletin Board",
42824
+ status: bulletinBoardStatus
42825
+ },
42826
+ "iservice365-hygiene-mobile-app"
42827
+ );
42828
+ }
42712
42829
  await session?.commitTransaction();
42713
42830
  return "Successfully added bulletin board.";
42714
42831
  } catch (error) {
@@ -43129,7 +43246,16 @@ function useSiteBillingItemRepo() {
43129
43246
  const { delNamespace, setCache, getCache } = (0, import_node_server_utils144.useCache)(namespace_collection);
43130
43247
  async function createIndexes() {
43131
43248
  try {
43132
- await collection.createIndexes([{ key: { site: 1, status: 1 } }]);
43249
+ await collection.createIndexes([
43250
+ // Serves the API list path: getAll filters by { site, status }.
43251
+ { key: { site: 1, status: 1 } },
43252
+ // Serves the cron path: processBilling() queries { status: "active" }
43253
+ // with no site, so the site-leading index above cannot be used.
43254
+ // A status-leading index avoids a full collection scan each run;
43255
+ // billingType is included so the recurring/non-recurring split can
43256
+ // be pushed down to the query later without another index.
43257
+ { key: { status: 1, billingType: 1 } }
43258
+ ]);
43133
43259
  return `Successfully created indexes for ${namespace_collection}.`;
43134
43260
  } catch (error) {
43135
43261
  import_node_server_utils144.logger.log({
@@ -43141,22 +43267,6 @@ function useSiteBillingItemRepo() {
43141
43267
  );
43142
43268
  }
43143
43269
  }
43144
- async function createTextIndex() {
43145
- try {
43146
- await collection.createIndex({
43147
- name: "text"
43148
- });
43149
- return "Successfully created text index on site billing item.";
43150
- } catch (error) {
43151
- import_node_server_utils144.logger.log({
43152
- level: "error",
43153
- message: error.message
43154
- });
43155
- throw new import_node_server_utils144.InternalServerError(
43156
- "Failed to create text index on site billing item."
43157
- );
43158
- }
43159
- }
43160
43270
  async function add(value, session) {
43161
43271
  try {
43162
43272
  value = MBillingItem(value);
@@ -43355,7 +43465,6 @@ function useSiteBillingItemRepo() {
43355
43465
  });
43356
43466
  }
43357
43467
  return {
43358
- createTextIndex,
43359
43468
  add,
43360
43469
  getAll,
43361
43470
  getById,
@@ -47019,7 +47128,8 @@ function UseAccessManagementRepo() {
47019
47128
  type: 1,
47020
47129
  cardNo: 1,
47021
47130
  isActivated: 1,
47022
- replacementStatus: 1
47131
+ replacementStatus: 1,
47132
+ damage: 1
47023
47133
  }
47024
47134
  }
47025
47135
  ],
@@ -48209,26 +48319,20 @@ function UseAccessManagementRepo() {
48209
48319
  requestDate: 1,
48210
48320
  createdAt: 1,
48211
48321
  updatedAt: 1,
48212
- userCred: 1
48322
+ userCred: 1,
48323
+ damage: 1
48213
48324
  }
48214
48325
  }
48215
48326
  );
48216
48327
  if (!card)
48217
48328
  return null;
48218
- const site = await collectionName("sites").findOne(
48219
- { _id: card.site },
48220
- { projection: { name: 1, status: 1 } }
48221
- );
48222
- const user = card.userId ? await collectionName("users").findOne(
48223
- { _id: card.userId },
48224
- { projection: { name: 1, email: 1 } }
48225
- ) : null;
48226
- const status = card.userId === null && card.isActivated === true ? "available" : card.userId !== null && card.isActivated === true ? "assigned" : card.isActivated === false && card.replacementStatus !== null ? "replaced" : "deleted";
48329
+ const history = await collectionName("access-cards-history").find({ cardId: convertedCardId }).toArray();
48227
48330
  return {
48228
48331
  ...card,
48229
- status,
48230
- site,
48231
- user
48332
+ // status,
48333
+ // site,
48334
+ // user,
48335
+ history
48232
48336
  };
48233
48337
  } catch (error) {
48234
48338
  throw new Error(error.message);
@@ -48548,6 +48652,16 @@ function UseAccessManagementRepo() {
48548
48652
  const updatedVisitor = result?._id;
48549
48653
  const assignCards = cards.map((card) => card._id);
48550
48654
  if (assignCards.length > 0) {
48655
+ const historyRecords = assignCards.map((cardId) => ({
48656
+ cardId: new import_mongodb97.ObjectId(cardId),
48657
+ userId: updatedVisitor,
48658
+ ...params.user,
48659
+ createdAt: /* @__PURE__ */ new Date()
48660
+ }));
48661
+ await collectionName("access-cards-history").insertMany(
48662
+ historyRecords,
48663
+ { session }
48664
+ );
48551
48665
  const bulkOps = assignCards.map((cardId) => {
48552
48666
  const updateFields = {
48553
48667
  staffNo: `STAFF-${cardId.toString().slice(-10)}`,
@@ -48662,11 +48776,35 @@ function UseAccessManagementRepo() {
48662
48776
  const session = import_node_server_utils158.useAtlas.getClient()?.startSession();
48663
48777
  try {
48664
48778
  await session?.startTransaction();
48665
- const userId = new import_mongodb97.ObjectId(params.userId);
48666
- const result = await collection().updateMany(
48667
- { userId },
48668
- { $set: { userId: null, status: "Available", updatedAt: /* @__PURE__ */ new Date() } }
48669
- );
48779
+ const { cards } = params;
48780
+ const isDamage = (status) => status.toLocaleLowerCase() === "lost" || status.toLocaleLowerCase() === "damage";
48781
+ const bulkOps = cards.map((card) => ({
48782
+ updateOne: {
48783
+ filter: { _id: new import_mongodb97.ObjectId(card.cardId) },
48784
+ update: {
48785
+ $set: {
48786
+ userId: null,
48787
+ status: card.status,
48788
+ remarks: card.remarks,
48789
+ damage: isDamage(card.status),
48790
+ updatedAt: /* @__PURE__ */ new Date(),
48791
+ userCred: null
48792
+ }
48793
+ }
48794
+ }
48795
+ }));
48796
+ const result = await collection().bulkWrite(bulkOps, { session });
48797
+ const historyRecords = cards.map((card) => ({
48798
+ cardId: new import_mongodb97.ObjectId(card.cardId),
48799
+ userId: new import_mongodb97.ObjectId(card.userId),
48800
+ status: card.status,
48801
+ remarks: card.remarks,
48802
+ damage: isDamage(card.status),
48803
+ createdAt: /* @__PURE__ */ new Date()
48804
+ }));
48805
+ await collectionName("access-cards-history").insertMany(historyRecords, {
48806
+ session
48807
+ });
48670
48808
  await session?.commitTransaction();
48671
48809
  return result;
48672
48810
  } catch (error) {
@@ -50629,15 +50767,22 @@ function useAccessManagementController() {
50629
50767
  };
50630
50768
  const checkoutVisitor = async (req, res) => {
50631
50769
  try {
50632
- const { userId } = req.body;
50770
+ const { cards } = req.body;
50633
50771
  const schema2 = import_joi87.default.object({
50634
- userId: import_joi87.default.string().hex().length(24).required()
50772
+ cards: import_joi87.default.array().items(
50773
+ import_joi87.default.object({
50774
+ cardId: import_joi87.default.string().hex().length(24).required(),
50775
+ userId: import_joi87.default.string().hex().length(24).required(),
50776
+ status: import_joi87.default.string().required(),
50777
+ remarks: import_joi87.default.string().allow("").required()
50778
+ })
50779
+ ).min(1).required()
50635
50780
  });
50636
- const { error } = schema2.validate({ userId });
50781
+ const { error } = schema2.validate({ cards });
50637
50782
  if (error) {
50638
50783
  return res.status(400).json({ message: error.message });
50639
50784
  }
50640
- const result = await checkoutVisitorSvc({ userId });
50785
+ const result = await checkoutVisitorSvc({ cards });
50641
50786
  return res.status(200).json({ message: "Success", data: result });
50642
50787
  } catch (error) {
50643
50788
  return res.status(500).json({
@@ -57957,18 +58102,10 @@ function useNfcPatrolLogRepo() {
57957
58102
  }
57958
58103
  }
57959
58104
  async function add(value, session) {
57960
- try {
57961
- value = MNfcPatrolLog(value);
57962
- const res = await collection.insertOne(value, { session });
57963
- delCachedData();
57964
- return res.insertedId;
57965
- } catch (error) {
57966
- const isDuplicated = error.message.includes("duplicate");
57967
- if (isDuplicated) {
57968
- throw new import_node_server_utils198.BadRequestError("NFC Patrol Log already exists.");
57969
- }
57970
- throw error;
57971
- }
58105
+ value = MNfcPatrolLog(value);
58106
+ const res = await collection.insertOne(value, { session });
58107
+ delCachedData();
58108
+ return res.insertedId;
57972
58109
  }
57973
58110
  async function getAllBySite({
57974
58111
  page = 1,
@@ -58099,7 +58236,8 @@ function useNfcPatrolLogService() {
58099
58236
  const {
58100
58237
  add: _add,
58101
58238
  getById,
58102
- updateCheckpoint
58239
+ updateCheckpoint,
58240
+ getAllBySite: _getAllBySite
58103
58241
  } = useNfcPatrolLogRepo();
58104
58242
  const routeRepo = useNfcPatrolRouteRepo();
58105
58243
  const tagRepo = useNfcPatrolTagRepo();
@@ -58114,9 +58252,30 @@ function useNfcPatrolLogService() {
58114
58252
  if (!route.startTimes?.length) {
58115
58253
  throw new import_node_server_utils199.BadRequestError("Route does not have any start time.");
58116
58254
  }
58255
+ if (!route.startTimes.includes(value.startTime)) {
58256
+ throw new import_node_server_utils199.BadRequestError("Invalid route start time.");
58257
+ }
58258
+ const existedLog = await _getAllBySite(
58259
+ {
58260
+ site: value.site,
58261
+ page: 1,
58262
+ limit: 1,
58263
+ date: value.date,
58264
+ route: {
58265
+ _id: route._id.toString(),
58266
+ startTime: value.startTime
58267
+ }
58268
+ },
58269
+ session
58270
+ );
58271
+ if (existedLog.items.length > 0) {
58272
+ throw new import_node_server_utils199.BadRequestError(
58273
+ `Patrol log for ${value.startTime} already exists on ${value.date}.`
58274
+ );
58275
+ }
58117
58276
  const checkPoints = [];
58118
58277
  let currentTime = /* @__PURE__ */ new Date(
58119
- `${value.date}T${route.startTimes[0]}:00`
58278
+ `${value.date}T${value.startTime}:00`
58120
58279
  );
58121
58280
  for (const checkPoint of route.checkPoints) {
58122
58281
  const tag = await tagRepo.getById(checkPoint.nfcTag_id, session);
@@ -58150,7 +58309,7 @@ function useNfcPatrolLogService() {
58150
58309
  _id: route._id.toString(),
58151
58310
  name: route.name,
58152
58311
  tolerance: Number(route.tolerance),
58153
- startTime: route.startTimes[0],
58312
+ startTime: value.startTime,
58154
58313
  endTime: currentTime.toISOString().substring(11, 16)
58155
58314
  },
58156
58315
  date: value.date,
@@ -58163,7 +58322,6 @@ function useNfcPatrolLogService() {
58163
58322
  await session?.commitTransaction();
58164
58323
  return "Successfully added NFC Patrol Log.";
58165
58324
  } catch (error) {
58166
- console.error("Add NFC Patrol Log Error:", error);
58167
58325
  await session?.abortTransaction();
58168
58326
  throw error;
58169
58327
  } finally {
@@ -70471,6 +70629,17 @@ function formatHidResponse(data) {
70471
70629
  function sanitizeHidPath(path5) {
70472
70630
  return path5.replace(/([?&]session=)[^&]+/i, "$1<redacted>");
70473
70631
  }
70632
+ function normalizeBaseUrl(value) {
70633
+ return String(value || "").replace(/\/+$/, "");
70634
+ }
70635
+ function getRelayAuth() {
70636
+ const username = process.env.HID_AMICO_RELAY_USERNAME;
70637
+ const password = process.env.HID_AMICO_RELAY_PASSWORD;
70638
+ if (!username || !password) {
70639
+ return void 0;
70640
+ }
70641
+ return { username, password };
70642
+ }
70474
70643
  function toHidRequestError(error, path5) {
70475
70644
  if (import_axios4.default.isAxiosError(error)) {
70476
70645
  const status = error.response?.status;
@@ -70510,10 +70679,12 @@ var HidAmicoClient = class {
70510
70679
  constructor(reader) {
70511
70680
  this.reader = reader;
70512
70681
  this.session = "";
70682
+ const relayAuth = getRelayAuth();
70513
70683
  this.http = import_axios4.default.create({
70514
- baseURL: reader.baseUrl.replace(/\/+$/, ""),
70684
+ baseURL: normalizeBaseUrl(reader.baseUrl),
70515
70685
  timeout: 15e3,
70516
- headers: { "Content-Type": "application/json" }
70686
+ headers: { "Content-Type": "application/json" },
70687
+ ...relayAuth ? { auth: relayAuth } : {}
70517
70688
  });
70518
70689
  }
70519
70690
  async post(path5, body) {
@@ -70746,7 +70917,12 @@ function useHidAmicoService() {
70746
70917
  try {
70747
70918
  await client.login();
70748
70919
  const valid = await client.sessionIsValid();
70749
- await repo.updateById(id, { lastSeenAt: /* @__PURE__ */ new Date(), lastSyncMessage: "Connection test succeeded." });
70920
+ await repo.updateById(id, {
70921
+ status: "active",
70922
+ lastSeenAt: /* @__PURE__ */ new Date(),
70923
+ lastSyncStatus: "completed",
70924
+ lastSyncMessage: "Connection test succeeded."
70925
+ });
70750
70926
  await repo.addEvent({ reader: id, site: reader.site, type: "test", payload: { valid } });
70751
70927
  return { valid, message: "HID Amico connection test succeeded." };
70752
70928
  } catch (error) {
@@ -70776,6 +70952,7 @@ function useHidAmicoService() {
70776
70952
  }
70777
70953
  const message = batches.length ? `Synced ${batches.length} HID object batch(es).` : "HID reader session verified. No sync payload was supplied.";
70778
70954
  await repo.updateById(id, {
70955
+ status: "active",
70779
70956
  lastSeenAt: /* @__PURE__ */ new Date(),
70780
70957
  lastSyncAt: /* @__PURE__ */ new Date(),
70781
70958
  lastSyncStatus: "completed",