@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/dist/index.mjs CHANGED
@@ -34503,6 +34503,10 @@ var PushTokenRepo = class {
34503
34503
  const docs = await this.collection().find({ userId: { $in: userIds }, appSlug: { $eq: appSlug } }).toArray();
34504
34504
  return docs.map((d) => d.token);
34505
34505
  }
34506
+ static async findUserIdsBySiteIdAndAppSlug(siteId, appSlug) {
34507
+ const docs = await this.collection().find({ siteId, appSlug }).toArray();
34508
+ return docs.map((d) => d.userId);
34509
+ }
34506
34510
  };
34507
34511
 
34508
34512
  // src/services/notification.service.ts
@@ -34648,6 +34652,24 @@ var NotificationService = class {
34648
34652
  "iservice365-ma-mobile-app"
34649
34653
  );
34650
34654
  }
34655
+ static async bulletinBoardCreatedForServiceApps(payload, appSlug) {
34656
+ await send(
34657
+ toStringArray(payload.to),
34658
+ "Bulletin Board",
34659
+ `There is new ${payload.status} bulletin board.`,
34660
+ {
34661
+ bulletinId: payload.bulletinId.toString(),
34662
+ status: payload.status,
34663
+ module: "bulletinBoard",
34664
+ screen: "/(user)/bulletinInfo",
34665
+ params: {
34666
+ id: payload.bulletinId.toString()
34667
+ }
34668
+ },
34669
+ false,
34670
+ appSlug
34671
+ );
34672
+ }
34651
34673
  };
34652
34674
 
34653
34675
  // src/utils/valid-values.ts
@@ -42658,6 +42680,101 @@ function useBulletinBoardService() {
42658
42680
  status: bulletinBoardStatus
42659
42681
  });
42660
42682
  }
42683
+ if (recipients?.find((item) => item === "mechanical_electrical")) {
42684
+ if (!siteId)
42685
+ return;
42686
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42687
+ siteId.toString(),
42688
+ "iservice365-m-and-e-mobile-app"
42689
+ );
42690
+ if (users2.length <= 0)
42691
+ return;
42692
+ NotificationService.bulletinBoardCreatedForServiceApps(
42693
+ {
42694
+ to: users2,
42695
+ bulletinId: bulletinBoardId,
42696
+ subject: "Bulletin Board",
42697
+ status: bulletinBoardStatus
42698
+ },
42699
+ "iservice365-m-and-e-mobile-app"
42700
+ );
42701
+ }
42702
+ if (recipients?.find((item) => item === "pest_control_services")) {
42703
+ if (!siteId)
42704
+ return;
42705
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42706
+ siteId.toString(),
42707
+ "iservice365-pest-control-mobile-app"
42708
+ );
42709
+ if (users2.length <= 0)
42710
+ return;
42711
+ NotificationService.bulletinBoardCreatedForServiceApps(
42712
+ {
42713
+ to: users2,
42714
+ bulletinId: bulletinBoardId,
42715
+ subject: "Bulletin Board",
42716
+ status: bulletinBoardStatus
42717
+ },
42718
+ "iservice365-pest-control-mobile-app"
42719
+ );
42720
+ }
42721
+ if (recipients?.find((item) => item === "pool_maintenance_services")) {
42722
+ if (!siteId)
42723
+ return;
42724
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42725
+ siteId.toString(),
42726
+ "iservice365-pool-maintenance-mobile-app"
42727
+ );
42728
+ if (users2.length <= 0)
42729
+ return;
42730
+ NotificationService.bulletinBoardCreatedForServiceApps(
42731
+ {
42732
+ to: users2,
42733
+ bulletinId: bulletinBoardId,
42734
+ subject: "Bulletin Board",
42735
+ status: bulletinBoardStatus
42736
+ },
42737
+ "iservice365-pool-maintenance-mobile-app"
42738
+ );
42739
+ }
42740
+ if (recipients?.find((item) => item === "landscaping_services")) {
42741
+ if (!siteId)
42742
+ return;
42743
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42744
+ siteId.toString(),
42745
+ "iservice365-landscaping-mobile-app"
42746
+ );
42747
+ if (users2.length <= 0)
42748
+ return;
42749
+ NotificationService.bulletinBoardCreatedForServiceApps(
42750
+ {
42751
+ to: users2,
42752
+ bulletinId: bulletinBoardId,
42753
+ subject: "Bulletin Board",
42754
+ status: bulletinBoardStatus
42755
+ },
42756
+ "iservice365-landscaping-mobile-app"
42757
+ );
42758
+ }
42759
+ if (recipients?.find((item) => item === "cleaning_services")) {
42760
+ if (!siteId)
42761
+ return;
42762
+ const users2 = await PushTokenRepo.findUserIdsBySiteIdAndAppSlug(
42763
+ siteId.toString(),
42764
+ "iservice365-hygiene-mobile-app"
42765
+ );
42766
+ if (users2.length <= 0)
42767
+ return;
42768
+ NotificationService.bulletinBoardCreatedForServiceApps(
42769
+ {
42770
+ to: users2,
42771
+ bulletinId: bulletinBoardId,
42772
+ subject: "Bulletin Board",
42773
+ status: bulletinBoardStatus
42774
+ },
42775
+ "iservice365-hygiene-mobile-app"
42776
+ );
42777
+ }
42661
42778
  await session?.commitTransaction();
42662
42779
  return "Successfully added bulletin board.";
42663
42780
  } catch (error) {
@@ -43087,7 +43204,16 @@ function useSiteBillingItemRepo() {
43087
43204
  const { delNamespace, setCache, getCache } = useCache43(namespace_collection);
43088
43205
  async function createIndexes() {
43089
43206
  try {
43090
- await collection.createIndexes([{ key: { site: 1, status: 1 } }]);
43207
+ await collection.createIndexes([
43208
+ // Serves the API list path: getAll filters by { site, status }.
43209
+ { key: { site: 1, status: 1 } },
43210
+ // Serves the cron path: processBilling() queries { status: "active" }
43211
+ // with no site, so the site-leading index above cannot be used.
43212
+ // A status-leading index avoids a full collection scan each run;
43213
+ // billingType is included so the recurring/non-recurring split can
43214
+ // be pushed down to the query later without another index.
43215
+ { key: { status: 1, billingType: 1 } }
43216
+ ]);
43091
43217
  return `Successfully created indexes for ${namespace_collection}.`;
43092
43218
  } catch (error) {
43093
43219
  logger111.log({
@@ -43099,22 +43225,6 @@ function useSiteBillingItemRepo() {
43099
43225
  );
43100
43226
  }
43101
43227
  }
43102
- async function createTextIndex() {
43103
- try {
43104
- await collection.createIndex({
43105
- name: "text"
43106
- });
43107
- return "Successfully created text index on site billing item.";
43108
- } catch (error) {
43109
- logger111.log({
43110
- level: "error",
43111
- message: error.message
43112
- });
43113
- throw new InternalServerError44(
43114
- "Failed to create text index on site billing item."
43115
- );
43116
- }
43117
- }
43118
43228
  async function add(value, session) {
43119
43229
  try {
43120
43230
  value = MBillingItem(value);
@@ -43313,7 +43423,6 @@ function useSiteBillingItemRepo() {
43313
43423
  });
43314
43424
  }
43315
43425
  return {
43316
- createTextIndex,
43317
43426
  add,
43318
43427
  getAll,
43319
43428
  getById,
@@ -47020,7 +47129,8 @@ function UseAccessManagementRepo() {
47020
47129
  type: 1,
47021
47130
  cardNo: 1,
47022
47131
  isActivated: 1,
47023
- replacementStatus: 1
47132
+ replacementStatus: 1,
47133
+ damage: 1
47024
47134
  }
47025
47135
  }
47026
47136
  ],
@@ -48210,26 +48320,20 @@ function UseAccessManagementRepo() {
48210
48320
  requestDate: 1,
48211
48321
  createdAt: 1,
48212
48322
  updatedAt: 1,
48213
- userCred: 1
48323
+ userCred: 1,
48324
+ damage: 1
48214
48325
  }
48215
48326
  }
48216
48327
  );
48217
48328
  if (!card)
48218
48329
  return null;
48219
- const site = await collectionName("sites").findOne(
48220
- { _id: card.site },
48221
- { projection: { name: 1, status: 1 } }
48222
- );
48223
- const user = card.userId ? await collectionName("users").findOne(
48224
- { _id: card.userId },
48225
- { projection: { name: 1, email: 1 } }
48226
- ) : null;
48227
- const status = card.userId === null && card.isActivated === true ? "available" : card.userId !== null && card.isActivated === true ? "assigned" : card.isActivated === false && card.replacementStatus !== null ? "replaced" : "deleted";
48330
+ const history = await collectionName("access-cards-history").find({ cardId: convertedCardId }).toArray();
48228
48331
  return {
48229
48332
  ...card,
48230
- status,
48231
- site,
48232
- user
48333
+ // status,
48334
+ // site,
48335
+ // user,
48336
+ history
48233
48337
  };
48234
48338
  } catch (error) {
48235
48339
  throw new Error(error.message);
@@ -48549,6 +48653,16 @@ function UseAccessManagementRepo() {
48549
48653
  const updatedVisitor = result?._id;
48550
48654
  const assignCards = cards.map((card) => card._id);
48551
48655
  if (assignCards.length > 0) {
48656
+ const historyRecords = assignCards.map((cardId) => ({
48657
+ cardId: new ObjectId97(cardId),
48658
+ userId: updatedVisitor,
48659
+ ...params.user,
48660
+ createdAt: /* @__PURE__ */ new Date()
48661
+ }));
48662
+ await collectionName("access-cards-history").insertMany(
48663
+ historyRecords,
48664
+ { session }
48665
+ );
48552
48666
  const bulkOps = assignCards.map((cardId) => {
48553
48667
  const updateFields = {
48554
48668
  staffNo: `STAFF-${cardId.toString().slice(-10)}`,
@@ -48663,11 +48777,35 @@ function UseAccessManagementRepo() {
48663
48777
  const session = useAtlas78.getClient()?.startSession();
48664
48778
  try {
48665
48779
  await session?.startTransaction();
48666
- const userId = new ObjectId97(params.userId);
48667
- const result = await collection().updateMany(
48668
- { userId },
48669
- { $set: { userId: null, status: "Available", updatedAt: /* @__PURE__ */ new Date() } }
48670
- );
48780
+ const { cards } = params;
48781
+ const isDamage = (status) => status.toLocaleLowerCase() === "lost" || status.toLocaleLowerCase() === "damage";
48782
+ const bulkOps = cards.map((card) => ({
48783
+ updateOne: {
48784
+ filter: { _id: new ObjectId97(card.cardId) },
48785
+ update: {
48786
+ $set: {
48787
+ userId: null,
48788
+ status: card.status,
48789
+ remarks: card.remarks,
48790
+ damage: isDamage(card.status),
48791
+ updatedAt: /* @__PURE__ */ new Date(),
48792
+ userCred: null
48793
+ }
48794
+ }
48795
+ }
48796
+ }));
48797
+ const result = await collection().bulkWrite(bulkOps, { session });
48798
+ const historyRecords = cards.map((card) => ({
48799
+ cardId: new ObjectId97(card.cardId),
48800
+ userId: new ObjectId97(card.userId),
48801
+ status: card.status,
48802
+ remarks: card.remarks,
48803
+ damage: isDamage(card.status),
48804
+ createdAt: /* @__PURE__ */ new Date()
48805
+ }));
48806
+ await collectionName("access-cards-history").insertMany(historyRecords, {
48807
+ session
48808
+ });
48671
48809
  await session?.commitTransaction();
48672
48810
  return result;
48673
48811
  } catch (error) {
@@ -50630,15 +50768,22 @@ function useAccessManagementController() {
50630
50768
  };
50631
50769
  const checkoutVisitor = async (req, res) => {
50632
50770
  try {
50633
- const { userId } = req.body;
50771
+ const { cards } = req.body;
50634
50772
  const schema2 = Joi87.object({
50635
- userId: Joi87.string().hex().length(24).required()
50773
+ cards: Joi87.array().items(
50774
+ Joi87.object({
50775
+ cardId: Joi87.string().hex().length(24).required(),
50776
+ userId: Joi87.string().hex().length(24).required(),
50777
+ status: Joi87.string().required(),
50778
+ remarks: Joi87.string().allow("").required()
50779
+ })
50780
+ ).min(1).required()
50636
50781
  });
50637
- const { error } = schema2.validate({ userId });
50782
+ const { error } = schema2.validate({ cards });
50638
50783
  if (error) {
50639
50784
  return res.status(400).json({ message: error.message });
50640
50785
  }
50641
- const result = await checkoutVisitorSvc({ userId });
50786
+ const result = await checkoutVisitorSvc({ cards });
50642
50787
  return res.status(200).json({ message: "Success", data: result });
50643
50788
  } catch (error) {
50644
50789
  return res.status(500).json({
@@ -58082,18 +58227,10 @@ function useNfcPatrolLogRepo() {
58082
58227
  }
58083
58228
  }
58084
58229
  async function add(value, session) {
58085
- try {
58086
- value = MNfcPatrolLog(value);
58087
- const res = await collection.insertOne(value, { session });
58088
- delCachedData();
58089
- return res.insertedId;
58090
- } catch (error) {
58091
- const isDuplicated = error.message.includes("duplicate");
58092
- if (isDuplicated) {
58093
- throw new BadRequestError179("NFC Patrol Log already exists.");
58094
- }
58095
- throw error;
58096
- }
58230
+ value = MNfcPatrolLog(value);
58231
+ const res = await collection.insertOne(value, { session });
58232
+ delCachedData();
58233
+ return res.insertedId;
58097
58234
  }
58098
58235
  async function getAllBySite({
58099
58236
  page = 1,
@@ -58227,7 +58364,8 @@ function useNfcPatrolLogService() {
58227
58364
  const {
58228
58365
  add: _add,
58229
58366
  getById,
58230
- updateCheckpoint
58367
+ updateCheckpoint,
58368
+ getAllBySite: _getAllBySite
58231
58369
  } = useNfcPatrolLogRepo();
58232
58370
  const routeRepo = useNfcPatrolRouteRepo();
58233
58371
  const tagRepo = useNfcPatrolTagRepo();
@@ -58242,9 +58380,30 @@ function useNfcPatrolLogService() {
58242
58380
  if (!route.startTimes?.length) {
58243
58381
  throw new BadRequestError180("Route does not have any start time.");
58244
58382
  }
58383
+ if (!route.startTimes.includes(value.startTime)) {
58384
+ throw new BadRequestError180("Invalid route start time.");
58385
+ }
58386
+ const existedLog = await _getAllBySite(
58387
+ {
58388
+ site: value.site,
58389
+ page: 1,
58390
+ limit: 1,
58391
+ date: value.date,
58392
+ route: {
58393
+ _id: route._id.toString(),
58394
+ startTime: value.startTime
58395
+ }
58396
+ },
58397
+ session
58398
+ );
58399
+ if (existedLog.items.length > 0) {
58400
+ throw new BadRequestError180(
58401
+ `Patrol log for ${value.startTime} already exists on ${value.date}.`
58402
+ );
58403
+ }
58245
58404
  const checkPoints = [];
58246
58405
  let currentTime = /* @__PURE__ */ new Date(
58247
- `${value.date}T${route.startTimes[0]}:00`
58406
+ `${value.date}T${value.startTime}:00`
58248
58407
  );
58249
58408
  for (const checkPoint of route.checkPoints) {
58250
58409
  const tag = await tagRepo.getById(checkPoint.nfcTag_id, session);
@@ -58278,7 +58437,7 @@ function useNfcPatrolLogService() {
58278
58437
  _id: route._id.toString(),
58279
58438
  name: route.name,
58280
58439
  tolerance: Number(route.tolerance),
58281
- startTime: route.startTimes[0],
58440
+ startTime: value.startTime,
58282
58441
  endTime: currentTime.toISOString().substring(11, 16)
58283
58442
  },
58284
58443
  date: value.date,
@@ -58291,7 +58450,6 @@ function useNfcPatrolLogService() {
58291
58450
  await session?.commitTransaction();
58292
58451
  return "Successfully added NFC Patrol Log.";
58293
58452
  } catch (error) {
58294
- console.error("Add NFC Patrol Log Error:", error);
58295
58453
  await session?.abortTransaction();
58296
58454
  throw error;
58297
58455
  } finally {
@@ -70755,6 +70913,17 @@ function formatHidResponse(data) {
70755
70913
  function sanitizeHidPath(path5) {
70756
70914
  return path5.replace(/([?&]session=)[^&]+/i, "$1<redacted>");
70757
70915
  }
70916
+ function normalizeBaseUrl(value) {
70917
+ return String(value || "").replace(/\/+$/, "");
70918
+ }
70919
+ function getRelayAuth() {
70920
+ const username = process.env.HID_AMICO_RELAY_USERNAME;
70921
+ const password = process.env.HID_AMICO_RELAY_PASSWORD;
70922
+ if (!username || !password) {
70923
+ return void 0;
70924
+ }
70925
+ return { username, password };
70926
+ }
70758
70927
  function toHidRequestError(error, path5) {
70759
70928
  if (axios4.isAxiosError(error)) {
70760
70929
  const status = error.response?.status;
@@ -70794,10 +70963,12 @@ var HidAmicoClient = class {
70794
70963
  constructor(reader) {
70795
70964
  this.reader = reader;
70796
70965
  this.session = "";
70966
+ const relayAuth = getRelayAuth();
70797
70967
  this.http = axios4.create({
70798
- baseURL: reader.baseUrl.replace(/\/+$/, ""),
70968
+ baseURL: normalizeBaseUrl(reader.baseUrl),
70799
70969
  timeout: 15e3,
70800
- headers: { "Content-Type": "application/json" }
70970
+ headers: { "Content-Type": "application/json" },
70971
+ ...relayAuth ? { auth: relayAuth } : {}
70801
70972
  });
70802
70973
  }
70803
70974
  async post(path5, body) {
@@ -71030,7 +71201,12 @@ function useHidAmicoService() {
71030
71201
  try {
71031
71202
  await client.login();
71032
71203
  const valid = await client.sessionIsValid();
71033
- await repo.updateById(id, { lastSeenAt: /* @__PURE__ */ new Date(), lastSyncMessage: "Connection test succeeded." });
71204
+ await repo.updateById(id, {
71205
+ status: "active",
71206
+ lastSeenAt: /* @__PURE__ */ new Date(),
71207
+ lastSyncStatus: "completed",
71208
+ lastSyncMessage: "Connection test succeeded."
71209
+ });
71034
71210
  await repo.addEvent({ reader: id, site: reader.site, type: "test", payload: { valid } });
71035
71211
  return { valid, message: "HID Amico connection test succeeded." };
71036
71212
  } catch (error) {
@@ -71060,6 +71236,7 @@ function useHidAmicoService() {
71060
71236
  }
71061
71237
  const message = batches.length ? `Synced ${batches.length} HID object batch(es).` : "HID reader session verified. No sync payload was supplied.";
71062
71238
  await repo.updateById(id, {
71239
+ status: "active",
71063
71240
  lastSeenAt: /* @__PURE__ */ new Date(),
71064
71241
  lastSyncAt: /* @__PURE__ */ new Date(),
71065
71242
  lastSyncStatus: "completed",