@7365admin1/core 2.51.0 → 2.52.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
+ ## 2.52.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8b9b973: get latest changes for today
8
+
3
9
  ## 2.51.0
4
10
 
5
11
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -1854,7 +1854,7 @@ declare function useBuildingRepo(): {
1854
1854
  getById: (_id: string | ObjectId) => Promise<TBuilding | null>;
1855
1855
  updateById: (_id: ObjectId | string, value: Pick<TBuilding, "name" | "block" | "levels" | "buildingFiles">, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
1856
1856
  deleteById: (_id: string | ObjectId, session?: ClientSession) => Promise<mongodb.UpdateResult<bson.Document>>;
1857
- getBuildingLevel: (site: string | ObjectId, block: number) => Promise<mongodb.WithId<bson.Document> | TBuilding | null>;
1857
+ getBuildingLevel: (site: string | ObjectId, block: number, isVMS: boolean) => Promise<bson.Document[] | TBuilding>;
1858
1858
  };
1859
1859
 
1860
1860
  declare function useBuildingService(): {
@@ -1870,6 +1870,7 @@ declare function useBuildingController(): {
1870
1870
  updateById: (req: Request, res: Response, next: NextFunction) => Promise<void>;
1871
1871
  deleteById: (req: Request, res: Response, next: NextFunction) => Promise<void>;
1872
1872
  getBuildingLevel: (req: Request, res: Response, next: NextFunction) => Promise<void>;
1873
+ uploadSpreadsheetBuilding: (req: Request, res: Response, next: NextFunction) => Promise<void>;
1873
1874
  };
1874
1875
 
1875
1876
  declare function useBuildingUnitRepo(): {
@@ -1927,6 +1928,7 @@ type TCamera = {
1927
1928
  };
1928
1929
  type TSiteCamera = {
1929
1930
  _id?: ObjectId;
1931
+ name?: string;
1930
1932
  site: string | ObjectId;
1931
1933
  host: string;
1932
1934
  username: string;
@@ -1953,6 +1955,7 @@ declare function MSiteCamera(value: TSiteCamera): {
1953
1955
  guardPost: number;
1954
1956
  status: string;
1955
1957
  direction: string;
1958
+ name: string;
1956
1959
  createdAt: string | Date;
1957
1960
  updatedAt: string | Date;
1958
1961
  deletedAt: string | Date;
@@ -2285,7 +2288,7 @@ declare function useSiteCameraRepo(): {
2285
2288
  };
2286
2289
 
2287
2290
  declare function useSiteCameraService(): {
2288
- add: (value: TSiteCamera) => Promise<string>;
2291
+ add: (value: TSiteCamera) => Promise<"CCTV(s) added successfully." | "ANPR(s) added successfully.">;
2289
2292
  listenToCapturedPlateNumber: (onDetected?: ((data: any) => void) | undefined) => Promise<void>;
2290
2293
  };
2291
2294
 
@@ -2705,7 +2708,7 @@ declare function useVisitorTransactionRepo(): {
2705
2708
  pageRange: string;
2706
2709
  }>;
2707
2710
  getOpenByPlateNumber: (plateNumber: string, site: string | ObjectId) => Promise<TVisitorTransaction | null>;
2708
- updateById: (_id: string | ObjectId, value: Partial<TVisitorTransaction>, session?: ClientSession, isNotManualCheckOut?: boolean) => Promise<mongodb.UpdateResult<bson.Document>>;
2711
+ updateById: (_id: string | ObjectId, value: Partial<TVisitorTransaction>, session?: ClientSession, isManualCheckOut?: boolean) => Promise<mongodb.UpdateResult<bson.Document>>;
2709
2712
  deleteVisitorTransaction: (_id: string | ObjectId) => Promise<number>;
2710
2713
  createTextIndex: () => Promise<void>;
2711
2714
  getDeliveryPickupTransactions: () => Promise<mongodb.WithId<bson.Document>[]>;
package/dist/index.js CHANGED
@@ -13313,23 +13313,42 @@ var CameraType = /* @__PURE__ */ ((CameraType2) => {
13313
13313
  })(CameraType || {});
13314
13314
  var schemaUpdateSiteCamera = import_joi34.default.object({
13315
13315
  host: import_joi34.default.string().required(),
13316
- username: import_joi34.default.string().required(),
13317
- password: import_joi34.default.string().required(),
13316
+ username: import_joi34.default.string().when("type", {
13317
+ is: "anpr",
13318
+ then: import_joi34.default.required(),
13319
+ otherwise: import_joi34.default.optional().allow(null, "")
13320
+ }),
13321
+ password: import_joi34.default.string().when("type", {
13322
+ is: "anpr",
13323
+ then: import_joi34.default.required(),
13324
+ otherwise: import_joi34.default.optional().allow(null, "")
13325
+ }),
13318
13326
  category: import_joi34.default.string().allow("standard", "resident", "visitor").required(),
13319
13327
  guardPost: import_joi34.default.number().integer().optional(),
13320
- direction: import_joi34.default.string().optional().allow("entry", "exit", "both", "none")
13328
+ direction: import_joi34.default.string().optional().allow("entry", "exit", "both", "none"),
13329
+ status: import_joi34.default.string().optional().allow("active", "inactive", "deleted"),
13330
+ name: import_joi34.default.string().optional().allow(null, "")
13321
13331
  });
13322
13332
  var schemaSiteCamera = import_joi34.default.object({
13323
13333
  _id: import_joi34.default.string().hex().optional(),
13324
13334
  site: import_joi34.default.string().hex().required(),
13325
13335
  host: import_joi34.default.string().required(),
13326
- username: import_joi34.default.string().required(),
13327
- password: import_joi34.default.string().required(),
13336
+ username: import_joi34.default.string().when("type", {
13337
+ is: "anpr",
13338
+ then: import_joi34.default.required(),
13339
+ otherwise: import_joi34.default.optional().allow(null, "")
13340
+ }),
13341
+ password: import_joi34.default.string().when("type", {
13342
+ is: "anpr",
13343
+ then: import_joi34.default.required(),
13344
+ otherwise: import_joi34.default.optional().allow(null, "")
13345
+ }),
13328
13346
  type: import_joi34.default.string().allow("ip", "anpr").required(),
13329
13347
  category: import_joi34.default.string().allow("standard", "resident", "visitor").required(),
13330
13348
  direction: import_joi34.default.string().required().allow("entry", "exit", "both", "none"),
13331
13349
  guardPost: import_joi34.default.number().integer().optional(),
13332
13350
  status: import_joi34.default.string().optional().allow("active", "inactive", "deleted"),
13351
+ name: import_joi34.default.string().optional().allow(null, ""),
13333
13352
  createdAt: import_joi34.default.date().optional(),
13334
13353
  updatedAt: import_joi34.default.date().optional(),
13335
13354
  deletedAt: import_joi34.default.date().optional()
@@ -13358,6 +13377,7 @@ function MSiteCamera(value) {
13358
13377
  guardPost: value.guardPost ?? 0,
13359
13378
  status: value.status ?? "active",
13360
13379
  direction: value.direction ?? "",
13380
+ name: value.name ?? "",
13361
13381
  createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
13362
13382
  updatedAt: value.updatedAt ?? "",
13363
13383
  deletedAt: value.deletedAt ?? ""
@@ -13672,7 +13692,24 @@ var schemaUpdateVisTrans = import_joi36.default.object({
13672
13692
  checkInRemarks: import_joi36.default.string().optional().allow("", null),
13673
13693
  checkOutRemarks: import_joi36.default.string().optional().allow("", null),
13674
13694
  expectedCheckIn: import_joi36.default.string().isoDate().optional(),
13675
- purpose: import_joi36.default.string().optional().allow(null, "")
13695
+ purpose: import_joi36.default.string().optional().allow(null, ""),
13696
+ members: import_joi36.default.array().items(
13697
+ import_joi36.default.object({
13698
+ name: import_joi36.default.string().optional().allow(null, ""),
13699
+ nric: import_joi36.default.string().optional().allow(null, ""),
13700
+ visitorPass: import_joi36.default.array().items(
13701
+ import_joi36.default.object({
13702
+ keyId: import_joi36.default.string().hex().length(24).required()
13703
+ })
13704
+ ).optional().allow(null),
13705
+ passKeys: import_joi36.default.array().items(
13706
+ import_joi36.default.object({
13707
+ keyId: import_joi36.default.string().hex().length(24).required()
13708
+ })
13709
+ ).optional().allow(null),
13710
+ contact: import_joi36.default.string().optional().allow(null, "")
13711
+ })
13712
+ ).optional().allow(null)
13676
13713
  });
13677
13714
  function MVisitorTransaction(value) {
13678
13715
  const { error } = schemaVisitorTransaction.validate(value, {
@@ -14122,14 +14159,14 @@ function useVisitorTransactionRepo() {
14122
14159
  );
14123
14160
  }
14124
14161
  }
14125
- async function updateById(_id, value, session, isNotManualCheckOut = true) {
14162
+ async function updateById(_id, value, session, isManualCheckOut = true) {
14126
14163
  try {
14127
14164
  _id = new import_mongodb40.ObjectId(_id);
14128
14165
  } catch (error) {
14129
14166
  throw new import_node_server_utils68.BadRequestError("Invalid visitor transaction ID format.");
14130
14167
  }
14131
14168
  value.updatedAt = /* @__PURE__ */ new Date();
14132
- if (value.checkOut && isNotManualCheckOut) {
14169
+ if (value.checkOut && isManualCheckOut) {
14133
14170
  value.manualCheckout = true;
14134
14171
  }
14135
14172
  try {
@@ -17552,7 +17589,7 @@ function useDahuaService() {
17552
17589
  );
17553
17590
  }
17554
17591
  }
17555
- if (["entry", "both"].includes(designation) && direction.toLowerCase() === "approach" && plateNumber) {
17592
+ if (["entry"].includes(designation) && plateNumber) {
17556
17593
  await closeOpenTransaction(plateNumber);
17557
17594
  const vehicle = await getVehicleByPlateNumber(plateNumber);
17558
17595
  const visitorTransaction = {
@@ -17607,11 +17644,11 @@ function useDahuaService() {
17607
17644
  } catch (error) {
17608
17645
  console.log("failed to create visitor transaction", error);
17609
17646
  loggerDahua.error(
17610
- `[${site}][${gate}] Error creating visitor transaction:`,
17647
+ `[${site}][${host}] Error creating visitor transaction:`,
17611
17648
  error
17612
17649
  );
17613
17650
  }
17614
- } else if (["exit", "both"].includes(designation) && direction.toLowerCase() === "leave" && plateNumber) {
17651
+ } else if (["exit"].includes(designation) && plateNumber) {
17615
17652
  const vehicle = await getVehicleByPlateNumber(plateNumber);
17616
17653
  const existingOpenTransaction = await getOpenByPlateNumber(
17617
17654
  plateNumber,
@@ -17636,6 +17673,92 @@ function useDahuaService() {
17636
17673
  if (onDetected2) {
17637
17674
  onDetected2({ reload: true, site: existingOpenTransaction?.site?.toString(), host, direction });
17638
17675
  }
17676
+ } else if (["both"].includes(designation) && plateNumber) {
17677
+ if (direction.toLowerCase() === "leave") {
17678
+ const vehicle = await getVehicleByPlateNumber(plateNumber);
17679
+ const existingOpenTransaction = await getOpenByPlateNumber(
17680
+ plateNumber,
17681
+ site
17682
+ );
17683
+ if (existingOpenTransaction?._id) {
17684
+ currentTransactionId = existingOpenTransaction._id.toString();
17685
+ currentSnapshotField = "snapshotExitImage";
17686
+ }
17687
+ if (vehicle?.recNo) {
17688
+ const dahuaPayload = {
17689
+ host,
17690
+ username,
17691
+ password,
17692
+ plateNumber,
17693
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
17694
+ recno: vehicle?.recNo
17695
+ };
17696
+ await removePlateNumber(dahuaPayload);
17697
+ }
17698
+ await closeOpenTransaction(plateNumber);
17699
+ if (onDetected2 && existingOpenTransaction?.site) {
17700
+ onDetected2({ reload: true, site: existingOpenTransaction?.site?.toString(), host, direction });
17701
+ }
17702
+ } else if (direction.toLowerCase() === "approach") {
17703
+ await closeOpenTransaction(plateNumber);
17704
+ const vehicle = await getVehicleByPlateNumber(plateNumber);
17705
+ const visitorTransaction = {
17706
+ site,
17707
+ plateNumber,
17708
+ org,
17709
+ status: "unregistered" /* UNREGISTERED */
17710
+ };
17711
+ const typesForAutoRegister = Object.values(PersonTypes);
17712
+ let startDate = vehicle?.start ? new Date(vehicle?.start) : /* @__PURE__ */ new Date();
17713
+ let endDate = vehicle?.end ? new Date(vehicle?.end) : new Date(startDate.getTime() + 60 * 60 * 1e3);
17714
+ if (vehicle && typeof vehicle.category === "string" && typesForAutoRegister.includes(vehicle.category)) {
17715
+ visitorTransaction.name = vehicle.name;
17716
+ visitorTransaction.nric = vehicle.nric;
17717
+ visitorTransaction.contact = vehicle.phoneNumber;
17718
+ visitorTransaction.block = Number(vehicle.block);
17719
+ visitorTransaction.level = vehicle.level;
17720
+ visitorTransaction.unit = vehicle.unit?.toString();
17721
+ visitorTransaction.unitName = vehicle.unitName;
17722
+ visitorTransaction.type = vehicle.category;
17723
+ visitorTransaction.status = "registered" /* REGISTERED */;
17724
+ visitorTransaction.recNo = vehicle.recNo;
17725
+ visitorTransaction.expiredAt = vehicle.end;
17726
+ }
17727
+ const dahuaPayload = {
17728
+ host,
17729
+ username,
17730
+ password,
17731
+ plateNumber,
17732
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
17733
+ owner: vehicle?.name ?? ""
17734
+ };
17735
+ dahuaPayload.start = formatDahuaDate(startDate);
17736
+ dahuaPayload.end = formatDahuaDate(endDate);
17737
+ const shouldHaveTimeLimit = visitorTransaction.type === "guest" /* GUEST */ || visitorTransaction.status === "unregistered" /* UNREGISTERED */;
17738
+ if (shouldHaveTimeLimit) {
17739
+ const startDate2 = /* @__PURE__ */ new Date();
17740
+ const endDate2 = new Date(startDate2.getTime() + 60 * 60 * 1e3);
17741
+ dahuaPayload.start = formatDahuaDate(startDate2);
17742
+ dahuaPayload.end = formatDahuaDate(endDate2);
17743
+ visitorTransaction.expiredAt = endDate2.toISOString();
17744
+ }
17745
+ try {
17746
+ await addPlateNumber(dahuaPayload);
17747
+ const result = await add(visitorTransaction, void 0, true);
17748
+ const transactionId = result?._id;
17749
+ currentTransactionId = transactionId?.toString();
17750
+ currentSnapshotField = "snapshotEntryImage";
17751
+ if (onDetected2) {
17752
+ onDetected2({ _id: transactionId, site: result?.site?.toString(), plateNumber: result.plateNumber, host, direction });
17753
+ }
17754
+ } catch (error) {
17755
+ console.log("failed to create visitor transaction", error);
17756
+ loggerDahua.error(
17757
+ `[${site}][${host}] Error creating visitor transaction:`,
17758
+ error
17759
+ );
17760
+ }
17761
+ }
17639
17762
  }
17640
17763
  }
17641
17764
  }
@@ -17767,6 +17890,9 @@ function useDahuaService() {
17767
17890
  }
17768
17891
  }
17769
17892
  async function listenToCamera(camera, onDetected) {
17893
+ if (camera?.type != "anpr" && camera?.status != "deleted") {
17894
+ return;
17895
+ }
17770
17896
  if (onDetected) {
17771
17897
  _savedOnDetected = onDetected;
17772
17898
  }
@@ -17775,6 +17901,14 @@ function useDahuaService() {
17775
17901
  return;
17776
17902
  }
17777
17903
  const cameraId = camera._id.toString();
17904
+ if (camera.status !== "active") {
17905
+ if (cameraRegistry.has(cameraId)) {
17906
+ loggerDahua.info(`[${camera?.host ?? camera?._id}] Camera is ${camera.status}. Stopping connection.`);
17907
+ cameraRegistry.get(cameraId)?.abort();
17908
+ cameraRegistry.delete(cameraId);
17909
+ }
17910
+ return;
17911
+ }
17778
17912
  if (cameraRegistry.has(cameraId)) {
17779
17913
  loggerDahua.info(`[${camera.host}] Stopping existing connection for update.`);
17780
17914
  cameraRegistry.get(cameraId)?.abort();
@@ -17922,6 +18056,9 @@ function useDahuaService() {
17922
18056
  continue;
17923
18057
  } else if ([400, 401, 403, 500].includes(response.statusCode)) {
17924
18058
  loggerDahua.error(`[${host}] Connection error: ${response.statusCode}`);
18059
+ if (onDetected) {
18060
+ onDetected({ site, messagePermanent: `Camera with last 3 characters ${host?.slice(-3)} Connection Error. Check username and password.` });
18061
+ }
17925
18062
  return;
17926
18063
  }
17927
18064
  loggerDahua.info(`[${host}] Successfully connected to ANPR.`);
@@ -17974,8 +18111,15 @@ function useDahuaService() {
17974
18111
  break;
17975
18112
  }
17976
18113
  loggerDahua.error(
17977
- `[${host}] Connection lost or error: ${error.message || error}. Retrying in 5s...`
18114
+ `[${host}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds...`
17978
18115
  );
18116
+ if (onDetected) {
18117
+ onDetected({
18118
+ site,
18119
+ host,
18120
+ message: `Camera in ${gate} Connection Lost. Retrying in 10 seconds. Check the camera if this issue persists, Or set the camera to Inactive to stop this notification.`
18121
+ });
18122
+ }
17979
18123
  } finally {
17980
18124
  if (bufferQueue?.destroy) {
17981
18125
  try {
@@ -17996,7 +18140,7 @@ function useDahuaService() {
17996
18140
  }
17997
18141
  }
17998
18142
  if (!signal.aborted) {
17999
- await new Promise((res) => setTimeout(res, 5e3));
18143
+ await new Promise((res) => setTimeout(res, 1e4));
18000
18144
  }
18001
18145
  }
18002
18146
  loggerDahua.info(`[${host}] ANPR Listener stopped.`);
@@ -18498,6 +18642,8 @@ function useSiteCameraRepo() {
18498
18642
  }
18499
18643
  try {
18500
18644
  await collection.deleteOne({ _id }, { session });
18645
+ const { listenToCamera } = useDahuaService();
18646
+ await listenToCamera({ _id, status: "deleted" });
18501
18647
  delCachedData();
18502
18648
  return "Successfully deleted site camera.";
18503
18649
  } catch (error) {
@@ -19335,7 +19481,7 @@ function useBuildingRepo() {
19335
19481
  }
19336
19482
  }
19337
19483
  }
19338
- async function getBuildingLevel(site, block) {
19484
+ async function getBuildingLevel(site, block, isVMS) {
19339
19485
  try {
19340
19486
  site = new import_mongodb49.ObjectId(site);
19341
19487
  } catch (error) {
@@ -19345,8 +19491,7 @@ function useBuildingRepo() {
19345
19491
  site,
19346
19492
  block
19347
19493
  };
19348
- const cacheOptions = { ...query };
19349
- const cacheKey = (0, import_node_server_utils82.makeCacheKey)(buildings_namespace_collection, cacheOptions);
19494
+ const cacheKey = (0, import_node_server_utils82.makeCacheKey)(buildings_namespace_collection, { ...query, isVMS });
19350
19495
  try {
19351
19496
  const cached = await getCache(cacheKey);
19352
19497
  if (cached) {
@@ -19356,7 +19501,24 @@ function useBuildingRepo() {
19356
19501
  });
19357
19502
  return cached;
19358
19503
  }
19359
- const result = await collection.findOne(query);
19504
+ const pipeline = [{ $match: { ...query } }];
19505
+ if (isVMS) {
19506
+ pipeline.push({
19507
+ $lookup: {
19508
+ from: "building-levels",
19509
+ let: { id: "$_id" },
19510
+ pipeline: [
19511
+ {
19512
+ $match: {
19513
+ $expr: { $eq: ["$block", "$$id"] }
19514
+ }
19515
+ }
19516
+ ],
19517
+ as: "building_levels"
19518
+ }
19519
+ });
19520
+ }
19521
+ const result = await collection.aggregate(pipeline).toArray();
19360
19522
  setCache(cacheKey, result, 300).then(() => {
19361
19523
  import_node_server_utils82.logger.log({
19362
19524
  level: "info",
@@ -19544,6 +19706,9 @@ function useBuildingService() {
19544
19706
  // src/controllers/building.controller.ts
19545
19707
  var import_node_server_utils84 = require("@7365admin1/node-server-utils");
19546
19708
  var import_joi44 = __toESM(require("joi"));
19709
+ var import_exceljs = __toESM(require("exceljs"));
19710
+ var import_csv_parser = __toESM(require("csv-parser"));
19711
+ var import_fs2 = __toESM(require("fs"));
19547
19712
  function useBuildingController() {
19548
19713
  const {
19549
19714
  getAll: _getAll,
@@ -19565,7 +19730,10 @@ function useBuildingController() {
19565
19730
  serial: import_joi44.default.string().optional().allow("", null),
19566
19731
  status: import_joi44.default.string().optional().allow("", null),
19567
19732
  // buildingFloorPlan: Joi.array().items(Joi.string()).optional(),
19568
- buildingFiles: import_joi44.default.array().items({ id: import_joi44.default.string().hex().optional().allow("", null), name: import_joi44.default.string().optional().allow("", null) }).optional().allow("", null)
19733
+ buildingFiles: import_joi44.default.array().items({
19734
+ id: import_joi44.default.string().hex().optional().allow("", null),
19735
+ name: import_joi44.default.string().optional().allow("", null)
19736
+ }).optional().allow("", null)
19569
19737
  });
19570
19738
  const { error } = validation.validate(value);
19571
19739
  if (error) {
@@ -19685,6 +19853,7 @@ function useBuildingController() {
19685
19853
  async function getBuildingLevel(req, res, next) {
19686
19854
  const site = req.params.site ?? "";
19687
19855
  let block = req.query.block;
19856
+ const isVMS = req.query.isVMS;
19688
19857
  const validation = import_joi44.default.object({
19689
19858
  site: import_joi44.default.string().hex().required(),
19690
19859
  block: import_joi44.default.number().required()
@@ -19696,20 +19865,132 @@ function useBuildingController() {
19696
19865
  }
19697
19866
  block = parseInt(block) ?? 0;
19698
19867
  try {
19699
- const siteBlock = await _getBuildingLevel(site, block);
19868
+ const siteBlock = await _getBuildingLevel(site, block, isVMS);
19700
19869
  res.json(siteBlock);
19701
19870
  return;
19702
19871
  } catch (error2) {
19703
19872
  next(error2);
19704
19873
  }
19705
19874
  }
19875
+ async function uploadSpreadsheetBuilding(req, res, next) {
19876
+ try {
19877
+ if (!req.file) {
19878
+ next(new import_node_server_utils84.BadRequestError("Spreadsheet file is required."));
19879
+ return;
19880
+ }
19881
+ const { originalname, path: path4 } = req.file;
19882
+ const lowerName = originalname.toLowerCase();
19883
+ const rowSchema = import_joi44.default.object({
19884
+ block: import_joi44.default.number().integer().min(0).required(),
19885
+ level: import_joi44.default.string().optional().allow(null, ""),
19886
+ unit: import_joi44.default.string().optional().allow(null, ""),
19887
+ category: import_joi44.default.string().trim().required(),
19888
+ name: import_joi44.default.string().trim().optional().allow(null, ""),
19889
+ email: import_joi44.default.string().email().lowercase().optional().allow(null, ""),
19890
+ phoneNumber: import_joi44.default.string().trim().optional().allow(null, "")
19891
+ });
19892
+ const querySchema = import_joi44.default.object({
19893
+ site: import_joi44.default.string().hex().length(24).required(),
19894
+ org: import_joi44.default.string().hex().length(24).optional().allow(null, "")
19895
+ });
19896
+ const { error: queryError, value: queryValue } = querySchema.validate(
19897
+ req.query,
19898
+ { abortEarly: false, convert: true }
19899
+ );
19900
+ if (queryError) {
19901
+ next(
19902
+ new import_node_server_utils84.BadRequestError(
19903
+ queryError.details.map((d) => d.message).join(", ")
19904
+ )
19905
+ );
19906
+ return;
19907
+ }
19908
+ const { site, org } = queryValue;
19909
+ let rows = [];
19910
+ if (lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls")) {
19911
+ const workbook = new import_exceljs.default.Workbook();
19912
+ await workbook.xlsx.readFile(path4);
19913
+ const worksheet = workbook.worksheets[0];
19914
+ if (!worksheet) {
19915
+ next(
19916
+ new import_node_server_utils84.BadRequestError("No worksheet found in uploaded Excel file.")
19917
+ );
19918
+ return;
19919
+ }
19920
+ const headerRow = worksheet.getRow(1);
19921
+ const headers = (headerRow.values || []).slice(1).map(
19922
+ (header) => String(header ?? "").trim().replace(/\(.*\)/, "").trim()
19923
+ );
19924
+ worksheet.eachRow((row, rowNumber) => {
19925
+ if (rowNumber === 1)
19926
+ return;
19927
+ const rowData = {};
19928
+ headers.forEach((header, index) => {
19929
+ rowData[header] = row.getCell(index + 1).value ?? "";
19930
+ });
19931
+ if (Object.values(rowData).some(
19932
+ (v) => v !== "" && v !== null && v !== void 0
19933
+ )) {
19934
+ rows.push(rowData);
19935
+ }
19936
+ });
19937
+ } else if (lowerName.endsWith(".csv")) {
19938
+ rows = await new Promise((resolve, reject) => {
19939
+ const parsed = [];
19940
+ import_fs2.default.createReadStream(path4).pipe((0, import_csv_parser.default)()).on("headers", (headers) => {
19941
+ headers = headers.map(
19942
+ (h) => h.trim().replace(/\(.*\)/, "").trim()
19943
+ );
19944
+ }).on("data", (row) => parsed.push(row)).on("end", () => resolve(parsed)).on("error", reject);
19945
+ });
19946
+ } else {
19947
+ next(
19948
+ new import_node_server_utils84.BadRequestError("Only .xlsx, .xls, or .csv files are allowed.")
19949
+ );
19950
+ return;
19951
+ }
19952
+ const validRows = [];
19953
+ const invalidRows = [];
19954
+ rows.forEach((row, index) => {
19955
+ const { error, value } = rowSchema.validate(row, {
19956
+ abortEarly: false,
19957
+ stripUnknown: true
19958
+ });
19959
+ if (error) {
19960
+ invalidRows.push({
19961
+ row: index + 2,
19962
+ data: row,
19963
+ errors: error.details.map((d) => d.message)
19964
+ });
19965
+ } else {
19966
+ validRows.push(value);
19967
+ }
19968
+ });
19969
+ res.status(200).json({
19970
+ message: "Spreadsheet import completed (buildings).",
19971
+ fileName: originalname,
19972
+ totalRows: rows.length,
19973
+ validRows: validRows.length,
19974
+ invalidRows: invalidRows.length,
19975
+ validationErrors: invalidRows,
19976
+ data: validRows
19977
+ // testing: return the validated building rows directly
19978
+ });
19979
+ import_fs2.default.unlink(path4, () => {
19980
+ });
19981
+ } catch (error) {
19982
+ import_node_server_utils84.logger.log({ level: "error", message: error.message });
19983
+ next(error);
19984
+ }
19985
+ }
19706
19986
  return {
19707
19987
  createBuilding,
19708
19988
  getAll,
19709
19989
  getById,
19710
19990
  updateById,
19711
19991
  deleteById,
19712
- getBuildingLevel
19992
+ getBuildingLevel,
19993
+ uploadSpreadsheetBuilding
19713
19994
  };
19714
19995
  }
19715
19996
 
@@ -20025,9 +20306,9 @@ function useBuildingUnitController() {
20025
20306
  // src/controllers/vehicle.controller.ts
20026
20307
  var import_node_server_utils87 = require("@7365admin1/node-server-utils");
20027
20308
  var import_joi46 = __toESM(require("joi"));
20028
- var import_exceljs = __toESM(require("exceljs"));
20029
- var import_csv_parser = __toESM(require("csv-parser"));
20030
- var import_fs2 = __toESM(require("fs"));
20309
+ var import_exceljs2 = __toESM(require("exceljs"));
20310
+ var import_csv_parser2 = __toESM(require("csv-parser"));
20311
+ var import_fs3 = __toESM(require("fs"));
20031
20312
  function useVehicleController() {
20032
20313
  const {
20033
20314
  add: _add,
@@ -20147,7 +20428,7 @@ function useVehicleController() {
20147
20428
  const { site, org } = queryValue;
20148
20429
  let rows = [];
20149
20430
  if (lowerName.endsWith(".xlsx") || lowerName.endsWith(".xls")) {
20150
- const workbook = new import_exceljs.default.Workbook();
20431
+ const workbook = new import_exceljs2.default.Workbook();
20151
20432
  await workbook.xlsx.readFile(path4);
20152
20433
  const worksheet = workbook.worksheets[0];
20153
20434
  if (!worksheet) {
@@ -20176,7 +20457,7 @@ function useVehicleController() {
20176
20457
  } else if (lowerName.endsWith(".csv")) {
20177
20458
  rows = await new Promise((resolve, reject) => {
20178
20459
  const parsed = [];
20179
- import_fs2.default.createReadStream(path4).pipe((0, import_csv_parser.default)()).on("headers", (headers) => {
20460
+ import_fs3.default.createReadStream(path4).pipe((0, import_csv_parser2.default)()).on("headers", (headers) => {
20180
20461
  headers = headers.map(
20181
20462
  (h) => h.trim().replace(/\(.*\)/, "").trim()
20182
20463
  );
@@ -20219,7 +20500,7 @@ function useVehicleController() {
20219
20500
  validationErrors: invalidRows,
20220
20501
  data
20221
20502
  });
20222
- import_fs2.default.unlink(path4, () => {
20503
+ import_fs3.default.unlink(path4, () => {
20223
20504
  });
20224
20505
  } catch (error) {
20225
20506
  import_node_server_utils87.logger.log({ level: "error", message: error.message });
@@ -20613,8 +20894,9 @@ function useSiteCameraService() {
20613
20894
  try {
20614
20895
  session.startTransaction();
20615
20896
  await _add(value, session);
20897
+ const message = value.type === "ip" ? "CCTV(s) added successfully." : "ANPR(s) added successfully.";
20616
20898
  await session.commitTransaction();
20617
- return "ANPR(s) added successfully.";
20899
+ return message;
20618
20900
  } catch (error2) {
20619
20901
  await session.abortTransaction();
20620
20902
  throw error2;
@@ -33748,7 +34030,7 @@ var import_node_server_utils153 = require("@7365admin1/node-server-utils");
33748
34030
  var import_mongodb90 = require("mongodb");
33749
34031
 
33750
34032
  // src/utils/access-management.ts
33751
- var import_fs3 = __toESM(require("fs"));
34033
+ var import_fs4 = __toESM(require("fs"));
33752
34034
  var import_path = __toESM(require("path"));
33753
34035
  var import_axios = __toESM(require("axios"));
33754
34036
  var import_xml2js = require("xml2js");
@@ -33778,7 +34060,7 @@ var minifyXml = (xml) => {
33778
34060
  return xml.replace(/>\s+</g, "><").replace(/\n/g, "").replace(/\r/g, "").replace(/\t/g, "").trim();
33779
34061
  };
33780
34062
  var readTemplate = (name, params) => {
33781
- const template = import_fs3.default.readFileSync(
34063
+ const template = import_fs4.default.readFileSync(
33782
34064
  import_path.default.join(__dirname, `../dist/public/xml-templates/${name}.xml`),
33783
34065
  "utf-8"
33784
34066
  );
@@ -33909,14 +34191,14 @@ var import_xml2js2 = require("xml2js");
33909
34191
 
33910
34192
  // src/utils/rsa-encryption.ts
33911
34193
  var crypto2 = __toESM(require("crypto"));
33912
- var import_fs4 = __toESM(require("fs"));
34194
+ var import_fs5 = __toESM(require("fs"));
33913
34195
  var import_path2 = __toESM(require("path"));
33914
34196
  var pub = import_path2.default.resolve(process.cwd(), "./src/public/rsa-keys/new_rsa_512_pub.pem");
33915
34197
  var priv = import_path2.default.resolve(process.cwd(), "./src/public/rsa-keys/new_rsa_512_priv.pem");
33916
34198
  var EncryptionCredentials = class {
33917
34199
  };
33918
- EncryptionCredentials.RAW_PUBLIC_KEY = import_fs4.default.readFileSync(pub, "utf8");
33919
- EncryptionCredentials.RAW_PRIVATE_KEY = import_fs4.default.readFileSync(priv, "utf8");
34200
+ EncryptionCredentials.RAW_PUBLIC_KEY = import_fs5.default.readFileSync(pub, "utf8");
34201
+ EncryptionCredentials.RAW_PRIVATE_KEY = import_fs5.default.readFileSync(priv, "utf8");
33920
34202
  var EntrypassRSAEncryption = class extends EncryptionCredentials {
33921
34203
  static hexToCardNumber(hex) {
33922
34204
  if (!/^[0-9A-Fa-f]{8}$/.test(hex)) {