@7365admin1/core 2.14.0 → 2.15.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
@@ -1388,7 +1388,9 @@ var schemaOccurrenceEntry = Joi5.object({
1388
1388
  subject: Joi5.string().hex().optional().allow(null, ""),
1389
1389
  occurrence: Joi5.string().optional().allow(null, ""),
1390
1390
  signature: Joi5.string().hex().optional().allow(null, ""),
1391
- incidentReportId: Joi5.string().hex().optional().allow(null, "")
1391
+ incidentReportId: Joi5.string().hex().optional().allow(null, ""),
1392
+ eSignature: Joi5.string().required(),
1393
+ createdByName: Joi5.string().optional().allow(null, "")
1392
1394
  });
1393
1395
  var schemaUpdateOccurrenceEntry = Joi5.object({
1394
1396
  _id: Joi5.string().hex().required(),
@@ -1397,7 +1399,9 @@ var schemaUpdateOccurrenceEntry = Joi5.object({
1397
1399
  subject: Joi5.string().hex().optional().allow(null, ""),
1398
1400
  occurrence: Joi5.string().optional().allow(null, ""),
1399
1401
  signature: Joi5.string().hex().optional().allow(null, ""),
1400
- incidentReportId: Joi5.string().hex().optional().allow(null, "")
1402
+ incidentReportId: Joi5.string().hex().optional().allow(null, ""),
1403
+ eSignature: Joi5.string().required(),
1404
+ createdByName: Joi5.string().optional().allow(null, "")
1401
1405
  });
1402
1406
  function MOccurrenceEntry(value) {
1403
1407
  if (value._id && typeof value._id === "string") {
@@ -1454,6 +1458,8 @@ function MOccurrenceEntry(value) {
1454
1458
  incidentReportId: value.incidentReportId,
1455
1459
  signature: value.signature,
1456
1460
  userName: value.userName,
1461
+ eSignature: value.eSignature,
1462
+ createdByName: value.createdByName,
1457
1463
  date: value.date,
1458
1464
  createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1459
1465
  updatedAt: value.updatedAt,
@@ -6424,12 +6430,12 @@ function useUserController() {
6424
6430
  }
6425
6431
  }
6426
6432
  async function createUserByVerification(req, res, next) {
6427
- const allowedTypes2 = ["user-sign-up", "user-invite"];
6433
+ const allowedTypes = ["user-sign-up", "user-invite"];
6428
6434
  const validation = Joi14.object({
6429
6435
  id: Joi14.string().hex().required(),
6430
6436
  name: Joi14.string().required(),
6431
6437
  password: Joi14.string().required(),
6432
- type: Joi14.string().required().valid(...allowedTypes2)
6438
+ type: Joi14.string().required().valid(...allowedTypes)
6433
6439
  });
6434
6440
  const id = req.params.id;
6435
6441
  const payload = { ...req.body };
@@ -15493,12 +15499,37 @@ import {
15493
15499
  import { BadRequestError as BadRequestError77, logger as logger61 } from "@7365admin1/node-server-utils";
15494
15500
  import Joi43 from "joi";
15495
15501
  import { ObjectId as ObjectId45 } from "mongodb";
15496
- var allowedTypes = ["whitelist", "blacklist"];
15497
- var allowedCategories = ["resident", "visitor"];
15502
+ var VehicleType = /* @__PURE__ */ ((VehicleType2) => {
15503
+ VehicleType2["WHITELIST"] = "whitelist";
15504
+ VehicleType2["BLOCKLIST"] = "blocklist";
15505
+ return VehicleType2;
15506
+ })(VehicleType || {});
15507
+ var VehicleCategory = /* @__PURE__ */ ((VehicleCategory2) => {
15508
+ VehicleCategory2["RESIDENT"] = "resident";
15509
+ VehicleCategory2["VISITOR"] = "visitor";
15510
+ return VehicleCategory2;
15511
+ })(VehicleCategory || {});
15512
+ var VehicleStatus = /* @__PURE__ */ ((VehicleStatus2) => {
15513
+ VehicleStatus2["PENDING"] = "pending";
15514
+ VehicleStatus2["ACTIVE"] = "active";
15515
+ VehicleStatus2["INACTIVE"] = "inactive";
15516
+ VehicleStatus2["DELETED"] = "deleted";
15517
+ return VehicleStatus2;
15518
+ })(VehicleStatus || {});
15519
+ var OrgNature = /* @__PURE__ */ ((OrgNature2) => {
15520
+ OrgNature2["PROPERTY_MANAGEMENT_AGENCY"] = "property_management_agency";
15521
+ OrgNature2["SECURITY_AGENCY"] = "security_agency";
15522
+ return OrgNature2;
15523
+ })(OrgNature || {});
15524
+ var ANPRMode = /* @__PURE__ */ ((ANPRMode2) => {
15525
+ ANPRMode2["TRAFFIC_REDLIST"] = "TrafficRedList";
15526
+ ANPRMode2["TRAFFIC_BLACKLIST"] = "TrafficBlackList";
15527
+ return ANPRMode2;
15528
+ })(ANPRMode || {});
15498
15529
  var vehicleSchema = Joi43.object({
15499
15530
  plateNumber: Joi43.string().required(),
15500
- type: Joi43.string().required().allow(...allowedTypes),
15501
- category: Joi43.string().required().allow(...allowedCategories),
15531
+ type: Joi43.string().required().valid(...Object.values(VehicleType)),
15532
+ category: Joi43.string().required().valid(...Object.values(VehicleCategory)),
15502
15533
  name: Joi43.string().required(),
15503
15534
  phoneNumber: Joi43.string().optional().allow("", null),
15504
15535
  recNo: Joi43.string().optional().allow("", null),
@@ -15512,7 +15543,8 @@ var vehicleSchema = Joi43.object({
15512
15543
  seasonPassType: Joi43.string().optional().allow("", null),
15513
15544
  start: Joi43.date().optional().allow("", null),
15514
15545
  end: Joi43.date().optional().allow("", null),
15515
- unitName: Joi43.string().optional().allow("", null)
15546
+ unitName: Joi43.string().optional().allow("", null),
15547
+ status: Joi43.string().optional().valid(...Object.values(VehicleStatus)).allow("")
15516
15548
  });
15517
15549
  function MVehicle(value) {
15518
15550
  const { error } = vehicleSchema.validate(value);
@@ -15541,6 +15573,11 @@ function MVehicle(value) {
15541
15573
  throw new BadRequestError77("Invalid building unit ID format.");
15542
15574
  }
15543
15575
  }
15576
+ const createdAtDate = value.createdAt ? new Date(value.createdAt) : /* @__PURE__ */ new Date();
15577
+ const expiredDate = new Date(createdAtDate);
15578
+ expiredDate.setFullYear(expiredDate.getFullYear() + 10);
15579
+ const createdAt = createdAtDate.toISOString();
15580
+ const expiredAt = value.end ?? expiredDate.toISOString();
15544
15581
  return {
15545
15582
  plateNumber: value.plateNumber ?? "",
15546
15583
  type: value.type ?? "",
@@ -15556,11 +15593,11 @@ function MVehicle(value) {
15556
15593
  nric: value.nric ?? "",
15557
15594
  remarks: value.remarks ?? "",
15558
15595
  seasonPassType: value.seasonPassType ?? "",
15559
- start: value.start ?? "",
15560
- end: value.end ?? "",
15561
- status: value.status ?? "active",
15596
+ start: value.start ?? createdAt,
15597
+ end: value.end ?? expiredAt,
15598
+ status: value.status ?? "active" /* ACTIVE */,
15562
15599
  unitName: value.unitName ?? "",
15563
- createdAt: value.createdAt ?? /* @__PURE__ */ new Date(),
15600
+ createdAt,
15564
15601
  updatedAt: value.updatedAt ?? "",
15565
15602
  deletedAt: value.deletedAt ?? ""
15566
15603
  };
@@ -15618,18 +15655,44 @@ function useVehicleRepo() {
15618
15655
  }
15619
15656
  const namespace_collection = "vehicles";
15620
15657
  const collection = db.collection(namespace_collection);
15621
- const { delNamespace, setCache, getCache, delCache } = useCache28(namespace_collection);
15658
+ const { delNamespace, setCache, getCache } = useCache28(namespace_collection);
15622
15659
  async function createIndex() {
15623
15660
  try {
15624
15661
  await collection.createIndexes([
15625
15662
  { key: { status: 1 } },
15626
15663
  { key: { type: 1 } },
15627
- { key: { category: 1 } }
15664
+ { key: { category: 1 } },
15665
+ {
15666
+ key: { nric: 1 },
15667
+ name: "uniq_nric_not_deleted_nonempty",
15668
+ unique: true,
15669
+ partialFilterExpression: {
15670
+ nric: { $type: "string", $gt: "" },
15671
+ deletedAt: ""
15672
+ }
15673
+ }
15628
15674
  ]);
15629
15675
  } catch (error) {
15676
+ console.error("createIndexes error:", error);
15630
15677
  throw new InternalServerError28("Failed to create index on vehicle.");
15631
15678
  }
15632
15679
  }
15680
+ async function createTextIndex() {
15681
+ try {
15682
+ await collection.createIndex({
15683
+ name: "text",
15684
+ plateNumber: "text",
15685
+ level: "text",
15686
+ unitName: "text",
15687
+ phoneNumber: "text",
15688
+ nric: "text"
15689
+ });
15690
+ } catch (error) {
15691
+ throw new InternalServerError28(
15692
+ "Failed to create text index on visitor transaction."
15693
+ );
15694
+ }
15695
+ }
15633
15696
  async function add(value, session) {
15634
15697
  try {
15635
15698
  value = MVehicle(value);
@@ -15661,37 +15724,40 @@ function useVehicleRepo() {
15661
15724
  search = "",
15662
15725
  sort = {},
15663
15726
  type = "",
15664
- category = ""
15727
+ category = "",
15728
+ status = ""
15665
15729
  }) {
15666
15730
  page = page > 0 ? page - 1 : 0;
15667
- const query = { status: "active" };
15731
+ const baseQuery = {
15732
+ ...status && { status },
15733
+ ...type && { type },
15734
+ ...type && { type }
15735
+ };
15736
+ let query = { ...baseQuery };
15737
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
15668
15738
  const cacheOptions = {
15669
- status: "active",
15739
+ ...status && { status },
15740
+ ...type && { type },
15741
+ ...type && { type },
15670
15742
  page: String(page),
15671
- limit: String(limit)
15743
+ limit: String(limit),
15744
+ sort: JSON.stringify(sort)
15672
15745
  };
15673
- sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
15674
- cacheOptions.sort = JSON.stringify(sort);
15675
15746
  if (search) {
15676
- query.$or = [{ name: { $regex: search, $options: "i" } }];
15747
+ query.$text = { $search: search };
15677
15748
  cacheOptions.search = search;
15678
15749
  }
15679
- if (type) {
15680
- query.type = type;
15681
- cacheOptions.type = type;
15682
- }
15683
- if (category) {
15684
- query.category = category;
15685
- cacheOptions.category = category;
15686
- }
15687
15750
  const cacheKey = makeCacheKey26(namespace_collection, cacheOptions);
15688
15751
  const cachedData = await getCache(cacheKey);
15689
15752
  if (cachedData) {
15690
15753
  logger62.info(`Cache hit for key: ${cacheKey}`);
15691
15754
  return cachedData;
15692
15755
  }
15756
+ const escapeRegex = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15693
15757
  try {
15694
- const items = await collection.aggregate([
15758
+ let items = [];
15759
+ let length = 0;
15760
+ items = await collection.aggregate([
15695
15761
  { $match: query },
15696
15762
  { $sort: sort },
15697
15763
  { $skip: page * limit },
@@ -15717,17 +15783,65 @@ function useVehicleRepo() {
15717
15783
  level: 1,
15718
15784
  unit: "$units.name",
15719
15785
  plateNumber: 1,
15720
- recNo: 1
15786
+ recNo: 1,
15787
+ nric: 1
15721
15788
  }
15722
15789
  }
15723
15790
  ]).toArray();
15724
- const length = await collection.countDocuments(query);
15791
+ length = await collection.countDocuments(query);
15792
+ if ((!items || items.length === 0) && search) {
15793
+ const escaped = escapeRegex(search);
15794
+ const regexQuery = {
15795
+ ...baseQuery,
15796
+ ...type && { type },
15797
+ ...category && { category },
15798
+ $or: [
15799
+ { name: { $regex: escaped, $options: "i" } },
15800
+ { plateNumber: { $regex: escaped, $options: "i" } },
15801
+ { company: { $regex: escaped, $options: "i" } },
15802
+ { level: { $regex: escaped, $options: "i" } },
15803
+ { unitName: { $regex: escaped, $options: "i" } },
15804
+ { contact: { $regex: escaped, $options: "i" } },
15805
+ { nric: { $regex: escaped, $options: "i" } }
15806
+ ]
15807
+ };
15808
+ items = await collection.aggregate([
15809
+ { $match: regexQuery },
15810
+ { $sort: sort },
15811
+ { $skip: page * limit },
15812
+ { $limit: limit },
15813
+ {
15814
+ $lookup: {
15815
+ from: "building-units",
15816
+ localField: "unit",
15817
+ foreignField: "_id",
15818
+ as: "units",
15819
+ pipeline: [{ $project: { name: 1 } }]
15820
+ }
15821
+ },
15822
+ { $unwind: { path: "$units", preserveNullAndEmptyArrays: true } },
15823
+ {
15824
+ $project: {
15825
+ name: 1,
15826
+ type: 1,
15827
+ category: 1,
15828
+ status: 1,
15829
+ phoneNumber: 1,
15830
+ block: 1,
15831
+ level: 1,
15832
+ unit: "$units.name",
15833
+ plateNumber: 1,
15834
+ recNo: 1,
15835
+ nric: 1
15836
+ }
15837
+ }
15838
+ ]).toArray();
15839
+ length = await collection.countDocuments(regexQuery);
15840
+ }
15725
15841
  const data = paginate21(items, page, limit, length);
15726
- setCache(cacheKey, data, 15 * 60).then(() => {
15727
- logger62.info(`Cache set for key: ${cacheKey}`);
15728
- }).catch((err) => {
15729
- logger62.error(`Failed to set cache for key: ${cacheKey}`, err);
15730
- });
15842
+ setCache(cacheKey, data, 15 * 60).then(() => logger62.info(`Cache set for key: ${cacheKey}`)).catch(
15843
+ (err) => logger62.error(`Failed to set cache for key: ${cacheKey}`, err)
15844
+ );
15731
15845
  return data;
15732
15846
  } catch (error) {
15733
15847
  throw error;
@@ -15821,7 +15935,10 @@ function useVehicleRepo() {
15821
15935
  type: 1,
15822
15936
  category: 1,
15823
15937
  status: 1,
15824
- recNo: 1
15938
+ recNo: 1,
15939
+ start: 1,
15940
+ end: 1,
15941
+ seasonPassType: 1
15825
15942
  }
15826
15943
  }
15827
15944
  ]).toArray();
@@ -15870,7 +15987,7 @@ function useVehicleRepo() {
15870
15987
  throw error2;
15871
15988
  }
15872
15989
  }
15873
- async function updateVehicle(_id, value) {
15990
+ async function updateVehicle(_id, value, session) {
15874
15991
  try {
15875
15992
  _id = new ObjectId46(_id);
15876
15993
  } catch (error) {
@@ -15881,7 +15998,11 @@ function useVehicleRepo() {
15881
15998
  ...value,
15882
15999
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15883
16000
  };
15884
- const res = await collection.updateOne({ _id }, { $set: updateValue });
16001
+ const res = await collection.updateOne(
16002
+ { _id },
16003
+ { $set: updateValue },
16004
+ { session }
16005
+ );
15885
16006
  if (res.modifiedCount === 0) {
15886
16007
  throw new InternalServerError28("Unable to update vehicle.");
15887
16008
  }
@@ -15960,8 +16081,126 @@ function useVehicleRepo() {
15960
16081
  );
15961
16082
  }
15962
16083
  }
16084
+ async function getVehiclesByNRIC({
16085
+ page = 1,
16086
+ limit = 10,
16087
+ search = "",
16088
+ sort = {}
16089
+ }) {
16090
+ page = page > 0 ? page - 1 : 0;
16091
+ const baseQuery = {
16092
+ deletedAt: ""
16093
+ };
16094
+ let query = { ...baseQuery };
16095
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
16096
+ const cacheOptions = {
16097
+ deletedAt: "",
16098
+ page: String(page),
16099
+ limit: String(limit),
16100
+ sort: JSON.stringify(sort)
16101
+ };
16102
+ if (search) {
16103
+ query.$text = { $search: search };
16104
+ cacheOptions.search = search;
16105
+ }
16106
+ const cacheKey = makeCacheKey26(namespace_collection, cacheOptions);
16107
+ const cachedData = await getCache(cacheKey);
16108
+ if (cachedData) {
16109
+ logger62.info(`Cache hit for key: ${cacheKey}`);
16110
+ return cachedData;
16111
+ }
16112
+ const escapeRegex = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16113
+ try {
16114
+ let items = [];
16115
+ let length = 0;
16116
+ items = await collection.aggregate([
16117
+ { $match: query },
16118
+ { $sort: sort },
16119
+ { $skip: page * limit },
16120
+ { $limit: limit },
16121
+ {
16122
+ $project: {
16123
+ name: 1,
16124
+ phoneNumber: 1,
16125
+ plateNumber: 1,
16126
+ recNo: 1,
16127
+ nric: 1,
16128
+ status: 1,
16129
+ type: 1
16130
+ }
16131
+ }
16132
+ ]).toArray();
16133
+ length = await collection.countDocuments(query);
16134
+ if ((!items || items.length === 0) && search) {
16135
+ const escaped = escapeRegex(search);
16136
+ const regexQuery = {
16137
+ ...baseQuery,
16138
+ $or: [
16139
+ { name: { $regex: escaped, $options: "i" } },
16140
+ { plateNumber: { $regex: escaped, $options: "i" } },
16141
+ { level: { $regex: escaped, $options: "i" } },
16142
+ { nric: { $regex: escaped, $options: "i" } }
16143
+ ]
16144
+ };
16145
+ items = await collection.aggregate([
16146
+ { $match: regexQuery },
16147
+ { $sort: sort },
16148
+ { $skip: page * limit },
16149
+ { $limit: limit },
16150
+ {
16151
+ $project: {
16152
+ name: 1,
16153
+ phoneNumber: 1,
16154
+ plateNumber: 1,
16155
+ recNo: 1,
16156
+ nric: 1,
16157
+ status: 1,
16158
+ type: 1
16159
+ }
16160
+ }
16161
+ ]).toArray();
16162
+ length = await collection.countDocuments(regexQuery);
16163
+ }
16164
+ const data = paginate21(items, page, limit, length);
16165
+ setCache(cacheKey, data, 15 * 60).then(() => logger62.info(`Cache set for key: ${cacheKey}`)).catch(
16166
+ (err) => logger62.error(`Failed to set cache for key: ${cacheKey}`, err)
16167
+ );
16168
+ return data;
16169
+ } catch (error) {
16170
+ throw error;
16171
+ }
16172
+ }
16173
+ async function deleteExpiredVehicles(session) {
16174
+ try {
16175
+ const tenYearsAgo = /* @__PURE__ */ new Date();
16176
+ tenYearsAgo.setFullYear(tenYearsAgo.getFullYear() - 10);
16177
+ const res = await collection.updateMany(
16178
+ {
16179
+ status: { $ne: "deleted" },
16180
+ end: { $exists: true, $lte: tenYearsAgo }
16181
+ // check only end
16182
+ },
16183
+ { $set: { status: "deleted", deletedAt: /* @__PURE__ */ new Date() } },
16184
+ { session }
16185
+ );
16186
+ if (res.modifiedCount === 0)
16187
+ throw new InternalServerError28("Unable to delete vehicle.");
16188
+ delNamespace().then(() => {
16189
+ logger62.info(`Cache cleared for namespace: ${namespace_collection}`);
16190
+ }).catch((err) => {
16191
+ logger62.error(
16192
+ `Failed to clear cache for namespace: ${namespace_collection}`,
16193
+ err
16194
+ );
16195
+ });
16196
+ return res.modifiedCount;
16197
+ } catch (error) {
16198
+ throw error;
16199
+ }
16200
+ }
15963
16201
  return {
15964
16202
  createIndex,
16203
+ createTextIndex,
15965
16204
  add,
15966
16205
  getVehicles,
15967
16206
  getSeasonPassTypes,
@@ -15969,7 +16208,9 @@ function useVehicleRepo() {
15969
16208
  updateVehicle,
15970
16209
  deleteVehicle,
15971
16210
  getByPlaceNumber,
15972
- getVehicleByPlateNumber
16211
+ getVehicleByPlateNumber,
16212
+ getVehiclesByNRIC,
16213
+ deleteExpiredVehicles
15973
16214
  };
15974
16215
  }
15975
16216
 
@@ -16355,7 +16596,7 @@ function useDahuaService() {
16355
16596
  username: Joi45.string().required(),
16356
16597
  password: Joi45.string().required(),
16357
16598
  plateNumber: Joi45.string().required(),
16358
- mode: Joi45.string().valid("TrafficBlackList", "TrafficRedList").required(),
16599
+ mode: Joi45.string().valid(...Object.values(ANPRMode)).required(),
16359
16600
  start: Joi45.string().isoDate().optional().allow("", null),
16360
16601
  end: Joi45.string().isoDate().optional().allow("", null),
16361
16602
  owner: Joi45.string().optional().allow("", null)
@@ -16379,7 +16620,7 @@ function useDahuaService() {
16379
16620
  host: value.host,
16380
16621
  username: value.username,
16381
16622
  password: value.password,
16382
- endpoint: `/cgi-bin/recordUpdater.cgi?action=insert&name=${value.mode}&PlateNumber=${value.plateNumber}&BeginTime=${value.start}&CancelTime=${value.end}&+OpenGate=true&MasterOfCar=${value.owner}`
16623
+ endpoint: `/cgi-bin/recordUpdater.cgi?action=insert&name=${value.mode}&PlateNumber=${value.plateNumber}&BeginTime=${value.start}&CancelTime=${value.end}&OpenGate=true&MasterOfCar=${value.owner}`
16383
16624
  });
16384
16625
  return response;
16385
16626
  } catch (error2) {
@@ -16427,18 +16668,148 @@ import {
16427
16668
  logger as logger64,
16428
16669
  useAtlas as useAtlas39
16429
16670
  } from "@7365admin1/node-server-utils";
16671
+ function formatDahuaDate(date) {
16672
+ const pad = (n) => String(n).padStart(2, "0");
16673
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(
16674
+ date.getDate()
16675
+ )} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(
16676
+ date.getSeconds()
16677
+ )}`;
16678
+ }
16430
16679
  function useVehicleService() {
16431
- const { add: _add, deleteVehicle: _deleteVehicle } = useVehicleRepo();
16680
+ const {
16681
+ add: _add,
16682
+ deleteVehicle: _deleteVehicle,
16683
+ updateVehicle: _updateVehicle,
16684
+ getVehicleById: _getVehicleById,
16685
+ deleteExpiredVehicles: _deleteExpiredVehicles
16686
+ } = useVehicleRepo();
16432
16687
  const {
16433
16688
  addPlateNumber: _addPlateNumber,
16434
16689
  removePlateNumber: _removePlateNumber
16435
16690
  } = useDahuaService();
16436
16691
  const { getAllCameraWithPassword: _getAllSiteCameras } = useSiteCameraRepo();
16692
+ const { getById: _getById } = useOrgRepo();
16437
16693
  async function add(value) {
16694
+ const session = useAtlas39.getClient()?.startSession();
16695
+ if (!session)
16696
+ throw new Error("Unable to start session for vehicle service.");
16697
+ const org = await _getById(value.org);
16698
+ if (!org)
16699
+ throw new BadRequestError80("Org not found");
16700
+ if (!Object.values(OrgNature).includes(org.nature)) {
16701
+ throw new BadRequestError80(
16702
+ "This organization is not allowed to add vehicles."
16703
+ );
16704
+ }
16705
+ value.status = "active" /* ACTIVE */;
16706
+ const addedBySecurity = org.nature === "security_agency" /* SECURITY_AGENCY */;
16707
+ const addedByPM = org.nature === "property_management_agency" /* PROPERTY_MANAGEMENT_AGENCY */;
16708
+ if (addedBySecurity)
16709
+ value.status = "pending" /* PENDING */;
16710
+ const isBlocklist = value.type === "blocklist" /* BLOCKLIST */;
16711
+ if (isBlocklist)
16712
+ value.status = "inactive" /* INACTIVE */;
16713
+ const _type = value.type;
16714
+ const _mode = isBlocklist ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
16715
+ const hasStart = typeof value.start === "string" ? value.start.trim() !== "" : !!value.start;
16716
+ const hasEnd = typeof value.end === "string" ? value.end.trim() !== "" : !!value.end;
16717
+ let startDate = null;
16718
+ let endDate = null;
16719
+ let start = "";
16720
+ let end = "";
16721
+ let startDateDahua;
16722
+ let endDateDahua;
16723
+ if (addedByPM && !hasStart && !hasEnd) {
16724
+ startDate = /* @__PURE__ */ new Date();
16725
+ endDate = new Date(startDate);
16726
+ endDate.setFullYear(endDate.getFullYear() + 10);
16727
+ start = startDate.toISOString();
16728
+ end = endDate.toISOString();
16729
+ startDateDahua = formatDahuaDate(startDate);
16730
+ endDateDahua = formatDahuaDate(endDate);
16731
+ } else {
16732
+ if (hasStart) {
16733
+ startDate = new Date(value.start);
16734
+ start = startDate.toISOString();
16735
+ startDateDahua = formatDahuaDate(startDate);
16736
+ }
16737
+ if (hasEnd) {
16738
+ endDate = new Date(value.end);
16739
+ end = endDate.toISOString();
16740
+ endDateDahua = formatDahuaDate(endDate);
16741
+ }
16742
+ }
16743
+ const owner = String(value.name ?? "").substring(0, 15);
16744
+ try {
16745
+ session.startTransaction();
16746
+ let message = "Vehicle plate number needs approval from property management.";
16747
+ if (value.status && value.status !== "pending" /* PENDING */) {
16748
+ const siteCameras = [];
16749
+ let page = 1;
16750
+ let pages = 1;
16751
+ const limit = 20;
16752
+ do {
16753
+ const siteCameraReq = await _getAllSiteCameras({
16754
+ site: value.site,
16755
+ type: "anpr",
16756
+ direction: ["both", "entry"],
16757
+ page,
16758
+ limit
16759
+ });
16760
+ pages = siteCameraReq.pages || 1;
16761
+ siteCameras.push(...siteCameraReq.items);
16762
+ page++;
16763
+ } while (page < pages);
16764
+ if (!siteCameras.length)
16765
+ throw new BadRequestError80("No site cameras found.");
16766
+ for (const camera of siteCameras) {
16767
+ const { host, username, password } = camera;
16768
+ const dahuaPayload = {
16769
+ host,
16770
+ username,
16771
+ password,
16772
+ plateNumber: value.plateNumber,
16773
+ mode: _mode,
16774
+ owner,
16775
+ ...startDateDahua ? { start: startDateDahua } : "",
16776
+ ...endDateDahua ? { end: endDateDahua } : ""
16777
+ };
16778
+ const dahuaResponse = await _addPlateNumber(dahuaPayload);
16779
+ if (dahuaResponse?.statusCode !== 200) {
16780
+ throw new BadRequestError80(
16781
+ `Failed to add plate number to ANPR ${_type}`
16782
+ );
16783
+ }
16784
+ const responseData = dahuaResponse?.data.toString("utf-8");
16785
+ value.recNo = responseData.split("=")[1]?.trim();
16786
+ message = `Vehicle plate number added to ${_type} successfully.`;
16787
+ }
16788
+ }
16789
+ const formattedValue = {
16790
+ ...value,
16791
+ start,
16792
+ // ISO string or ""
16793
+ end
16794
+ // ISO string or ""
16795
+ };
16796
+ await _add(formattedValue, session);
16797
+ await session.commitTransaction();
16798
+ return message;
16799
+ } catch (error) {
16800
+ logger64.error("Error in vehicle service add:", error);
16801
+ await session.abortTransaction();
16802
+ throw error;
16803
+ } finally {
16804
+ session.endSession();
16805
+ }
16806
+ }
16807
+ async function deleteVehicle(_id, recno, site, type, bypass = false) {
16438
16808
  const session = useAtlas39.getClient()?.startSession();
16439
16809
  if (!session) {
16440
16810
  throw new Error("Unable to start session for vehicle service.");
16441
16811
  }
16812
+ const _mode = type !== "whitelist" /* WHITELIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
16442
16813
  try {
16443
16814
  session.startTransaction();
16444
16815
  const siteCameras = [];
@@ -16447,9 +16818,8 @@ function useVehicleService() {
16447
16818
  const limit = 20;
16448
16819
  do {
16449
16820
  const siteCameraReq = await _getAllSiteCameras({
16450
- site: value.site,
16821
+ site,
16451
16822
  type: "anpr",
16452
- direction: ["both", "entry"],
16453
16823
  page,
16454
16824
  limit
16455
16825
  });
@@ -16461,55 +16831,82 @@ function useVehicleService() {
16461
16831
  throw new BadRequestError80("No site cameras found.");
16462
16832
  }
16463
16833
  for (const camera of siteCameras) {
16464
- const { host, username, password } = camera;
16834
+ const host = camera.host;
16835
+ const username = camera.username;
16836
+ const password = camera.password;
16465
16837
  const dahuaPayload = {
16466
16838
  host,
16467
16839
  username,
16468
16840
  password,
16469
- plateNumber: value.plateNumber,
16470
- mode: "TrafficRedList",
16471
- start: value.start ? new Date(value.start).toISOString() : "",
16472
- end: value.end ? new Date(value.end).toISOString() : "",
16473
- owner: value.name
16841
+ recno,
16842
+ mode: _mode
16474
16843
  };
16475
- const dahuaResponse = await _addPlateNumber(dahuaPayload);
16476
- if (dahuaResponse?.statusCode !== 200) {
16477
- throw new BadRequestError80("Failed to add plate number to ANPR");
16844
+ const dahuaResponse = await _removePlateNumber(dahuaPayload);
16845
+ if (!bypass && dahuaResponse?.statusCode !== 200) {
16846
+ throw new BadRequestError80(
16847
+ `Failed to remove plate number to ANPR from ${type}`
16848
+ );
16478
16849
  }
16479
- const responseData = dahuaResponse?.data.toString("utf-8");
16480
- value.recNo = responseData.split("=")[1]?.trim();
16481
- const formattedValue = {
16482
- ...value,
16483
- start: value.start ? new Date(value.start).toISOString() : "",
16484
- end: value.end ? new Date(value.end).toISOString() : ""
16485
- };
16486
- await _add(formattedValue, session);
16487
16850
  }
16851
+ await _deleteVehicle(_id, session);
16488
16852
  await session.commitTransaction();
16489
- return "Vehicle plate number added successfully.";
16853
+ return `Vehicle plate number deleted from ${type} record successfully.`;
16490
16854
  } catch (error) {
16491
- logger64.error("Error in vehicle service add:", error);
16492
16855
  await session.abortTransaction();
16493
- throw error;
16856
+ if (error instanceof AppError14)
16857
+ throw error;
16858
+ throw new InternalServerError29("Failed to delete vehicle");
16494
16859
  } finally {
16495
16860
  session.endSession();
16496
16861
  }
16497
16862
  }
16498
- async function deleteVehicle(_id, recno, site, bypass = false) {
16863
+ async function approveVehicleById(id, orgId, siteId) {
16499
16864
  const session = useAtlas39.getClient()?.startSession();
16500
16865
  if (!session) {
16501
16866
  throw new Error("Unable to start session for vehicle service.");
16502
16867
  }
16868
+ const org = await _getById(orgId);
16869
+ if (!org)
16870
+ throw new BadRequestError80("Org not found");
16871
+ const allowedNatures2 = ["property_management_agency"];
16872
+ if (!allowedNatures2.includes(org.nature)) {
16873
+ throw new BadRequestError80(
16874
+ "Only property management can approve vehicles."
16875
+ );
16876
+ }
16877
+ const vehicle = await _getVehicleById(id);
16878
+ if (!vehicle) {
16879
+ throw new BadRequestError80("Vehicle not found");
16880
+ }
16881
+ const owner = vehicle.name.substring(0, 15);
16882
+ const hasStart = typeof vehicle.start === "string" ? vehicle.start.trim() !== "" : !!vehicle.start;
16883
+ const hasEnd = typeof vehicle.end === "string" ? vehicle.end.trim() !== "" : !!vehicle.end;
16884
+ let startDate = null;
16885
+ let endDate = null;
16886
+ if (!hasStart && !hasEnd) {
16887
+ startDate = /* @__PURE__ */ new Date();
16888
+ endDate = new Date(startDate);
16889
+ endDate.setFullYear(endDate.getFullYear() + 10);
16890
+ } else {
16891
+ startDate = hasStart ? new Date(vehicle.start) : null;
16892
+ endDate = hasEnd ? new Date(vehicle.end) : null;
16893
+ }
16894
+ const startIso = startDate ? startDate.toISOString() : "";
16895
+ const endIso = endDate ? endDate.toISOString() : "";
16896
+ const startDahua = startDate ? formatDahuaDate(startDate) : "";
16897
+ const endDahua = endDate ? formatDahuaDate(endDate) : "";
16503
16898
  try {
16504
16899
  session.startTransaction();
16505
16900
  const siteCameras = [];
16506
16901
  let page = 1;
16507
16902
  let pages = 1;
16508
16903
  const limit = 20;
16904
+ let value = {};
16509
16905
  do {
16510
16906
  const siteCameraReq = await _getAllSiteCameras({
16511
- site,
16907
+ site: siteId,
16512
16908
  type: "anpr",
16909
+ direction: ["both", "entry"],
16513
16910
  page,
16514
16911
  limit
16515
16912
  });
@@ -16521,24 +16918,51 @@ function useVehicleService() {
16521
16918
  throw new BadRequestError80("No site cameras found.");
16522
16919
  }
16523
16920
  for (const camera of siteCameras) {
16524
- const host = camera.host;
16525
- const username = camera.username;
16526
- const password = camera.password;
16921
+ const { host, username, password } = camera;
16527
16922
  const dahuaPayload = {
16528
16923
  host,
16529
16924
  username,
16530
16925
  password,
16531
- recno,
16532
- mode: "TrafficRedList"
16926
+ plateNumber: vehicle.plateNumber,
16927
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
16928
+ start: startDahua,
16929
+ end: endDahua,
16930
+ owner
16533
16931
  };
16534
- const dahuaResponse = await _removePlateNumber(dahuaPayload);
16535
- if (!bypass && dahuaResponse?.statusCode !== 200) {
16536
- throw new BadRequestError80("Failed to remove plate number to ANPR");
16932
+ const dahuaResponse = await _addPlateNumber(dahuaPayload);
16933
+ if (dahuaResponse?.statusCode !== 200) {
16934
+ throw new BadRequestError80("Failed to add plate number to ANPR");
16537
16935
  }
16936
+ const responseData = dahuaResponse?.data.toString("utf-8");
16937
+ value.recNo = responseData.split("=")[1]?.trim();
16538
16938
  }
16539
- await _deleteVehicle(_id, session);
16939
+ value.status = "active";
16940
+ const formattedValue = {
16941
+ ...value,
16942
+ start: startIso,
16943
+ end: endIso
16944
+ };
16945
+ await _updateVehicle(id, formattedValue, session);
16946
+ await session.commitTransaction();
16947
+ return "Vehicle plate number approved and added successfully.";
16948
+ } catch (error) {
16949
+ logger64.error("Error in vehicle service add:", error);
16950
+ await session.abortTransaction();
16951
+ throw error;
16952
+ } finally {
16953
+ session.endSession();
16954
+ }
16955
+ }
16956
+ async function processDeletingExpiredVehicles() {
16957
+ const session = useAtlas39.getClient()?.startSession();
16958
+ if (!session) {
16959
+ throw new Error("Unable to start session for vehicle service.");
16960
+ }
16961
+ try {
16962
+ session.startTransaction();
16963
+ await _deleteExpiredVehicles();
16540
16964
  await session.commitTransaction();
16541
- return "Vehicle plate number deleted successfully.";
16965
+ return `Expired Vehicle plate numbers deleted successfully.`;
16542
16966
  } catch (error) {
16543
16967
  await session.abortTransaction();
16544
16968
  if (error instanceof AppError14)
@@ -16550,7 +16974,9 @@ function useVehicleService() {
16550
16974
  }
16551
16975
  return {
16552
16976
  add,
16553
- deleteVehicle
16977
+ deleteVehicle,
16978
+ approveVehicleById,
16979
+ processDeletingExpiredVehicles
16554
16980
  };
16555
16981
  }
16556
16982
 
@@ -16558,12 +16984,17 @@ function useVehicleService() {
16558
16984
  import { BadRequestError as BadRequestError81, logger as logger65 } from "@7365admin1/node-server-utils";
16559
16985
  import Joi46 from "joi";
16560
16986
  function useVehicleController() {
16561
- const { add: _add, deleteVehicle: _deleteVehicle } = useVehicleService();
16987
+ const {
16988
+ add: _add,
16989
+ deleteVehicle: _deleteVehicle,
16990
+ approveVehicleById: _approveVehicleById
16991
+ } = useVehicleService();
16562
16992
  const {
16563
16993
  getSeasonPassTypes: _getSeasonPassTypes,
16564
16994
  getVehicles: _getVehicles,
16565
16995
  getVehicleById: _getVehicleById,
16566
- updateVehicle: _updateVehicle
16996
+ updateVehicle: _updateVehicle,
16997
+ getVehiclesByNRIC: _getVehiclesByNRIC
16567
16998
  } = useVehicleRepo();
16568
16999
  async function add(req, res, next) {
16569
17000
  const payload = req.body;
@@ -16594,8 +17025,9 @@ function useVehicleController() {
16594
17025
  limit: Joi46.number().integer().min(1).max(100).allow("", null).default(10),
16595
17026
  sort: Joi46.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
16596
17027
  order: Joi46.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
16597
- type: Joi46.string().optional().allow("", ...allowedTypes),
16598
- category: Joi46.string().optional().allow("", ...allowedCategories)
17028
+ type: Joi46.string().optional().valid(...Object.values(VehicleType)),
17029
+ category: Joi46.string().optional().valid(...Object.values(VehicleCategory)),
17030
+ status: Joi46.string().optional().valid(...Object.values(VehicleStatus)).allow("")
16599
17031
  });
16600
17032
  const query = { ...req.query };
16601
17033
  const { error } = validation.validate(query);
@@ -16609,6 +17041,7 @@ function useVehicleController() {
16609
17041
  const limit = parseInt(req.query.limit ?? "10");
16610
17042
  const type = req.query.type ?? "";
16611
17043
  const category = req.query.category ?? "";
17044
+ const status = req.query.status ?? "";
16612
17045
  const sortObj = {};
16613
17046
  const sortFields = String(req.query.sort).split(",");
16614
17047
  const sortOrders = String(req.query.order).split(",");
@@ -16625,7 +17058,8 @@ function useVehicleController() {
16625
17058
  limit,
16626
17059
  sort: sortObj,
16627
17060
  type,
16628
- category
17061
+ category,
17062
+ status
16629
17063
  });
16630
17064
  res.json(data);
16631
17065
  return;
@@ -16681,7 +17115,8 @@ function useVehicleController() {
16681
17115
  block: Joi46.number().integer().optional().allow(0, null),
16682
17116
  level: Joi46.string().optional().allow("", null),
16683
17117
  unit: Joi46.string().optional().allow("", null),
16684
- plateNumber: Joi46.string().optional().allow("", null)
17118
+ plateNumber: Joi46.string().optional().allow("", null),
17119
+ nric: Joi46.string().optional().allow("", null)
16685
17120
  });
16686
17121
  const _id = req.params.id;
16687
17122
  const payload = { ...req.body };
@@ -16702,23 +17137,23 @@ function useVehicleController() {
16702
17137
  }
16703
17138
  }
16704
17139
  async function deleteVehicle(req, res, next) {
16705
- const site = req.params.site ?? "";
16706
- const _id = req.params.id ?? "";
16707
- const recno = req.params.recno ?? "";
16708
- const bypass = req.query.bypass === "true";
17140
+ const _id = req.params.id;
16709
17141
  const deleteVehicleSchema = Joi46.object({
16710
- _id: Joi46.string().hex().required(),
17142
+ _id: Joi46.string().hex().length(24).required(),
16711
17143
  recno: Joi46.string().required(),
16712
- bypass: Joi46.boolean().optional()
17144
+ site: Joi46.string().hex().length(24).required(),
17145
+ type: Joi46.string().valid("whitelist", "blocklist").required(),
17146
+ bypass: Joi46.boolean().optional().default(true)
16713
17147
  });
16714
- const { error } = deleteVehicleSchema.validate({ _id, recno, bypass });
17148
+ const { error, value } = deleteVehicleSchema.validate({ _id, ...req.body });
16715
17149
  if (error) {
16716
17150
  logger65.log({ level: "error", message: error.message });
16717
17151
  next(new BadRequestError81(error.message));
16718
17152
  return;
16719
17153
  }
17154
+ const { recno, site, type, bypass } = value;
16720
17155
  try {
16721
- const data = await _deleteVehicle(_id, recno, site, bypass);
17156
+ const data = await _deleteVehicle(_id, recno, site, type, bypass);
16722
17157
  res.json({
16723
17158
  message: "Vehicle deleted successfully.",
16724
17159
  data
@@ -16729,13 +17164,83 @@ function useVehicleController() {
16729
17164
  return;
16730
17165
  }
16731
17166
  }
17167
+ async function approveVehicleById(req, res, next) {
17168
+ const validation = Joi46.object({
17169
+ _id: Joi46.string().hex().length(24).required(),
17170
+ org: Joi46.string().hex().length(24).required(),
17171
+ site: Joi46.string().hex().length(24).required()
17172
+ });
17173
+ const _id = req.params.id;
17174
+ const payload = { ...req.body };
17175
+ const { error, value } = validation.validate({ _id, ...payload });
17176
+ if (error) {
17177
+ logger65.log({ level: "error", message: error.message });
17178
+ next(new BadRequestError81(error.message));
17179
+ return;
17180
+ }
17181
+ try {
17182
+ await _approveVehicleById(value._id, value.org, value.site);
17183
+ res.json({ message: "Successfully approved and updated vehicle." });
17184
+ return;
17185
+ } catch (error2) {
17186
+ logger65.log({ level: "error", message: error2.message });
17187
+ next(error2);
17188
+ return;
17189
+ }
17190
+ }
17191
+ async function getVehiclesByNRIC(req, res, next) {
17192
+ const allowedFields = ["start", "end"];
17193
+ const allowedOrder = ["asc", "desc"];
17194
+ const validation = Joi46.object({
17195
+ search: Joi46.string().optional().allow("", null),
17196
+ page: Joi46.number().integer().min(1).allow("", null).default(1),
17197
+ limit: Joi46.number().integer().min(1).max(100).allow("", null).default(10),
17198
+ sort: Joi46.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
17199
+ order: Joi46.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder)
17200
+ });
17201
+ const query = { ...req.query };
17202
+ const { error } = validation.validate(query);
17203
+ if (error) {
17204
+ logger65.log({ level: "error", message: error.message });
17205
+ next(new BadRequestError81(error.message));
17206
+ return;
17207
+ }
17208
+ const search = req.query.search ?? "";
17209
+ const page = parseInt(req.query.page ?? "1");
17210
+ const limit = parseInt(req.query.limit ?? "10");
17211
+ const sortObj = {};
17212
+ const sortFields = String(req.query.sort).split(",");
17213
+ const sortOrders = String(req.query.order).split(",");
17214
+ sortFields.forEach((field, index) => {
17215
+ if (allowedFields.includes(field)) {
17216
+ const order = sortOrders[index] === "asc" ? 1 : -1;
17217
+ sortObj[field] = order;
17218
+ }
17219
+ });
17220
+ try {
17221
+ const data = await _getVehiclesByNRIC({
17222
+ search,
17223
+ page,
17224
+ limit,
17225
+ sort: sortObj
17226
+ });
17227
+ res.json(data);
17228
+ return;
17229
+ } catch (error2) {
17230
+ logger65.log({ level: "error", message: error2.message });
17231
+ next(error2);
17232
+ return;
17233
+ }
17234
+ }
16732
17235
  return {
16733
17236
  add,
16734
17237
  getVehicles,
16735
17238
  getSeasonPassTypes,
16736
17239
  getVehicleById,
16737
17240
  updateVehicle,
16738
- deleteVehicle
17241
+ deleteVehicle,
17242
+ approveVehicleById,
17243
+ getVehiclesByNRIC
16739
17244
  };
16740
17245
  }
16741
17246
 
@@ -16837,7 +17342,7 @@ function useSiteCameraController() {
16837
17342
  }
16838
17343
  async function getAll(req, res, next) {
16839
17344
  const query = req.query;
16840
- const allowedTypes2 = ["ip", "exit", "entry", "both"];
17345
+ const allowedTypes = ["ip", "exit", "entry", "both"];
16841
17346
  const validation = Joi47.object({
16842
17347
  type: Joi47.string().optional(),
16843
17348
  site: Joi47.string().optional(),
@@ -24322,20 +24827,19 @@ function useDocumentManagementController() {
24322
24827
  // src/models/bulletin-board.model.ts
24323
24828
  import Joi75 from "joi";
24324
24829
  import { ObjectId as ObjectId71 } from "mongodb";
24830
+ var BULLETIN_RECIPIENTS = [
24831
+ "resident",
24832
+ "security_agency",
24833
+ "cleaning_services",
24834
+ "mechanical_electrical",
24835
+ "property_management_agency"
24836
+ ];
24837
+ var STATUS_VALUES = ["active", "expired", "deleted"];
24325
24838
  var schemaBulletinBoard = Joi75.object({
24326
24839
  _id: Joi75.string().hex().optional().allow("", null),
24327
24840
  site: Joi75.string().hex().optional().allow("", null),
24328
24841
  orgId: Joi75.string().hex().optional().allow("", null),
24329
- recipients: Joi75.array().items(
24330
- Joi75.string().valid(
24331
- "admin",
24332
- "organization",
24333
- "site",
24334
- "service-provider",
24335
- "service-provider-member",
24336
- "resident"
24337
- )
24338
- ).optional(),
24842
+ recipients: Joi75.array().items(Joi75.string().valid(...BULLETIN_RECIPIENTS)).unique().optional(),
24339
24843
  title: Joi75.string().optional().allow("", null),
24340
24844
  content: Joi75.string().optional().allow("", null),
24341
24845
  file: Joi75.array().items(
@@ -24348,23 +24852,14 @@ var schemaBulletinBoard = Joi75.object({
24348
24852
  noExpiration: Joi75.boolean().optional(),
24349
24853
  startDate: Joi75.date().optional().allow("", null),
24350
24854
  endDate: Joi75.date().optional().allow("", null),
24351
- status: Joi75.string().optional().allow("", null),
24855
+ status: Joi75.string().valid(...STATUS_VALUES).optional(),
24352
24856
  createdAt: Joi75.date().optional().allow(null),
24353
24857
  updatedAt: Joi75.date().optional().allow(null),
24354
24858
  deletedAt: Joi75.date().optional().allow(null)
24355
24859
  });
24356
24860
  var schemaUpdateBulletinBoard = Joi75.object({
24357
24861
  _id: Joi75.string().hex().required(),
24358
- recipients: Joi75.array().items(
24359
- Joi75.string().valid(
24360
- "admin",
24361
- "organization",
24362
- "site",
24363
- "service-provider",
24364
- "service-provider-member",
24365
- "resident"
24366
- )
24367
- ).optional(),
24862
+ recipients: Joi75.array().items(Joi75.string().valid(...BULLETIN_RECIPIENTS)).unique().optional(),
24368
24863
  title: Joi75.string().optional().allow("", null),
24369
24864
  content: Joi75.string().optional().allow("", null),
24370
24865
  file: Joi75.array().items(
@@ -24377,7 +24872,7 @@ var schemaUpdateBulletinBoard = Joi75.object({
24377
24872
  noExpiration: Joi75.boolean().optional(),
24378
24873
  startDate: Joi75.date().optional().allow("", null),
24379
24874
  endDate: Joi75.date().optional().allow("", null),
24380
- status: Joi75.string().optional().allow("", null)
24875
+ status: Joi75.string().valid(...STATUS_VALUES).optional()
24381
24876
  });
24382
24877
  function MBulletinBoard(value) {
24383
24878
  const { error } = schemaBulletinBoard.validate(value);
@@ -24477,7 +24972,8 @@ function useBulletinBoardRepo() {
24477
24972
  limit = 10,
24478
24973
  sort = {},
24479
24974
  site = "",
24480
- status = "active"
24975
+ status = "active",
24976
+ recipients = []
24481
24977
  }, session) {
24482
24978
  page = page > 0 ? page - 1 : 0;
24483
24979
  try {
@@ -24487,7 +24983,9 @@ function useBulletinBoardRepo() {
24487
24983
  }
24488
24984
  const query = {
24489
24985
  site,
24490
- status
24986
+ status,
24987
+ ...search && { $text: { $search: search } },
24988
+ ...recipients?.length && { recipients: { $in: recipients } }
24491
24989
  };
24492
24990
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
24493
24991
  const cacheOptions = {
@@ -24495,12 +24993,10 @@ function useBulletinBoardRepo() {
24495
24993
  status,
24496
24994
  sort: JSON.stringify(sort),
24497
24995
  page,
24498
- limit
24996
+ limit,
24997
+ ...search && { search },
24998
+ ...recipients?.length && { recipients: recipients.sort().join(",") }
24499
24999
  };
24500
- if (search) {
24501
- query.$text = { $search: search };
24502
- cacheOptions.search = search;
24503
- }
24504
25000
  const cacheKey = makeCacheKey39(namespace_collection, cacheOptions);
24505
25001
  const cachedData = await getCache(cacheKey);
24506
25002
  if (cachedData) {
@@ -24592,7 +25088,7 @@ function useBulletinBoardRepo() {
24592
25088
  throw error;
24593
25089
  }
24594
25090
  }
24595
- async function deleteBulletinBoardById(_id) {
25091
+ async function deleteBulletinBoardById(_id, session) {
24596
25092
  try {
24597
25093
  _id = new ObjectId72(_id);
24598
25094
  } catch (error) {
@@ -24604,7 +25100,11 @@ function useBulletinBoardRepo() {
24604
25100
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
24605
25101
  deletedAt: (/* @__PURE__ */ new Date()).toISOString()
24606
25102
  };
24607
- const res = await collection.updateOne({ _id }, { $set: updateValue });
25103
+ const res = await collection.updateOne(
25104
+ { _id },
25105
+ { $set: updateValue },
25106
+ { session }
25107
+ );
24608
25108
  if (res.modifiedCount === 0) {
24609
25109
  throw new InternalServerError42("Unable to delete bulletin board.");
24610
25110
  }
@@ -24670,8 +25170,11 @@ function useBulletinBoardService() {
24670
25170
  const {
24671
25171
  add: _add,
24672
25172
  updateBulletinBoardById: _updateBulletinBoardById,
24673
- processExpiredBulletinBoards: _processExpiredBulletinBoards
25173
+ processExpiredBulletinBoards: _processExpiredBulletinBoards,
25174
+ deleteBulletinBoardById: _deleteBulletinBoardById,
25175
+ getBulletinBoardById: _getBulletinBoardById
24674
25176
  } = useBulletinBoardRepo();
25177
+ const { deleteFileById: _deleteFileById } = useFileRepo();
24675
25178
  async function add(value) {
24676
25179
  const session = useAtlas65.getClient()?.startSession();
24677
25180
  session?.startTransaction();
@@ -24714,10 +25217,31 @@ function useBulletinBoardService() {
24714
25217
  session?.endSession();
24715
25218
  }
24716
25219
  }
25220
+ async function deleteBulletinBoardById(id) {
25221
+ const session = useAtlas65.getClient()?.startSession();
25222
+ session?.startTransaction();
25223
+ try {
25224
+ const existingBulletinBoard = await _getBulletinBoardById(id);
25225
+ if (Array.isArray(existingBulletinBoard.file)) {
25226
+ for (const file of existingBulletinBoard.file) {
25227
+ await _deleteFileById(file, session);
25228
+ }
25229
+ }
25230
+ await _deleteBulletinBoardById(id, session);
25231
+ await session?.commitTransaction();
25232
+ return "Successfully deleted bulletin board.";
25233
+ } catch (error) {
25234
+ await session?.abortTransaction();
25235
+ throw error;
25236
+ } finally {
25237
+ session?.endSession();
25238
+ }
25239
+ }
24717
25240
  return {
24718
25241
  add,
24719
25242
  updateBulletinBoardById,
24720
- processExpiredBulletinBoards
25243
+ processExpiredBulletinBoards,
25244
+ deleteBulletinBoardById
24721
25245
  };
24722
25246
  }
24723
25247
 
@@ -24725,12 +25249,12 @@ function useBulletinBoardService() {
24725
25249
  import { BadRequestError as BadRequestError123, logger as logger104 } from "@7365admin1/node-server-utils";
24726
25250
  import Joi76 from "joi";
24727
25251
  function useBulletinBoardController() {
24728
- const { add: _add, updateBulletinBoardById: _updateBulletinBoardById } = useBulletinBoardService();
24729
25252
  const {
24730
- getAll: _getAll,
24731
- getBulletinBoardById: _getBulletinBoardById,
25253
+ add: _add,
25254
+ updateBulletinBoardById: _updateBulletinBoardById,
24732
25255
  deleteBulletinBoardById: _deleteBulletinBoardById
24733
- } = useBulletinBoardRepo();
25256
+ } = useBulletinBoardService();
25257
+ const { getAll: _getAll, getBulletinBoardById: _getBulletinBoardById } = useBulletinBoardRepo();
24734
25258
  async function add(req, res, next) {
24735
25259
  const payload = { ...req.body };
24736
25260
  const { error } = schemaBulletinBoard.validate(payload, {
@@ -24762,12 +25286,23 @@ function useBulletinBoardController() {
24762
25286
  sort: Joi76.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
24763
25287
  order: Joi76.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
24764
25288
  site: Joi76.string().hex().required(),
24765
- status: Joi76.string().optional().allow(null, "")
25289
+ status: Joi76.string().optional().allow(null, ""),
25290
+ recipients: Joi76.string().optional().allow("", null).custom((value, helpers) => {
25291
+ const parsed = value.split(",").map((v) => v.trim()).filter(Boolean);
25292
+ for (const r of parsed) {
25293
+ if (!BULLETIN_RECIPIENTS.includes(r)) {
25294
+ return helpers.error("any.only");
25295
+ }
25296
+ }
25297
+ return value;
25298
+ }).messages({
25299
+ "any.only": `Recipients must be one of: ${BULLETIN_RECIPIENTS.join(
25300
+ ", "
25301
+ )}`
25302
+ })
24766
25303
  });
24767
25304
  const query = { ...req.query };
24768
- const { error } = validation.validate(query, {
24769
- abortEarly: false
24770
- });
25305
+ const { error } = validation.validate(query, { abortEarly: false });
24771
25306
  if (error) {
24772
25307
  const messages = error.details.map((d) => d.message).join(", ");
24773
25308
  logger104.log({ level: "error", message: messages });
@@ -24779,9 +25314,15 @@ function useBulletinBoardController() {
24779
25314
  const limit = parseInt(req.query.limit ?? "10");
24780
25315
  const site = req.query.site ?? "";
24781
25316
  const status = req.query.status ?? "active";
25317
+ const recipientsRaw = req.query.recipients ?? "";
25318
+ const recipients = recipientsRaw && typeof recipientsRaw === "string" ? Array.from(
25319
+ new Set(
25320
+ recipientsRaw.split(",").map((r) => r.trim()).filter(Boolean)
25321
+ )
25322
+ ) : [];
24782
25323
  const sortObj = {};
24783
- const sortFields = String(req.query.sort).split(",");
24784
- const sortOrders = String(req.query.order).split(",");
25324
+ const sortFields = String(req.query.sort ?? "").split(",").filter(Boolean);
25325
+ const sortOrders = String(req.query.order ?? "").split(",").filter(Boolean);
24785
25326
  sortFields.forEach((field, index) => {
24786
25327
  if (allowedFields.includes(field)) {
24787
25328
  const order = sortOrders[index] === "asc" ? 1 : -1;
@@ -24795,7 +25336,8 @@ function useBulletinBoardController() {
24795
25336
  limit,
24796
25337
  sort: sortObj,
24797
25338
  site,
24798
- status
25339
+ status,
25340
+ recipients
24799
25341
  });
24800
25342
  res.status(200).json(data);
24801
25343
  return;
@@ -24878,6 +25420,16 @@ function useBulletinBoardController() {
24878
25420
  import { BadRequestError as BadRequestError124, logger as logger105 } from "@7365admin1/node-server-utils";
24879
25421
  import { ObjectId as ObjectId73 } from "mongodb";
24880
25422
  import Joi77 from "joi";
25423
+
25424
+ // src/types/enums/billing-frequency.enum.ts
25425
+ var EBillingFrequency = /* @__PURE__ */ ((EBillingFrequency2) => {
25426
+ EBillingFrequency2["MONTHLY"] = "monthly";
25427
+ EBillingFrequency2["QAURTERLY"] = "quarterly";
25428
+ EBillingFrequency2["ANNUALLY"] = "annually";
25429
+ return EBillingFrequency2;
25430
+ })(EBillingFrequency || {});
25431
+
25432
+ // src/models/site-billing-item.model.ts
24881
25433
  var schemaUnits = Joi77.object({
24882
25434
  _id: Joi77.string().hex().optional(),
24883
25435
  name: Joi77.string().optional()
@@ -24888,7 +25440,7 @@ var schemaBillingItem = Joi77.object({
24888
25440
  org: Joi77.string().hex().required(),
24889
25441
  name: Joi77.string().required(),
24890
25442
  amount: Joi77.number().required(),
24891
- frequency: Joi77.string().valid("month", "quarter", "annual").required(),
25443
+ frequency: Joi77.string().valid(...Object.values(EBillingFrequency)).required(),
24892
25444
  billingType: Joi77.string().valid("recurring", "non-recurring").required(),
24893
25445
  dueInDays: Joi77.number().optional().allow(null, ""),
24894
25446
  date: Joi77.string().required(),
@@ -24913,7 +25465,7 @@ var schemaUpdateSiteBillingItem = Joi77.object({
24913
25465
  org: Joi77.string().hex().optional().allow(null, ""),
24914
25466
  name: Joi77.string().optional().allow(null, ""),
24915
25467
  amount: Joi77.number().optional().allow(null, ""),
24916
- frequency: Joi77.string().valid("month", "quarter", "annual").optional().allow(null, ""),
25468
+ frequency: Joi77.string().valid(...Object.values(EBillingFrequency)).optional().allow(null, ""),
24917
25469
  billingType: Joi77.string().valid("recurring", "non-recurring").optional().allow(null, ""),
24918
25470
  dueInDays: Joi77.number().optional().allow(null, ""),
24919
25471
  date: Joi77.string().optional().allow(null, ""),
@@ -25067,7 +25619,16 @@ function useSiteBillingItemRepo() {
25067
25619
  ...search && {
25068
25620
  $or: [
25069
25621
  { name: { $regex: search, $options: "i" } },
25070
- { frequency: { $regex: search, $options: "i" } }
25622
+ { frequency: { $regex: search, $options: "i" } },
25623
+ {
25624
+ $expr: {
25625
+ $regexMatch: {
25626
+ input: { $toString: "$totalAmount" },
25627
+ regex: search,
25628
+ options: "i"
25629
+ }
25630
+ }
25631
+ }
25071
25632
  ]
25072
25633
  },
25073
25634
  ...ObjectId74.isValid(site) && { site: new ObjectId74(site) }
@@ -26243,7 +26804,8 @@ function useEventManagementRepo() {
26243
26804
  sort = {},
26244
26805
  site = "",
26245
26806
  status = "",
26246
- type = ""
26807
+ type = "",
26808
+ date = ""
26247
26809
  }, session) {
26248
26810
  page = page > 0 ? page - 1 : 0;
26249
26811
  try {
@@ -26254,7 +26816,13 @@ function useEventManagementRepo() {
26254
26816
  const baseQuery = {
26255
26817
  site,
26256
26818
  status: status ? status : { $ne: "deleted" },
26257
- ...type && { type }
26819
+ ...type && { type },
26820
+ ...date && {
26821
+ dateTime: {
26822
+ $gte: `${date}T00:00:00.000Z`,
26823
+ $lt: `${date}T23:59:59.999Z`
26824
+ }
26825
+ }
26258
26826
  };
26259
26827
  let query = { ...baseQuery };
26260
26828
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
@@ -26552,7 +27120,8 @@ function useEventManagementController() {
26552
27120
  order: Joi82.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
26553
27121
  site: Joi82.string().hex().required(),
26554
27122
  status: Joi82.string().optional(),
26555
- type: Joi82.string().optional().valid("TASK", "EVENT").allow(null, "")
27123
+ type: Joi82.string().optional().valid("TASK", "EVENT").allow(null, ""),
27124
+ date: Joi82.string().optional().allow(null, "")
26556
27125
  });
26557
27126
  const query = { ...req.query };
26558
27127
  const { error } = validation.validate(query, {
@@ -26570,6 +27139,7 @@ function useEventManagementController() {
26570
27139
  const site = req.query.site ?? "";
26571
27140
  const status = req.query.status ?? "";
26572
27141
  const type = req.query.type ?? "";
27142
+ const date = req.query.date ?? "";
26573
27143
  const sortObj = {};
26574
27144
  const sortFields = String(req.query.sort).split(",");
26575
27145
  const sortOrders = String(req.query.order).split(",");
@@ -26587,7 +27157,8 @@ function useEventManagementController() {
26587
27157
  sort: sortObj,
26588
27158
  site,
26589
27159
  status,
26590
- type
27160
+ type,
27161
+ date
26591
27162
  });
26592
27163
  res.status(200).json(data);
26593
27164
  return;
@@ -26913,6 +27484,7 @@ function useSiteUnitBillingRepo() {
26913
27484
  }
26914
27485
  };
26915
27486
  }
27487
+ const unitSearchRegex = search ? search.trim().replace(/\s+/g, "").replace(/\//g, "\\s*/\\s*") : null;
26916
27488
  const query = {
26917
27489
  paymentStatus,
26918
27490
  status,
@@ -26920,7 +27492,16 @@ function useSiteUnitBillingRepo() {
26920
27492
  $or: [
26921
27493
  { unitOwner: { $regex: search, $options: "i" } },
26922
27494
  { billName: { $regex: search, $options: "i" } },
26923
- { unit: { $regex: search, $options: "i" } }
27495
+ { unit: { $regex: unitSearchRegex, $options: "i" } },
27496
+ {
27497
+ $expr: {
27498
+ $regexMatch: {
27499
+ input: { $toString: "$amountPaid" },
27500
+ regex: search,
27501
+ options: "i"
27502
+ }
27503
+ }
27504
+ }
26924
27505
  ]
26925
27506
  },
26926
27507
  ...ObjectId80.isValid(site) && { site: new ObjectId80(site) },
@@ -27347,14 +27928,14 @@ function useSiteUnitBillingService() {
27347
27928
  function isBillingChecker(billing_item, todayMonth, todayDate) {
27348
27929
  const billingMonth = Number(billing_item.month);
27349
27930
  const billingDay = Number(billing_item.date);
27350
- if (billing_item.frequency === "month") {
27931
+ if (billing_item.frequency === "monthly" /* MONTHLY */) {
27351
27932
  return todayDate === billingDay;
27352
27933
  }
27353
- if (billing_item.frequency === "quarter") {
27934
+ if (billing_item.frequency === "quarterly" /* QAURTERLY */) {
27354
27935
  const monthDiff = todayMonth - billingMonth;
27355
27936
  return monthDiff % 3 === 0 && todayDate === billingDay;
27356
27937
  }
27357
- if (billing_item.frequency === "annual") {
27938
+ if (billing_item.frequency === "annually" /* ANNUALLY */) {
27358
27939
  return todayMonth === billingMonth && todayDate === billingDay;
27359
27940
  }
27360
27941
  return false;
@@ -28150,7 +28731,7 @@ function UseAccessManagementRepo() {
28150
28731
  {
28151
28732
  $match: {
28152
28733
  ...defaultQuery,
28153
- status: { $ne: "deleted" }
28734
+ status: { $eq: "active" }
28154
28735
  }
28155
28736
  },
28156
28737
  // ✅ Only project needed fields before heavy lookups
@@ -28270,7 +28851,7 @@ function UseAccessManagementRepo() {
28270
28851
  userType
28271
28852
  }
28272
28853
  },
28273
- { $project: { _id: 1, userId: 1, type: 1, cardNo: 1, isActivated: 1 } }
28854
+ { $project: { _id: 1, userId: 1, type: 1, cardNo: 1, isActivated: 1, replacementStatus: 1 } }
28274
28855
  ],
28275
28856
  as: "accessCards"
28276
28857
  }
@@ -28289,7 +28870,21 @@ function UseAccessManagementRepo() {
28289
28870
  $filter: {
28290
28871
  input: "$accessCards",
28291
28872
  as: "card",
28292
- cond: { $ne: ["$$card.userId", null] }
28873
+ cond: { $and: [{ $ne: ["$$card.userId", null] }, { $eq: ["$$card.isActivated", true] }] }
28874
+ }
28875
+ },
28876
+ f_replaced: {
28877
+ $filter: {
28878
+ input: "$accessCards",
28879
+ as: "card",
28880
+ cond: { $and: [{ $eq: ["$$card.isActivated", false] }, { $ne: ["$$card.replacementStatus", null] }] }
28881
+ }
28882
+ },
28883
+ f_deleted: {
28884
+ $filter: {
28885
+ input: "$accessCards",
28886
+ as: "card",
28887
+ cond: { $and: [{ $eq: ["$$card.isActivated", false] }, { $eq: ["$$card.replacementStatus", null] }] }
28293
28888
  }
28294
28889
  }
28295
28890
  }
@@ -28321,6 +28916,14 @@ function UseAccessManagementRepo() {
28321
28916
  non_physical: { $size: { $filter: { input: { $ifNull: ["$f_Assigned", []] }, as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } } }
28322
28917
  }
28323
28918
  },
28919
+ replaced: {
28920
+ physical: { $filter: { input: "$f_replaced", as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } },
28921
+ non_physical: { $filter: { input: "$f_replaced", as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } }
28922
+ },
28923
+ deleted: {
28924
+ physical: { $filter: { input: "$f_deleted", as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } },
28925
+ non_physical: { $filter: { input: "$f_deleted", as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } }
28926
+ },
28324
28927
  totalCardCount: {
28325
28928
  $add: [
28326
28929
  { $size: { $filter: { input: { $ifNull: ["$f_Available", []] }, as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } } },
@@ -28777,16 +29380,26 @@ function UseAccessManagementRepo() {
28777
29380
  async function cardReplacementRepo(params) {
28778
29381
  const session = useAtlas74.getClient()?.startSession();
28779
29382
  try {
28780
- const { cardId, remarks } = params;
29383
+ const { cardId, remarks, issuedCardId, unitId, userId } = params;
28781
29384
  const id = new ObjectId83(cardId);
29385
+ const newCardId = new ObjectId83(issuedCardId);
29386
+ const unit = new ObjectId83(unitId);
29387
+ const user = new ObjectId83(userId);
28782
29388
  session?.startTransaction();
28783
- const card = await collection().findOneAndUpdate(
28784
- { _id: id },
28785
- { $set: { remarks, replacementStatus: "Pending", requestDate: /* @__PURE__ */ new Date(), isActivated: false } },
28786
- { returnDocument: "after", session }
28787
- );
29389
+ const sessionResult = await Promise.all([
29390
+ await collection().findOneAndUpdate(
29391
+ { _id: id },
29392
+ { $set: { remarks, replacementStatus: "Complete", requestDate: /* @__PURE__ */ new Date(), isActivated: false } },
29393
+ { returnDocument: "after", session }
29394
+ ),
29395
+ await collection().findOneAndUpdate(
29396
+ { _id: newCardId },
29397
+ { $set: { updatedAt: /* @__PURE__ */ new Date(), assignedUnit: unit, userId: user } },
29398
+ { returnDocument: "after", session }
29399
+ )
29400
+ ]);
28788
29401
  await session?.commitTransaction();
28789
- return card;
29402
+ return sessionResult;
28790
29403
  } catch (error) {
28791
29404
  await session?.abortTransaction();
28792
29405
  throw new Error(error.message);
@@ -29035,7 +29648,8 @@ function UseAccessManagementRepo() {
29035
29648
  rawItems.map(async (item) => {
29036
29649
  const date = new Date(item["startDate (format MM/DD/YYYY)"]);
29037
29650
  const endDate = new Date(date.setFullYear(date.getFullYear() + 10));
29038
- const cardNumber = item["cardNo (number 0-65535 ex. 301)"].toString().padStart(10, "0");
29651
+ const cardNumber = String(Number(item["cardNo (number 0-65535 ex. 301)"] || 0)).padStart(6, "0");
29652
+ const facilityCode = String(Number(item["facilityCode (number 0-255 ex. 11)"] || 0)).padStart(4, "0");
29039
29653
  const pin = item["pin (number 6 digits only)"] ? item["pin (number 6 digits only)"].toString().padStart(6, "0") : "123456";
29040
29654
  const match = item["accessLevel (number ex. 1)"];
29041
29655
  const accessLevel = match ? match : null;
@@ -29049,9 +29663,9 @@ function UseAccessManagementRepo() {
29049
29663
  accessLevel,
29050
29664
  accessGroup,
29051
29665
  accessType: "Normal" /* NORMAL */,
29052
- cardNo: cardNumber,
29666
+ cardNo: `${facilityCode}${cardNumber}`,
29053
29667
  pin,
29054
- qrData: await createQrData({ cardNumber }),
29668
+ qrData: await createQrData({ cardNumber: `${facilityCode}${cardNumber}` }),
29055
29669
  startDate: new Date(item["startDate (format MM/DD/YYYY)"]),
29056
29670
  endDate: new Date(item["endDate (format MM/DD/YYYY)"] || endDate),
29057
29671
  isActivated: true,
@@ -29125,6 +29739,66 @@ function UseAccessManagementRepo() {
29125
29739
  session?.endSession();
29126
29740
  }
29127
29741
  }
29742
+ async function deleteCardRepo(params) {
29743
+ try {
29744
+ const { cardId, remarks } = params;
29745
+ const id = new ObjectId83(cardId);
29746
+ const result = await collection().findOneAndUpdate({ _id: id }, { $set: { isActivated: false, updatedAt: /* @__PURE__ */ new Date(), remarks, requestDate: /* @__PURE__ */ new Date() } }, { returnDocument: "after" });
29747
+ return result;
29748
+ } catch (error) {
29749
+ throw new Error(error.message);
29750
+ }
29751
+ }
29752
+ async function getCardDetailsRepo(params) {
29753
+ try {
29754
+ const { siteId, cardId } = params;
29755
+ const convertedSiteId = new ObjectId83(siteId);
29756
+ const convertedCardId = new ObjectId83(cardId);
29757
+ const card = await collection().findOne(
29758
+ {
29759
+ _id: convertedCardId,
29760
+ site: convertedSiteId,
29761
+ type: "NFC" /* NFC */,
29762
+ userType: "Visitor/Resident" /* DEFAULT */
29763
+ },
29764
+ {
29765
+ projection: {
29766
+ userId: 1,
29767
+ site: 1,
29768
+ type: 1,
29769
+ userType: 1,
29770
+ cardNo: 1,
29771
+ isActivated: 1,
29772
+ replacementStatus: 1,
29773
+ vmsRemarks: 1,
29774
+ remarks: 1,
29775
+ requestDate: 1,
29776
+ createdAt: 1,
29777
+ updatedAt: 1
29778
+ }
29779
+ }
29780
+ );
29781
+ if (!card)
29782
+ return null;
29783
+ const site = await collectionName("sites").findOne(
29784
+ { _id: card.site },
29785
+ { projection: { name: 1, status: 1 } }
29786
+ );
29787
+ const user = card.userId ? await collectionName("users").findOne(
29788
+ { _id: card.userId },
29789
+ { projection: { name: 1, email: 1 } }
29790
+ ) : null;
29791
+ const status = card.userId === null && card.isActivated === true ? "available" : card.userId !== null && card.isActivated === true ? "assigned" : card.isActivated === false && card.replacementStatus !== null ? "replaced" : "deleted";
29792
+ return {
29793
+ ...card,
29794
+ status,
29795
+ site,
29796
+ user
29797
+ };
29798
+ } catch (error) {
29799
+ throw new Error(error.message);
29800
+ }
29801
+ }
29128
29802
  return {
29129
29803
  createIndexes,
29130
29804
  createIndexForEntrypass,
@@ -29145,7 +29819,9 @@ function UseAccessManagementRepo() {
29145
29819
  getCardReplacementRepo,
29146
29820
  getAccessManagementSettingsRepo,
29147
29821
  bulkPhysicalAccessCardRepo,
29148
- assignAccessCardToUnitRepo
29822
+ assignAccessCardToUnitRepo,
29823
+ deleteCardRepo,
29824
+ getCardDetailsRepo
29149
29825
  };
29150
29826
  }
29151
29827
 
@@ -29177,7 +29853,9 @@ function useAccessManagementSvc() {
29177
29853
  getCardReplacementRepo,
29178
29854
  getAccessManagementSettingsRepo,
29179
29855
  bulkPhysicalAccessCardRepo,
29180
- assignAccessCardToUnitRepo
29856
+ assignAccessCardToUnitRepo,
29857
+ deleteCardRepo,
29858
+ getCardDetailsRepo
29181
29859
  } = UseAccessManagementRepo();
29182
29860
  const addPhysicalCardSvc = async (payload) => {
29183
29861
  try {
@@ -29425,6 +30103,22 @@ function useAccessManagementSvc() {
29425
30103
  throw new Error(err.message);
29426
30104
  }
29427
30105
  };
30106
+ const deleteCardSvc = async (params) => {
30107
+ try {
30108
+ const response = await deleteCardRepo({ ...params });
30109
+ return response;
30110
+ } catch (err) {
30111
+ throw new Error(err.message);
30112
+ }
30113
+ };
30114
+ const getCardDetailsSvc = async (params) => {
30115
+ try {
30116
+ const response = await getCardDetailsRepo({ ...params });
30117
+ return response;
30118
+ } catch (err) {
30119
+ throw new Error(err.message);
30120
+ }
30121
+ };
29428
30122
  return {
29429
30123
  addPhysicalCardSvc,
29430
30124
  addNonPhysicalCardSvc,
@@ -29449,7 +30143,9 @@ function useAccessManagementSvc() {
29449
30143
  getAccessManagementSettingsSvc,
29450
30144
  convertBufferFile,
29451
30145
  bulkPhysicalAccessCardSvc,
29452
- assignAccessCardToUnitSvc
30146
+ assignAccessCardToUnitSvc,
30147
+ deleteCardSvc,
30148
+ getCardDetailsSvc
29453
30149
  };
29454
30150
  }
29455
30151
 
@@ -29481,7 +30177,9 @@ function useAccessManagementController() {
29481
30177
  getAccessManagementSettingsSvc,
29482
30178
  convertBufferFile,
29483
30179
  bulkPhysicalAccessCardSvc,
29484
- assignAccessCardToUnitSvc
30180
+ assignAccessCardToUnitSvc,
30181
+ deleteCardSvc,
30182
+ getCardDetailsSvc
29485
30183
  } = useAccessManagementSvc();
29486
30184
  const addPhysicalCard = async (req, res) => {
29487
30185
  try {
@@ -29710,11 +30408,24 @@ function useAccessManagementController() {
29710
30408
  userType: Joi85.string().optional().allow("", null),
29711
30409
  type: Joi85.string().optional().allow("", null)
29712
30410
  });
30411
+ const user = req.cookies?.sid;
29713
30412
  const { error } = schema2.validate({ site, userType, type });
29714
30413
  if (error) {
29715
30414
  return res.status(400).json({ message: error.message });
29716
30415
  }
30416
+ const key = `${namespace2}:${user}:available-access-cards`;
30417
+ const listKey = `${namespace2}:${user}:list`;
30418
+ const { redis } = useCache48(key);
30419
+ const cachedData = await getCache({ key, redis });
30420
+ if (cachedData) {
30421
+ console.log("\u26A1 Cache hit:", key);
30422
+ redis.expire(key, 60).catch(console.error);
30423
+ redis.lrem(listKey, 0, key).then(() => redis.lpush(listKey, key)).then(() => redis.ltrim(listKey, 0, 9)).catch(console.error);
30424
+ return res.status(200).json({ message: "Success", data: cachedData });
30425
+ }
29717
30426
  const result = await availableAccessCardsSvc({ site, userType, type });
30427
+ await setCache({ key, data: result, ttlSeconds: 60, redis });
30428
+ redis.lrem(listKey, 0, key).then(() => redis.lpush(listKey, key)).then(() => redis.ltrim(listKey, 0, 9)).catch(console.error);
29718
30429
  return res.status(200).json({ message: "Success", data: result });
29719
30430
  } catch (error) {
29720
30431
  return res.status(400).json({
@@ -29896,16 +30607,19 @@ function useAccessManagementController() {
29896
30607
  };
29897
30608
  const cardReplacement = async (req, res) => {
29898
30609
  try {
29899
- const { cardId, remarks } = req.body;
30610
+ const { cardId, remarks, unitId, issuedCardId, userId } = req.body;
29900
30611
  const schema2 = Joi85.object({
29901
30612
  cardId: Joi85.string().required(),
29902
- remarks: Joi85.string().optional().allow("", null)
30613
+ remarks: Joi85.string().optional().allow("", null),
30614
+ unitId: Joi85.string().hex().required(),
30615
+ issuedCardId: Joi85.string().required(),
30616
+ userId: Joi85.string().hex().required()
29903
30617
  });
29904
- const { error } = schema2.validate({ cardId, remarks });
30618
+ const { error } = schema2.validate({ cardId, remarks, unitId, issuedCardId, userId });
29905
30619
  if (error) {
29906
30620
  return res.status(400).json({ message: error.message });
29907
30621
  }
29908
- const result = await cardReplacementSvc({ cardId, remarks });
30622
+ const result = await cardReplacementSvc({ cardId, remarks, unitId, issuedCardId, userId });
29909
30623
  return res.status(200).json({ message: "Success", data: result });
29910
30624
  } catch (error) {
29911
30625
  return res.status(500).json({
@@ -30036,6 +30750,46 @@ function useAccessManagementController() {
30036
30750
  });
30037
30751
  }
30038
30752
  };
30753
+ const deleteCard = async (req, res) => {
30754
+ try {
30755
+ const { cardId, remarks } = req.body;
30756
+ const schema2 = Joi85.object({
30757
+ cardId: Joi85.string().hex().required(),
30758
+ remarks: Joi85.string().optional().allow("", null)
30759
+ });
30760
+ const { error } = schema2.validate({ cardId, remarks });
30761
+ if (error) {
30762
+ return res.status(400).json({ message: error.message });
30763
+ }
30764
+ const result = await deleteCardSvc({ cardId, remarks });
30765
+ return res.status(200).json({ message: "Success", data: result });
30766
+ } catch (error) {
30767
+ return res.status(500).json({
30768
+ data: null,
30769
+ message: error.message
30770
+ });
30771
+ }
30772
+ };
30773
+ const getCardDetails = async (req, res) => {
30774
+ try {
30775
+ const { siteId, cardId } = req.query;
30776
+ const schema2 = Joi85.object({
30777
+ siteId: Joi85.string().hex().required(),
30778
+ cardId: Joi85.string().hex().required()
30779
+ });
30780
+ const { error } = schema2.validate({ siteId, cardId });
30781
+ if (error) {
30782
+ return res.status(400).json({ message: error.message });
30783
+ }
30784
+ const result = await getCardDetailsSvc({ siteId, cardId });
30785
+ return res.status(200).json({ message: "Success", data: result });
30786
+ } catch (error) {
30787
+ return res.status(500).json({
30788
+ data: null,
30789
+ message: error.message
30790
+ });
30791
+ }
30792
+ };
30039
30793
  return {
30040
30794
  addPhysicalCard,
30041
30795
  addNonPhysicalCard,
@@ -30057,7 +30811,9 @@ function useAccessManagementController() {
30057
30811
  getCardReplacement,
30058
30812
  getAccessManagementSettings,
30059
30813
  bulkPhysicalAccessCard,
30060
- assignAccessCardToUnit
30814
+ assignAccessCardToUnit,
30815
+ deleteCard,
30816
+ getCardDetails
30061
30817
  };
30062
30818
  }
30063
30819
 
@@ -31809,9 +32565,9 @@ function useStatementOfAccountRepo() {
31809
32565
  page = page > 0 ? page - 1 : 0;
31810
32566
  let dateExpr = {};
31811
32567
  if (dateFrom && dateTo) {
31812
- const startDate = new Date(dateFrom);
32568
+ let startDate = new Date(dateFrom);
31813
32569
  startDate.setHours(0, 0, 0, 0);
31814
- const endDate = new Date(dateTo);
32570
+ let endDate = new Date(dateTo);
31815
32571
  endDate.setHours(23, 59, 59, 999);
31816
32572
  dateExpr = {
31817
32573
  $expr: {
@@ -31822,13 +32578,15 @@ function useStatementOfAccountRepo() {
31822
32578
  }
31823
32579
  };
31824
32580
  }
32581
+ const unitSearchRegex = search ? search.trim().replace(/\s+/g, "").replace(/\//g, "\\s*/\\s*") : null;
31825
32582
  const query = {
31826
32583
  ...status && status !== "all" && { status },
31827
32584
  ...search && {
31828
32585
  $or: [
31829
32586
  { unitOwner: { $regex: search, $options: "i" } },
31830
- { unit: { $regex: search, $options: "i" } },
31831
- { email: { $regex: search, $options: "i" } }
32587
+ { unit: { $regex: unitSearchRegex, $options: "i" } },
32588
+ { email: { $regex: search, $options: "i" } },
32589
+ { category: { $regex: search, $options: "i" } }
31832
32590
  ]
31833
32591
  },
31834
32592
  ...ObjectId91.isValid(site) && { site: new ObjectId91(site) },
@@ -34122,6 +34880,9 @@ function useIncidentReportRepo() {
34122
34880
  $regex: search,
34123
34881
  $options: "i"
34124
34882
  }
34883
+ },
34884
+ {
34885
+ approvedByName: { $regex: search, $options: "i" }
34125
34886
  }
34126
34887
  ]
34127
34888
  },
@@ -36677,7 +37438,9 @@ function useNfcPatrolLogController() {
36677
37438
  };
36678
37439
  }
36679
37440
  export {
37441
+ ANPRMode,
36680
37442
  AccessTypeProps,
37443
+ BULLETIN_RECIPIENTS,
36681
37444
  DEVICE_STATUS,
36682
37445
  EAccessCardTypes,
36683
37446
  EAccessCardUserTypes,
@@ -36736,12 +37499,15 @@ export {
36736
37499
  MVerification,
36737
37500
  MVisitorTransaction,
36738
37501
  MWorkOrder,
37502
+ OrgNature,
36739
37503
  PERSON_TYPES,
37504
+ STATUS_VALUES,
36740
37505
  UseAccessManagementRepo,
36741
- allowedCategories,
37506
+ VehicleCategory,
37507
+ VehicleStatus,
37508
+ VehicleType,
36742
37509
  allowedFieldsSite,
36743
37510
  allowedNatures,
36744
- allowedTypes,
36745
37511
  attendanceSchema,
36746
37512
  attendanceSettingsSchema,
36747
37513
  chatSchema,