@7365admin1/core 2.14.0 → 2.16.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.js CHANGED
@@ -30,7 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ ANPRMode: () => ANPRMode,
33
34
  AccessTypeProps: () => AccessTypeProps,
35
+ BULLETIN_RECIPIENTS: () => BULLETIN_RECIPIENTS,
34
36
  DEVICE_STATUS: () => DEVICE_STATUS,
35
37
  EAccessCardTypes: () => EAccessCardTypes,
36
38
  EAccessCardUserTypes: () => EAccessCardUserTypes,
@@ -89,12 +91,15 @@ __export(src_exports, {
89
91
  MVerification: () => MVerification,
90
92
  MVisitorTransaction: () => MVisitorTransaction,
91
93
  MWorkOrder: () => MWorkOrder,
94
+ OrgNature: () => OrgNature,
92
95
  PERSON_TYPES: () => PERSON_TYPES,
96
+ STATUS_VALUES: () => STATUS_VALUES,
93
97
  UseAccessManagementRepo: () => UseAccessManagementRepo,
94
- allowedCategories: () => allowedCategories,
98
+ VehicleCategory: () => VehicleCategory,
99
+ VehicleStatus: () => VehicleStatus,
100
+ VehicleType: () => VehicleType,
95
101
  allowedFieldsSite: () => allowedFieldsSite,
96
102
  allowedNatures: () => allowedNatures,
97
- allowedTypes: () => allowedTypes,
98
103
  attendanceSchema: () => attendanceSchema,
99
104
  attendanceSettingsSchema: () => attendanceSettingsSchema,
100
105
  chatSchema: () => chatSchema,
@@ -1669,7 +1674,9 @@ var schemaOccurrenceEntry = import_joi5.default.object({
1669
1674
  subject: import_joi5.default.string().hex().optional().allow(null, ""),
1670
1675
  occurrence: import_joi5.default.string().optional().allow(null, ""),
1671
1676
  signature: import_joi5.default.string().hex().optional().allow(null, ""),
1672
- incidentReportId: import_joi5.default.string().hex().optional().allow(null, "")
1677
+ incidentReportId: import_joi5.default.string().hex().optional().allow(null, ""),
1678
+ eSignature: import_joi5.default.string().required(),
1679
+ createdByName: import_joi5.default.string().optional().allow(null, "")
1673
1680
  });
1674
1681
  var schemaUpdateOccurrenceEntry = import_joi5.default.object({
1675
1682
  _id: import_joi5.default.string().hex().required(),
@@ -1678,7 +1685,9 @@ var schemaUpdateOccurrenceEntry = import_joi5.default.object({
1678
1685
  subject: import_joi5.default.string().hex().optional().allow(null, ""),
1679
1686
  occurrence: import_joi5.default.string().optional().allow(null, ""),
1680
1687
  signature: import_joi5.default.string().hex().optional().allow(null, ""),
1681
- incidentReportId: import_joi5.default.string().hex().optional().allow(null, "")
1688
+ incidentReportId: import_joi5.default.string().hex().optional().allow(null, ""),
1689
+ eSignature: import_joi5.default.string().required(),
1690
+ createdByName: import_joi5.default.string().optional().allow(null, "")
1682
1691
  });
1683
1692
  function MOccurrenceEntry(value) {
1684
1693
  if (value._id && typeof value._id === "string") {
@@ -1735,6 +1744,8 @@ function MOccurrenceEntry(value) {
1735
1744
  incidentReportId: value.incidentReportId,
1736
1745
  signature: value.signature,
1737
1746
  userName: value.userName,
1747
+ eSignature: value.eSignature,
1748
+ createdByName: value.createdByName,
1738
1749
  date: value.date,
1739
1750
  createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1740
1751
  updatedAt: value.updatedAt,
@@ -6617,12 +6628,12 @@ function useUserController() {
6617
6628
  }
6618
6629
  }
6619
6630
  async function createUserByVerification(req, res, next) {
6620
- const allowedTypes2 = ["user-sign-up", "user-invite"];
6631
+ const allowedTypes = ["user-sign-up", "user-invite"];
6621
6632
  const validation = import_joi14.default.object({
6622
6633
  id: import_joi14.default.string().hex().required(),
6623
6634
  name: import_joi14.default.string().required(),
6624
6635
  password: import_joi14.default.string().required(),
6625
- type: import_joi14.default.string().required().valid(...allowedTypes2)
6636
+ type: import_joi14.default.string().required().valid(...allowedTypes)
6626
6637
  });
6627
6638
  const id = req.params.id;
6628
6639
  const payload = { ...req.body };
@@ -11029,7 +11040,7 @@ function useFeedbackController() {
11029
11040
  }
11030
11041
  async function deleteFeedback(req, res, next) {
11031
11042
  const validation = import_joi28.default.string().hex().required();
11032
- const _id = req.query.id;
11043
+ const _id = req.params.id;
11033
11044
  const { error } = validation.validate(_id);
11034
11045
  if (error) {
11035
11046
  import_node_server_utils55.logger.log({ level: "error", message: error.message });
@@ -11393,8 +11404,7 @@ function useWorkOrderController() {
11393
11404
  }
11394
11405
  async function deleteWorkOrder(req, res, next) {
11395
11406
  const validation = import_joi29.default.string().hex().required();
11396
- const _id = req.query.id;
11397
- console.log(_id);
11407
+ const _id = req.params.id;
11398
11408
  const { error } = validation.validate(_id);
11399
11409
  if (error) {
11400
11410
  import_node_server_utils57.logger.log({ level: "error", message: error.message });
@@ -15115,11 +15125,14 @@ function usePersonRepo() {
15115
15125
  const namespace_collection = "site.people";
15116
15126
  const collection = db.collection(namespace_collection);
15117
15127
  const { delNamespace, getCache, setCache } = (0, import_node_server_utils79.useCache)(namespace_collection);
15118
- async function createIndex() {
15128
+ async function createIndexes() {
15119
15129
  try {
15120
- await collection.createIndex([{ nric: 1 }, { contact: 1 }]);
15130
+ await collection.createIndexes([
15131
+ { key: { contact: 1 } },
15132
+ { key: { nric: 1 } }
15133
+ ]);
15121
15134
  } catch (error) {
15122
- throw new import_node_server_utils79.InternalServerError("Failed to create index.");
15135
+ throw new import_node_server_utils79.InternalServerError("Failed to create index on site people.");
15123
15136
  }
15124
15137
  }
15125
15138
  async function createTextIndex() {
@@ -15509,6 +15522,47 @@ function usePersonRepo() {
15509
15522
  throw new import_node_server_utils79.InternalServerError("Failed to fetch people by plate number.");
15510
15523
  }
15511
15524
  }
15525
+ async function getPeopleByNRIC({
15526
+ page = 1,
15527
+ limit = 10,
15528
+ nric = "",
15529
+ sort = {},
15530
+ site = ""
15531
+ }) {
15532
+ page = page > 0 ? page - 1 : 0;
15533
+ const normalizedNric = nric.trim();
15534
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
15535
+ const query = {
15536
+ ...nric && { nric: normalizedNric },
15537
+ site: typeof site === "string" ? new import_mongodb44.ObjectId(site) : site
15538
+ };
15539
+ const cacheOptions = {
15540
+ site,
15541
+ ...nric && { nric: normalizedNric },
15542
+ page: String(page),
15543
+ limit: String(limit),
15544
+ sort: JSON.stringify(sort)
15545
+ };
15546
+ try {
15547
+ const cacheKey = (0, import_node_server_utils79.makeCacheKey)(namespace_collection, cacheOptions);
15548
+ const cachedData = await getCache(cacheKey);
15549
+ if (cachedData) {
15550
+ import_node_server_utils79.logger.info(`Cache hit for key: ${cacheKey}`);
15551
+ return cachedData;
15552
+ }
15553
+ const items = await collection.find(query).sort(sort).skip(page * limit).limit(limit).toArray();
15554
+ const length = await collection.countDocuments(query);
15555
+ const data = (0, import_node_server_utils79.paginate)(items, page, limit, length);
15556
+ setCache(cacheKey, data, 15 * 60).then(() => {
15557
+ import_node_server_utils79.logger.info(`Cache set for key: ${cacheKey}`);
15558
+ }).catch((err) => {
15559
+ import_node_server_utils79.logger.error(`Failed to set cache for key: ${cacheKey}`, err);
15560
+ });
15561
+ return data;
15562
+ } catch (error) {
15563
+ throw new import_node_server_utils79.InternalServerError("Failed to retrieve person by NRIC.");
15564
+ }
15565
+ }
15512
15566
  return {
15513
15567
  add,
15514
15568
  getAll,
@@ -15518,11 +15572,12 @@ function usePersonRepo() {
15518
15572
  createTextIndex,
15519
15573
  getPersonByPlateNumber,
15520
15574
  getByNRIC,
15521
- createIndex,
15575
+ createIndexes,
15522
15576
  getPersonByPhoneNumber,
15523
15577
  getPeopleByUnit,
15524
15578
  getCompany,
15525
- getPeopleByPlateNumber
15579
+ getPeopleByPlateNumber,
15580
+ getPeopleByNRIC
15526
15581
  };
15527
15582
  }
15528
15583
 
@@ -15533,12 +15588,45 @@ var import_node_server_utils81 = require("@7365admin1/node-server-utils");
15533
15588
  var import_node_server_utils80 = require("@7365admin1/node-server-utils");
15534
15589
  var import_joi43 = __toESM(require("joi"));
15535
15590
  var import_mongodb45 = require("mongodb");
15536
- var allowedTypes = ["whitelist", "blacklist"];
15537
- var allowedCategories = ["resident", "visitor"];
15591
+ var VehicleType = /* @__PURE__ */ ((VehicleType2) => {
15592
+ VehicleType2["WHITELIST"] = "whitelist";
15593
+ VehicleType2["BLOCKLIST"] = "blocklist";
15594
+ return VehicleType2;
15595
+ })(VehicleType || {});
15596
+ var VehicleCategory = /* @__PURE__ */ ((VehicleCategory2) => {
15597
+ VehicleCategory2["RESIDENT"] = "resident";
15598
+ VehicleCategory2["VISITOR"] = "visitor";
15599
+ return VehicleCategory2;
15600
+ })(VehicleCategory || {});
15601
+ var VehicleStatus = /* @__PURE__ */ ((VehicleStatus2) => {
15602
+ VehicleStatus2["PENDING"] = "pending";
15603
+ VehicleStatus2["ACTIVE"] = "active";
15604
+ VehicleStatus2["INACTIVE"] = "inactive";
15605
+ VehicleStatus2["DELETED"] = "deleted";
15606
+ return VehicleStatus2;
15607
+ })(VehicleStatus || {});
15608
+ var OrgNature = /* @__PURE__ */ ((OrgNature2) => {
15609
+ OrgNature2["PROPERTY_MANAGEMENT_AGENCY"] = "property_management_agency";
15610
+ OrgNature2["SECURITY_AGENCY"] = "security_agency";
15611
+ return OrgNature2;
15612
+ })(OrgNature || {});
15613
+ var ANPRMode = /* @__PURE__ */ ((ANPRMode2) => {
15614
+ ANPRMode2["TRAFFIC_REDLIST"] = "TrafficRedList";
15615
+ ANPRMode2["TRAFFIC_BLACKLIST"] = "TrafficBlackList";
15616
+ return ANPRMode2;
15617
+ })(ANPRMode || {});
15538
15618
  var vehicleSchema = import_joi43.default.object({
15539
- plateNumber: import_joi43.default.string().required(),
15540
- type: import_joi43.default.string().required().allow(...allowedTypes),
15541
- category: import_joi43.default.string().required().allow(...allowedCategories),
15619
+ plateNumber: import_joi43.default.alternatives().try(
15620
+ import_joi43.default.array().items(import_joi43.default.string().trim().min(1)).min(1),
15621
+ import_joi43.default.string().trim().min(1)
15622
+ ).custom((value) => {
15623
+ if (typeof value === "string") {
15624
+ return value.split(",").map((p) => p.trim()).filter(Boolean);
15625
+ }
15626
+ return value;
15627
+ }).required(),
15628
+ type: import_joi43.default.string().required().valid(...Object.values(VehicleType)),
15629
+ category: import_joi43.default.string().required().valid(...Object.values(VehicleCategory)),
15542
15630
  name: import_joi43.default.string().required(),
15543
15631
  phoneNumber: import_joi43.default.string().optional().allow("", null),
15544
15632
  recNo: import_joi43.default.string().optional().allow("", null),
@@ -15552,7 +15640,8 @@ var vehicleSchema = import_joi43.default.object({
15552
15640
  seasonPassType: import_joi43.default.string().optional().allow("", null),
15553
15641
  start: import_joi43.default.date().optional().allow("", null),
15554
15642
  end: import_joi43.default.date().optional().allow("", null),
15555
- unitName: import_joi43.default.string().optional().allow("", null)
15643
+ unitName: import_joi43.default.string().optional().allow("", null),
15644
+ status: import_joi43.default.string().optional().valid(...Object.values(VehicleStatus)).allow("")
15556
15645
  });
15557
15646
  function MVehicle(value) {
15558
15647
  const { error } = vehicleSchema.validate(value);
@@ -15581,6 +15670,11 @@ function MVehicle(value) {
15581
15670
  throw new import_node_server_utils80.BadRequestError("Invalid building unit ID format.");
15582
15671
  }
15583
15672
  }
15673
+ const createdAtDate = value.createdAt ? new Date(value.createdAt) : /* @__PURE__ */ new Date();
15674
+ const expiredDate = new Date(createdAtDate);
15675
+ expiredDate.setFullYear(expiredDate.getFullYear() + 10);
15676
+ const createdAt = createdAtDate.toISOString();
15677
+ const expiredAt = value.end ?? expiredDate.toISOString();
15584
15678
  return {
15585
15679
  plateNumber: value.plateNumber ?? "",
15586
15680
  type: value.type ?? "",
@@ -15596,11 +15690,11 @@ function MVehicle(value) {
15596
15690
  nric: value.nric ?? "",
15597
15691
  remarks: value.remarks ?? "",
15598
15692
  seasonPassType: value.seasonPassType ?? "",
15599
- start: value.start ?? "",
15600
- end: value.end ?? "",
15601
- status: value.status ?? "active",
15693
+ start: value.start ?? createdAt,
15694
+ end: value.end ?? expiredAt,
15695
+ status: value.status ?? "active" /* ACTIVE */,
15602
15696
  unitName: value.unitName ?? "",
15603
- createdAt: value.createdAt ?? /* @__PURE__ */ new Date(),
15697
+ createdAt,
15604
15698
  updatedAt: value.updatedAt ?? "",
15605
15699
  deletedAt: value.deletedAt ?? ""
15606
15700
  };
@@ -15658,7 +15752,7 @@ function useVehicleRepo() {
15658
15752
  }
15659
15753
  const namespace_collection = "vehicles";
15660
15754
  const collection = db.collection(namespace_collection);
15661
- const { delNamespace, setCache, getCache, delCache } = (0, import_node_server_utils81.useCache)(namespace_collection);
15755
+ const { delNamespace, setCache, getCache } = (0, import_node_server_utils81.useCache)(namespace_collection);
15662
15756
  async function createIndex() {
15663
15757
  try {
15664
15758
  await collection.createIndexes([
@@ -15670,6 +15764,22 @@ function useVehicleRepo() {
15670
15764
  throw new import_node_server_utils81.InternalServerError("Failed to create index on vehicle.");
15671
15765
  }
15672
15766
  }
15767
+ async function createTextIndex() {
15768
+ try {
15769
+ await collection.createIndex({
15770
+ name: "text",
15771
+ plateNumber: "text",
15772
+ level: "text",
15773
+ unitName: "text",
15774
+ phoneNumber: "text",
15775
+ nric: "text"
15776
+ });
15777
+ } catch (error) {
15778
+ throw new import_node_server_utils81.InternalServerError(
15779
+ "Failed to create text index on visitor transaction."
15780
+ );
15781
+ }
15782
+ }
15673
15783
  async function add(value, session) {
15674
15784
  try {
15675
15785
  value = MVehicle(value);
@@ -15701,73 +15811,178 @@ function useVehicleRepo() {
15701
15811
  search = "",
15702
15812
  sort = {},
15703
15813
  type = "",
15704
- category = ""
15814
+ category = "",
15815
+ status = ""
15705
15816
  }) {
15706
15817
  page = page > 0 ? page - 1 : 0;
15707
- const query = { status: "active" };
15818
+ const baseQuery = {
15819
+ ...status && { status },
15820
+ ...type && { type },
15821
+ ...category && { category }
15822
+ };
15823
+ let query = { ...baseQuery };
15824
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
15708
15825
  const cacheOptions = {
15709
- status: "active",
15826
+ ...status && { status },
15827
+ ...type && { type },
15828
+ ...category && { category },
15710
15829
  page: String(page),
15711
- limit: String(limit)
15830
+ limit: String(limit),
15831
+ sort: JSON.stringify(sort)
15712
15832
  };
15713
- sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
15714
- cacheOptions.sort = JSON.stringify(sort);
15715
15833
  if (search) {
15716
- query.$or = [{ name: { $regex: search, $options: "i" } }];
15834
+ query.$text = { $search: search };
15717
15835
  cacheOptions.search = search;
15718
15836
  }
15719
- if (type) {
15720
- query.type = type;
15721
- cacheOptions.type = type;
15722
- }
15723
- if (category) {
15724
- query.category = category;
15725
- cacheOptions.category = category;
15726
- }
15727
15837
  const cacheKey = (0, import_node_server_utils81.makeCacheKey)(namespace_collection, cacheOptions);
15728
15838
  const cachedData = await getCache(cacheKey);
15729
15839
  if (cachedData) {
15730
15840
  import_node_server_utils81.logger.info(`Cache hit for key: ${cacheKey}`);
15731
15841
  return cachedData;
15732
15842
  }
15733
- try {
15734
- const items = await collection.aggregate([
15735
- { $match: query },
15736
- { $sort: sort },
15737
- { $skip: page * limit },
15738
- { $limit: limit },
15739
- {
15740
- $lookup: {
15741
- from: "building-units",
15742
- localField: "unit",
15743
- foreignField: "_id",
15744
- as: "units",
15745
- pipeline: [{ $project: { name: 1 } }]
15843
+ const escapeRegex = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15844
+ const buildGroupedPipeline = (matchQuery) => [
15845
+ { $match: matchQuery },
15846
+ {
15847
+ $lookup: {
15848
+ from: "building-units",
15849
+ localField: "unit",
15850
+ foreignField: "_id",
15851
+ as: "units",
15852
+ pipeline: [{ $project: { name: 1 } }]
15853
+ }
15854
+ },
15855
+ { $unwind: { path: "$units", preserveNullAndEmptyArrays: true } },
15856
+ {
15857
+ $project: {
15858
+ _id: 1,
15859
+ name: 1,
15860
+ type: 1,
15861
+ category: 1,
15862
+ status: 1,
15863
+ phoneNumber: 1,
15864
+ block: 1,
15865
+ level: 1,
15866
+ unit: "$units.name",
15867
+ recNo: 1,
15868
+ nric: 1,
15869
+ plateNumber: 1
15870
+ }
15871
+ },
15872
+ {
15873
+ $group: {
15874
+ _id: {
15875
+ $cond: [
15876
+ {
15877
+ $and: [{ $ne: ["$nric", null] }, { $ne: ["$nric", ""] }]
15878
+ },
15879
+ {
15880
+ nric: "$nric",
15881
+ block: "$block",
15882
+ level: "$level",
15883
+ unit: "$unit"
15884
+ },
15885
+ "$_id"
15886
+ ]
15887
+ },
15888
+ vehicleId: { $first: "$_id" },
15889
+ name: { $first: "$name" },
15890
+ category: { $first: "$category" },
15891
+ phoneNumber: { $first: "$phoneNumber" },
15892
+ block: { $first: "$block" },
15893
+ level: { $first: "$level" },
15894
+ unit: { $first: "$unit" },
15895
+ nric: { $first: "$nric" },
15896
+ plates: {
15897
+ $addToSet: {
15898
+ plateNumber: "$plateNumber",
15899
+ recNo: "$recNo",
15900
+ status: "$status",
15901
+ type: "$type"
15902
+ }
15746
15903
  }
15747
- },
15748
- { $unwind: { path: "$units", preserveNullAndEmptyArrays: true } },
15749
- {
15750
- $project: {
15751
- name: 1,
15752
- type: 1,
15753
- category: 1,
15754
- status: 1,
15755
- phoneNumber: 1,
15756
- block: 1,
15757
- level: 1,
15758
- unit: "$units.name",
15759
- plateNumber: 1,
15760
- recNo: 1
15904
+ }
15905
+ },
15906
+ {
15907
+ $project: {
15908
+ _id: "$vehicleId",
15909
+ name: 1,
15910
+ type: 1,
15911
+ category: 1,
15912
+ status: 1,
15913
+ phoneNumber: 1,
15914
+ block: 1,
15915
+ level: 1,
15916
+ unit: 1,
15917
+ nric: 1,
15918
+ plates: {
15919
+ $filter: {
15920
+ input: "$plates",
15921
+ as: "plate",
15922
+ cond: {
15923
+ $and: [
15924
+ { $ne: ["$$plate.plateNumber", null] },
15925
+ { $ne: ["$$plate.plateNumber", ""] }
15926
+ ]
15927
+ }
15928
+ }
15761
15929
  }
15762
15930
  }
15763
- ]).toArray();
15764
- const length = await collection.countDocuments(query);
15931
+ },
15932
+ { $sort: sort },
15933
+ { $skip: page * limit },
15934
+ { $limit: limit }
15935
+ ];
15936
+ const buildGroupedCountPipeline = (matchQuery) => [
15937
+ { $match: matchQuery },
15938
+ {
15939
+ $group: {
15940
+ _id: {
15941
+ $cond: [
15942
+ {
15943
+ $and: [{ $ne: ["$nric", null] }, { $ne: ["$nric", ""] }]
15944
+ },
15945
+ {
15946
+ nric: "$nric",
15947
+ block: "$block",
15948
+ level: "$level",
15949
+ unit: "$unit"
15950
+ },
15951
+ "$_id"
15952
+ ]
15953
+ }
15954
+ }
15955
+ },
15956
+ { $count: "total" }
15957
+ ];
15958
+ try {
15959
+ let items = [];
15960
+ let length = 0;
15961
+ items = await collection.aggregate(buildGroupedPipeline(query)).toArray();
15962
+ const countResult = await collection.aggregate(buildGroupedCountPipeline(query)).toArray();
15963
+ length = countResult[0]?.total || 0;
15964
+ if ((!items || items.length === 0) && search) {
15965
+ const escaped = escapeRegex(search);
15966
+ const regexQuery = {
15967
+ ...baseQuery,
15968
+ $or: [
15969
+ { name: { $regex: escaped, $options: "i" } },
15970
+ { plateNumber: { $regex: escaped, $options: "i" } },
15971
+ { company: { $regex: escaped, $options: "i" } },
15972
+ { level: { $regex: escaped, $options: "i" } },
15973
+ { unitName: { $regex: escaped, $options: "i" } },
15974
+ { contact: { $regex: escaped, $options: "i" } },
15975
+ { nric: { $regex: escaped, $options: "i" } }
15976
+ ]
15977
+ };
15978
+ items = await collection.aggregate(buildGroupedPipeline(regexQuery)).toArray();
15979
+ const regexCountResult = await collection.aggregate(buildGroupedCountPipeline(regexQuery)).toArray();
15980
+ length = regexCountResult[0]?.total || 0;
15981
+ }
15765
15982
  const data = (0, import_node_server_utils81.paginate)(items, page, limit, length);
15766
- setCache(cacheKey, data, 15 * 60).then(() => {
15767
- import_node_server_utils81.logger.info(`Cache set for key: ${cacheKey}`);
15768
- }).catch((err) => {
15769
- import_node_server_utils81.logger.error(`Failed to set cache for key: ${cacheKey}`, err);
15770
- });
15983
+ setCache(cacheKey, data, 15 * 60).then(() => import_node_server_utils81.logger.info(`Cache set for key: ${cacheKey}`)).catch(
15984
+ (err) => import_node_server_utils81.logger.error(`Failed to set cache for key: ${cacheKey}`, err)
15985
+ );
15771
15986
  return data;
15772
15987
  } catch (error) {
15773
15988
  throw error;
@@ -15840,40 +16055,176 @@ function useVehicleRepo() {
15840
16055
  }
15841
16056
  const data = await collection.aggregate([
15842
16057
  { $match: { _id } },
16058
+ { $limit: 1 },
16059
+ {
16060
+ $project: {
16061
+ _id: 1,
16062
+ nric: 1,
16063
+ block: 1,
16064
+ level: 1,
16065
+ unit: 1
16066
+ }
16067
+ },
15843
16068
  {
15844
16069
  $lookup: {
15845
- from: "building-units",
15846
- localField: "unit",
15847
- foreignField: "_id",
15848
- as: "units",
15849
- pipeline: [{ $project: { name: 1 } }]
16070
+ from: collection.collectionName,
16071
+ let: {
16072
+ vehicleId: "$_id",
16073
+ vehicleNric: "$nric",
16074
+ vehicleBlock: "$block",
16075
+ vehicleLevel: "$level",
16076
+ vehicleUnit: "$unit"
16077
+ },
16078
+ pipeline: [
16079
+ {
16080
+ $match: {
16081
+ $expr: {
16082
+ $cond: [
16083
+ {
16084
+ $and: [
16085
+ { $ne: ["$$vehicleNric", null] },
16086
+ { $ne: ["$$vehicleNric", ""] }
16087
+ ]
16088
+ },
16089
+ {
16090
+ $and: [
16091
+ { $eq: ["$nric", "$$vehicleNric"] },
16092
+ { $eq: ["$block", "$$vehicleBlock"] },
16093
+ { $eq: ["$level", "$$vehicleLevel"] },
16094
+ { $eq: ["$unit", "$$vehicleUnit"] }
16095
+ ]
16096
+ },
16097
+ { $eq: ["$_id", "$$vehicleId"] }
16098
+ ]
16099
+ }
16100
+ }
16101
+ },
16102
+ {
16103
+ $lookup: {
16104
+ from: "building-units",
16105
+ localField: "unit",
16106
+ foreignField: "_id",
16107
+ as: "units",
16108
+ pipeline: [{ $project: { name: 1 } }]
16109
+ }
16110
+ },
16111
+ {
16112
+ $unwind: { path: "$units", preserveNullAndEmptyArrays: true }
16113
+ },
16114
+ {
16115
+ $project: {
16116
+ _id: 1,
16117
+ name: 1,
16118
+ phoneNumber: 1,
16119
+ plateNumber: 1,
16120
+ nric: 1,
16121
+ block: 1,
16122
+ level: 1,
16123
+ rawUnit: "$unit",
16124
+ unit: "$units.name",
16125
+ type: 1,
16126
+ category: 1,
16127
+ status: 1,
16128
+ recNo: 1,
16129
+ start: 1,
16130
+ end: 1,
16131
+ seasonPassType: 1
16132
+ }
16133
+ },
16134
+ {
16135
+ $group: {
16136
+ _id: {
16137
+ $cond: [
16138
+ {
16139
+ $and: [
16140
+ { $ne: ["$nric", null] },
16141
+ { $ne: ["$nric", ""] }
16142
+ ]
16143
+ },
16144
+ {
16145
+ nric: "$nric",
16146
+ block: "$block",
16147
+ level: "$level",
16148
+ unit: "$unit"
16149
+ },
16150
+ "$_id"
16151
+ ]
16152
+ },
16153
+ vehicleId: { $first: "$_id" },
16154
+ name: { $first: "$name" },
16155
+ category: { $first: "$category" },
16156
+ phoneNumber: { $first: "$phoneNumber" },
16157
+ block: { $first: "$block" },
16158
+ level: { $first: "$level" },
16159
+ unit: { $first: "$unit" },
16160
+ nric: { $first: "$nric" },
16161
+ start: { $first: "$start" },
16162
+ end: { $first: "$end" },
16163
+ seasonPassType: { $first: "$seasonPassType" },
16164
+ plates: {
16165
+ $addToSet: {
16166
+ plateNumber: "$plateNumber",
16167
+ recNo: "$recNo",
16168
+ status: "$status",
16169
+ type: "$type"
16170
+ }
16171
+ }
16172
+ }
16173
+ },
16174
+ {
16175
+ $project: {
16176
+ _id: "$vehicleId",
16177
+ name: 1,
16178
+ phoneNumber: 1,
16179
+ nric: 1,
16180
+ block: 1,
16181
+ level: 1,
16182
+ unit: 1,
16183
+ type: 1,
16184
+ category: 1,
16185
+ status: 1,
16186
+ start: 1,
16187
+ end: 1,
16188
+ seasonPassType: 1,
16189
+ plates: {
16190
+ $filter: {
16191
+ input: "$plates",
16192
+ as: "plate",
16193
+ cond: {
16194
+ $and: [
16195
+ { $ne: ["$$plate.plateNumber", null] },
16196
+ { $ne: ["$$plate.plateNumber", ""] }
16197
+ ]
16198
+ }
16199
+ }
16200
+ }
16201
+ }
16202
+ }
16203
+ ],
16204
+ as: "vehicle"
15850
16205
  }
15851
16206
  },
15852
- { $unwind: { path: "$units", preserveNullAndEmptyArrays: true } },
15853
16207
  {
15854
16208
  $project: {
15855
- name: 1,
15856
- phoneNumber: 1,
15857
- plateNumber: 1,
15858
- block: 1,
15859
- level: 1,
15860
- unit: "$units.name",
15861
- type: 1,
15862
- category: 1,
15863
- status: 1,
15864
- recNo: 1
16209
+ vehicle: { $arrayElemAt: ["$vehicle", 0] }
16210
+ }
16211
+ },
16212
+ {
16213
+ $replaceRoot: {
16214
+ newRoot: "$vehicle"
15865
16215
  }
15866
16216
  }
15867
16217
  ]).toArray();
15868
16218
  if (!data || !data.length) {
15869
16219
  throw new import_node_server_utils81.NotFoundError("Vehicle not found.");
15870
16220
  }
15871
- setCache(cacheKey, data[0], 15 * 60).then(() => {
16221
+ const result = data[0];
16222
+ setCache(cacheKey, result, 15 * 60).then(() => {
15872
16223
  import_node_server_utils81.logger.info(`Cache set for key: ${cacheKey}`);
15873
16224
  }).catch((err) => {
15874
16225
  import_node_server_utils81.logger.error(`Failed to set cache for key: ${cacheKey}`, err);
15875
16226
  });
15876
- return data[0];
16227
+ return result;
15877
16228
  } catch (error) {
15878
16229
  throw error;
15879
16230
  }
@@ -15910,7 +16261,7 @@ function useVehicleRepo() {
15910
16261
  throw error2;
15911
16262
  }
15912
16263
  }
15913
- async function updateVehicle(_id, value) {
16264
+ async function updateVehicle(_id, value, session) {
15914
16265
  try {
15915
16266
  _id = new import_mongodb46.ObjectId(_id);
15916
16267
  } catch (error) {
@@ -15921,7 +16272,11 @@ function useVehicleRepo() {
15921
16272
  ...value,
15922
16273
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15923
16274
  };
15924
- const res = await collection.updateOne({ _id }, { $set: updateValue });
16275
+ const res = await collection.updateOne(
16276
+ { _id },
16277
+ { $set: updateValue },
16278
+ { session }
16279
+ );
15925
16280
  if (res.modifiedCount === 0) {
15926
16281
  throw new import_node_server_utils81.InternalServerError("Unable to update vehicle.");
15927
16282
  }
@@ -16000,8 +16355,94 @@ function useVehicleRepo() {
16000
16355
  );
16001
16356
  }
16002
16357
  }
16358
+ async function getVehiclesByNRIC({
16359
+ page = 1,
16360
+ limit = 10,
16361
+ nric = "",
16362
+ sort = {}
16363
+ }) {
16364
+ page = page > 0 ? page - 1 : 0;
16365
+ if (!nric) {
16366
+ throw new import_node_server_utils81.BadRequestError("NRIC is required.");
16367
+ }
16368
+ const _nric = nric.trim();
16369
+ const query = {
16370
+ deletedAt: "",
16371
+ nric: _nric
16372
+ };
16373
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
16374
+ const cacheOptions = {
16375
+ deletedAt: "",
16376
+ nric: _nric,
16377
+ page: String(page),
16378
+ limit: String(limit),
16379
+ sort: JSON.stringify(sort)
16380
+ };
16381
+ const cacheKey = (0, import_node_server_utils81.makeCacheKey)(namespace_collection, cacheOptions);
16382
+ const cachedData = await getCache(cacheKey);
16383
+ if (cachedData) {
16384
+ import_node_server_utils81.logger.info(`Cache hit for key: ${cacheKey}`);
16385
+ return cachedData;
16386
+ }
16387
+ try {
16388
+ const items = await collection.aggregate([
16389
+ { $match: query },
16390
+ { $sort: sort },
16391
+ { $skip: page * limit },
16392
+ { $limit: limit },
16393
+ {
16394
+ $project: {
16395
+ name: 1,
16396
+ phoneNumber: 1,
16397
+ plateNumber: 1,
16398
+ recNo: 1,
16399
+ nric: 1,
16400
+ status: 1,
16401
+ type: 1
16402
+ }
16403
+ }
16404
+ ]).toArray();
16405
+ const length = await collection.countDocuments(query);
16406
+ const data = (0, import_node_server_utils81.paginate)(items, page, limit, length);
16407
+ setCache(cacheKey, data, 15 * 60).then(() => import_node_server_utils81.logger.info(`Cache set for key: ${cacheKey}`)).catch(
16408
+ (err) => import_node_server_utils81.logger.error(`Failed to set cache for key: ${cacheKey}`, err)
16409
+ );
16410
+ return data;
16411
+ } catch (error) {
16412
+ throw error;
16413
+ }
16414
+ }
16415
+ async function deleteExpiredVehicles(session) {
16416
+ try {
16417
+ const tenYearsAgo = /* @__PURE__ */ new Date();
16418
+ tenYearsAgo.setFullYear(tenYearsAgo.getFullYear() - 10);
16419
+ const res = await collection.updateMany(
16420
+ {
16421
+ status: { $ne: "deleted" },
16422
+ end: { $exists: true, $lte: tenYearsAgo }
16423
+ // check only end
16424
+ },
16425
+ { $set: { status: "deleted", deletedAt: /* @__PURE__ */ new Date() } },
16426
+ { session }
16427
+ );
16428
+ if (res.modifiedCount === 0)
16429
+ throw new import_node_server_utils81.InternalServerError("Unable to delete vehicle.");
16430
+ delNamespace().then(() => {
16431
+ import_node_server_utils81.logger.info(`Cache cleared for namespace: ${namespace_collection}`);
16432
+ }).catch((err) => {
16433
+ import_node_server_utils81.logger.error(
16434
+ `Failed to clear cache for namespace: ${namespace_collection}`,
16435
+ err
16436
+ );
16437
+ });
16438
+ return res.modifiedCount;
16439
+ } catch (error) {
16440
+ throw error;
16441
+ }
16442
+ }
16003
16443
  return {
16004
16444
  createIndex,
16445
+ createTextIndex,
16005
16446
  add,
16006
16447
  getVehicles,
16007
16448
  getSeasonPassTypes,
@@ -16009,7 +16450,9 @@ function useVehicleRepo() {
16009
16450
  updateVehicle,
16010
16451
  deleteVehicle,
16011
16452
  getByPlaceNumber,
16012
- getVehicleByPlateNumber
16453
+ getVehicleByPlateNumber,
16454
+ getVehiclesByNRIC,
16455
+ deleteExpiredVehicles
16013
16456
  };
16014
16457
  }
16015
16458
 
@@ -16395,7 +16838,7 @@ function useDahuaService() {
16395
16838
  username: import_joi45.default.string().required(),
16396
16839
  password: import_joi45.default.string().required(),
16397
16840
  plateNumber: import_joi45.default.string().required(),
16398
- mode: import_joi45.default.string().valid("TrafficBlackList", "TrafficRedList").required(),
16841
+ mode: import_joi45.default.string().valid(...Object.values(ANPRMode)).required(),
16399
16842
  start: import_joi45.default.string().isoDate().optional().allow("", null),
16400
16843
  end: import_joi45.default.string().isoDate().optional().allow("", null),
16401
16844
  owner: import_joi45.default.string().optional().allow("", null)
@@ -16461,18 +16904,154 @@ function useDahuaService() {
16461
16904
 
16462
16905
  // src/services/vehicle.service.ts
16463
16906
  var import_node_server_utils83 = require("@7365admin1/node-server-utils");
16907
+ function formatDahuaDate(date) {
16908
+ const pad = (n) => String(n).padStart(2, "0");
16909
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(
16910
+ date.getDate()
16911
+ )} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(
16912
+ date.getSeconds()
16913
+ )}`;
16914
+ }
16464
16915
  function useVehicleService() {
16465
- const { add: _add, deleteVehicle: _deleteVehicle } = useVehicleRepo();
16916
+ const {
16917
+ add: _add,
16918
+ deleteVehicle: _deleteVehicle,
16919
+ updateVehicle: _updateVehicle,
16920
+ getVehicleById: _getVehicleById,
16921
+ deleteExpiredVehicles: _deleteExpiredVehicles
16922
+ } = useVehicleRepo();
16466
16923
  const {
16467
16924
  addPlateNumber: _addPlateNumber,
16468
16925
  removePlateNumber: _removePlateNumber
16469
16926
  } = useDahuaService();
16470
16927
  const { getAllCameraWithPassword: _getAllSiteCameras } = useSiteCameraRepo();
16928
+ const { getById: _getById } = useOrgRepo();
16471
16929
  async function add(value) {
16472
16930
  const session = import_node_server_utils83.useAtlas.getClient()?.startSession();
16473
16931
  if (!session) {
16474
16932
  throw new Error("Unable to start session for vehicle service.");
16475
16933
  }
16934
+ const org = await _getById(value.org);
16935
+ if (!org)
16936
+ throw new import_node_server_utils83.BadRequestError("Org not found");
16937
+ if (!Object.values(OrgNature).includes(org.nature)) {
16938
+ throw new import_node_server_utils83.BadRequestError(
16939
+ "This organization is not allowed to add vehicles."
16940
+ );
16941
+ }
16942
+ value.status = "active" /* ACTIVE */;
16943
+ const addedBySecurity = org.nature === "security_agency" /* SECURITY_AGENCY */;
16944
+ const addedByPM = org.nature === "property_management_agency" /* PROPERTY_MANAGEMENT_AGENCY */;
16945
+ if (addedBySecurity)
16946
+ value.status = "pending" /* PENDING */;
16947
+ const isBlocklist = value.type === "blocklist" /* BLOCKLIST */;
16948
+ if (isBlocklist)
16949
+ value.status = "inactive" /* INACTIVE */;
16950
+ const _type = value.type;
16951
+ const _mode = isBlocklist ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
16952
+ const hasStart = typeof value.start === "string" ? value.start.trim() !== "" : !!value.start;
16953
+ const hasEnd = typeof value.end === "string" ? value.end.trim() !== "" : !!value.end;
16954
+ let startDate = null;
16955
+ let endDate = null;
16956
+ let start = "";
16957
+ let end = "";
16958
+ let startDateDahua;
16959
+ let endDateDahua;
16960
+ if (addedByPM && !hasStart && !hasEnd) {
16961
+ startDate = /* @__PURE__ */ new Date();
16962
+ endDate = new Date(startDate);
16963
+ endDate.setFullYear(endDate.getFullYear() + 10);
16964
+ start = startDate.toISOString();
16965
+ end = endDate.toISOString();
16966
+ startDateDahua = formatDahuaDate(startDate);
16967
+ endDateDahua = formatDahuaDate(endDate);
16968
+ } else {
16969
+ if (hasStart) {
16970
+ startDate = new Date(value.start);
16971
+ start = startDate.toISOString();
16972
+ startDateDahua = formatDahuaDate(startDate);
16973
+ }
16974
+ if (hasEnd) {
16975
+ endDate = new Date(value.end);
16976
+ end = endDate.toISOString();
16977
+ endDateDahua = formatDahuaDate(endDate);
16978
+ }
16979
+ }
16980
+ const owner = String(value.name ?? "").substring(0, 15);
16981
+ const plateNumbers = value.plateNumber;
16982
+ try {
16983
+ session.startTransaction();
16984
+ let message = "Vehicle plate number needs approval from property management.";
16985
+ let siteCameras = [];
16986
+ if (value.status && value.status !== "pending" /* PENDING */) {
16987
+ let page = 1;
16988
+ let pages = 1;
16989
+ const limit = 20;
16990
+ do {
16991
+ const siteCameraReq = await _getAllSiteCameras({
16992
+ site: value.site,
16993
+ type: "anpr",
16994
+ direction: ["both", "entry"],
16995
+ page,
16996
+ limit
16997
+ });
16998
+ pages = siteCameraReq.pages || 1;
16999
+ siteCameras.push(...siteCameraReq.items);
17000
+ page++;
17001
+ } while (page <= pages);
17002
+ if (!siteCameras.length) {
17003
+ throw new import_node_server_utils83.BadRequestError("No site cameras found.");
17004
+ }
17005
+ }
17006
+ for (const plateNumber of plateNumbers) {
17007
+ const vehicleValue = {
17008
+ ...value,
17009
+ plateNumber,
17010
+ start,
17011
+ end
17012
+ };
17013
+ if (vehicleValue.status && vehicleValue.status !== "pending" /* PENDING */) {
17014
+ for (const camera of siteCameras) {
17015
+ const { host, username, password } = camera;
17016
+ const dahuaPayload = {
17017
+ host,
17018
+ username,
17019
+ password,
17020
+ plateNumber,
17021
+ mode: _mode,
17022
+ owner,
17023
+ ...startDateDahua ? { start: startDateDahua } : {},
17024
+ ...endDateDahua ? { end: endDateDahua } : {}
17025
+ };
17026
+ const dahuaResponse = await _addPlateNumber(dahuaPayload);
17027
+ if (dahuaResponse?.statusCode !== 200) {
17028
+ throw new import_node_server_utils83.BadRequestError(
17029
+ `Failed to add plate number to ANPR ${_type}`
17030
+ );
17031
+ }
17032
+ const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
17033
+ vehicleValue.recNo = responseData.split("=")[1]?.trim();
17034
+ }
17035
+ message = `Vehicle plate number added to ${_type} successfully.`;
17036
+ }
17037
+ await _add(vehicleValue, session);
17038
+ }
17039
+ await session.commitTransaction();
17040
+ return message;
17041
+ } catch (error) {
17042
+ import_node_server_utils83.logger.error("Error in vehicle service add:", error);
17043
+ await session.abortTransaction();
17044
+ throw error;
17045
+ } finally {
17046
+ session.endSession();
17047
+ }
17048
+ }
17049
+ async function deleteVehicle(_id, recno, site, type, bypass = false) {
17050
+ const session = import_node_server_utils83.useAtlas.getClient()?.startSession();
17051
+ if (!session) {
17052
+ throw new Error("Unable to start session for vehicle service.");
17053
+ }
17054
+ const _mode = type !== "whitelist" /* WHITELIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
16476
17055
  try {
16477
17056
  session.startTransaction();
16478
17057
  const siteCameras = [];
@@ -16481,9 +17060,8 @@ function useVehicleService() {
16481
17060
  const limit = 20;
16482
17061
  do {
16483
17062
  const siteCameraReq = await _getAllSiteCameras({
16484
- site: value.site,
17063
+ site,
16485
17064
  type: "anpr",
16486
- direction: ["both", "entry"],
16487
17065
  page,
16488
17066
  limit
16489
17067
  });
@@ -16495,55 +17073,82 @@ function useVehicleService() {
16495
17073
  throw new import_node_server_utils83.BadRequestError("No site cameras found.");
16496
17074
  }
16497
17075
  for (const camera of siteCameras) {
16498
- const { host, username, password } = camera;
17076
+ const host = camera.host;
17077
+ const username = camera.username;
17078
+ const password = camera.password;
16499
17079
  const dahuaPayload = {
16500
17080
  host,
16501
17081
  username,
16502
17082
  password,
16503
- plateNumber: value.plateNumber,
16504
- mode: "TrafficRedList",
16505
- start: value.start ? new Date(value.start).toISOString() : "",
16506
- end: value.end ? new Date(value.end).toISOString() : "",
16507
- owner: value.name
17083
+ recno,
17084
+ mode: _mode
16508
17085
  };
16509
- const dahuaResponse = await _addPlateNumber(dahuaPayload);
16510
- if (dahuaResponse?.statusCode !== 200) {
16511
- throw new import_node_server_utils83.BadRequestError("Failed to add plate number to ANPR");
17086
+ const dahuaResponse = await _removePlateNumber(dahuaPayload);
17087
+ if (!bypass && dahuaResponse?.statusCode !== 200) {
17088
+ throw new import_node_server_utils83.BadRequestError(
17089
+ `Failed to remove plate number to ANPR from ${type}`
17090
+ );
16512
17091
  }
16513
- const responseData = dahuaResponse?.data.toString("utf-8");
16514
- value.recNo = responseData.split("=")[1]?.trim();
16515
- const formattedValue = {
16516
- ...value,
16517
- start: value.start ? new Date(value.start).toISOString() : "",
16518
- end: value.end ? new Date(value.end).toISOString() : ""
16519
- };
16520
- await _add(formattedValue, session);
16521
17092
  }
17093
+ await _deleteVehicle(_id, session);
16522
17094
  await session.commitTransaction();
16523
- return "Vehicle plate number added successfully.";
17095
+ return `Vehicle plate number deleted from ${type} record successfully.`;
16524
17096
  } catch (error) {
16525
- import_node_server_utils83.logger.error("Error in vehicle service add:", error);
16526
17097
  await session.abortTransaction();
16527
- throw error;
17098
+ if (error instanceof import_node_server_utils83.AppError)
17099
+ throw error;
17100
+ throw new import_node_server_utils83.InternalServerError("Failed to delete vehicle");
16528
17101
  } finally {
16529
17102
  session.endSession();
16530
17103
  }
16531
17104
  }
16532
- async function deleteVehicle(_id, recno, site, bypass = false) {
17105
+ async function approveVehicleById(id, orgId, siteId) {
16533
17106
  const session = import_node_server_utils83.useAtlas.getClient()?.startSession();
16534
17107
  if (!session) {
16535
17108
  throw new Error("Unable to start session for vehicle service.");
16536
17109
  }
17110
+ const org = await _getById(orgId);
17111
+ if (!org)
17112
+ throw new import_node_server_utils83.BadRequestError("Org not found");
17113
+ const allowedNatures2 = ["property_management_agency"];
17114
+ if (!allowedNatures2.includes(org.nature)) {
17115
+ throw new import_node_server_utils83.BadRequestError(
17116
+ "Only property management can approve vehicles."
17117
+ );
17118
+ }
17119
+ const vehicle = await _getVehicleById(id);
17120
+ if (!vehicle) {
17121
+ throw new import_node_server_utils83.BadRequestError("Vehicle not found");
17122
+ }
17123
+ const owner = vehicle.name.substring(0, 15);
17124
+ const hasStart = typeof vehicle.start === "string" ? vehicle.start.trim() !== "" : !!vehicle.start;
17125
+ const hasEnd = typeof vehicle.end === "string" ? vehicle.end.trim() !== "" : !!vehicle.end;
17126
+ let startDate = null;
17127
+ let endDate = null;
17128
+ if (!hasStart && !hasEnd) {
17129
+ startDate = /* @__PURE__ */ new Date();
17130
+ endDate = new Date(startDate);
17131
+ endDate.setFullYear(endDate.getFullYear() + 10);
17132
+ } else {
17133
+ startDate = hasStart ? new Date(vehicle.start) : null;
17134
+ endDate = hasEnd ? new Date(vehicle.end) : null;
17135
+ }
17136
+ const startIso = startDate ? startDate.toISOString() : "";
17137
+ const endIso = endDate ? endDate.toISOString() : "";
17138
+ const startDahua = startDate ? formatDahuaDate(startDate) : "";
17139
+ const endDahua = endDate ? formatDahuaDate(endDate) : "";
16537
17140
  try {
16538
17141
  session.startTransaction();
16539
17142
  const siteCameras = [];
16540
17143
  let page = 1;
16541
17144
  let pages = 1;
16542
17145
  const limit = 20;
17146
+ let value = {};
16543
17147
  do {
16544
17148
  const siteCameraReq = await _getAllSiteCameras({
16545
- site,
17149
+ site: siteId,
16546
17150
  type: "anpr",
17151
+ direction: ["both", "entry"],
16547
17152
  page,
16548
17153
  limit
16549
17154
  });
@@ -16555,24 +17160,51 @@ function useVehicleService() {
16555
17160
  throw new import_node_server_utils83.BadRequestError("No site cameras found.");
16556
17161
  }
16557
17162
  for (const camera of siteCameras) {
16558
- const host = camera.host;
16559
- const username = camera.username;
16560
- const password = camera.password;
17163
+ const { host, username, password } = camera;
16561
17164
  const dahuaPayload = {
16562
17165
  host,
16563
17166
  username,
16564
17167
  password,
16565
- recno,
16566
- mode: "TrafficRedList"
17168
+ plateNumber: vehicle.plateNumber,
17169
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
17170
+ start: startDahua,
17171
+ end: endDahua,
17172
+ owner
16567
17173
  };
16568
- const dahuaResponse = await _removePlateNumber(dahuaPayload);
16569
- if (!bypass && dahuaResponse?.statusCode !== 200) {
16570
- throw new import_node_server_utils83.BadRequestError("Failed to remove plate number to ANPR");
17174
+ const dahuaResponse = await _addPlateNumber(dahuaPayload);
17175
+ if (dahuaResponse?.statusCode !== 200) {
17176
+ throw new import_node_server_utils83.BadRequestError("Failed to add plate number to ANPR");
16571
17177
  }
17178
+ const responseData = dahuaResponse?.data.toString("utf-8");
17179
+ value.recNo = responseData.split("=")[1]?.trim();
16572
17180
  }
16573
- await _deleteVehicle(_id, session);
17181
+ value.status = "active";
17182
+ const formattedValue = {
17183
+ ...value,
17184
+ start: startIso,
17185
+ end: endIso
17186
+ };
17187
+ await _updateVehicle(id, formattedValue, session);
16574
17188
  await session.commitTransaction();
16575
- return "Vehicle plate number deleted successfully.";
17189
+ return "Vehicle plate number approved and added successfully.";
17190
+ } catch (error) {
17191
+ import_node_server_utils83.logger.error("Error in vehicle service add:", error);
17192
+ await session.abortTransaction();
17193
+ throw error;
17194
+ } finally {
17195
+ session.endSession();
17196
+ }
17197
+ }
17198
+ async function processDeletingExpiredVehicles() {
17199
+ const session = import_node_server_utils83.useAtlas.getClient()?.startSession();
17200
+ if (!session) {
17201
+ throw new Error("Unable to start session for vehicle service.");
17202
+ }
17203
+ try {
17204
+ session.startTransaction();
17205
+ await _deleteExpiredVehicles();
17206
+ await session.commitTransaction();
17207
+ return `Expired Vehicle plate numbers deleted successfully.`;
16576
17208
  } catch (error) {
16577
17209
  await session.abortTransaction();
16578
17210
  if (error instanceof import_node_server_utils83.AppError)
@@ -16584,7 +17216,9 @@ function useVehicleService() {
16584
17216
  }
16585
17217
  return {
16586
17218
  add,
16587
- deleteVehicle
17219
+ deleteVehicle,
17220
+ approveVehicleById,
17221
+ processDeletingExpiredVehicles
16588
17222
  };
16589
17223
  }
16590
17224
 
@@ -16592,23 +17226,28 @@ function useVehicleService() {
16592
17226
  var import_node_server_utils84 = require("@7365admin1/node-server-utils");
16593
17227
  var import_joi46 = __toESM(require("joi"));
16594
17228
  function useVehicleController() {
16595
- const { add: _add, deleteVehicle: _deleteVehicle } = useVehicleService();
17229
+ const {
17230
+ add: _add,
17231
+ deleteVehicle: _deleteVehicle,
17232
+ approveVehicleById: _approveVehicleById
17233
+ } = useVehicleService();
16596
17234
  const {
16597
17235
  getSeasonPassTypes: _getSeasonPassTypes,
16598
17236
  getVehicles: _getVehicles,
16599
17237
  getVehicleById: _getVehicleById,
16600
- updateVehicle: _updateVehicle
17238
+ updateVehicle: _updateVehicle,
17239
+ getVehiclesByNRIC: _getVehiclesByNRIC
16601
17240
  } = useVehicleRepo();
16602
17241
  async function add(req, res, next) {
16603
17242
  const payload = req.body;
16604
- const { error } = vehicleSchema.validate(payload);
17243
+ const { error, value } = vehicleSchema.validate(payload);
16605
17244
  if (error) {
16606
17245
  import_node_server_utils84.logger.log({ level: "error", message: error.message });
16607
17246
  next(new import_node_server_utils84.BadRequestError(error.message));
16608
17247
  return;
16609
17248
  }
16610
17249
  try {
16611
- const data = await _add(payload);
17250
+ const data = await _add(value);
16612
17251
  res.status(201).json({
16613
17252
  message: "Vehicle added successfully.",
16614
17253
  data
@@ -16628,8 +17267,9 @@ function useVehicleController() {
16628
17267
  limit: import_joi46.default.number().integer().min(1).max(100).allow("", null).default(10),
16629
17268
  sort: import_joi46.default.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
16630
17269
  order: import_joi46.default.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
16631
- type: import_joi46.default.string().optional().allow("", ...allowedTypes),
16632
- category: import_joi46.default.string().optional().allow("", ...allowedCategories)
17270
+ type: import_joi46.default.string().optional().valid(...Object.values(VehicleType)).allow(null, ""),
17271
+ category: import_joi46.default.string().optional().valid(...Object.values(VehicleCategory)).allow(null, ""),
17272
+ status: import_joi46.default.string().optional().valid(...Object.values(VehicleStatus)).allow(null, "")
16633
17273
  });
16634
17274
  const query = { ...req.query };
16635
17275
  const { error } = validation.validate(query);
@@ -16643,6 +17283,7 @@ function useVehicleController() {
16643
17283
  const limit = parseInt(req.query.limit ?? "10");
16644
17284
  const type = req.query.type ?? "";
16645
17285
  const category = req.query.category ?? "";
17286
+ const status = req.query.status ?? "";
16646
17287
  const sortObj = {};
16647
17288
  const sortFields = String(req.query.sort).split(",");
16648
17289
  const sortOrders = String(req.query.order).split(",");
@@ -16659,7 +17300,8 @@ function useVehicleController() {
16659
17300
  limit,
16660
17301
  sort: sortObj,
16661
17302
  type,
16662
- category
17303
+ category,
17304
+ status
16663
17305
  });
16664
17306
  res.json(data);
16665
17307
  return;
@@ -16715,7 +17357,8 @@ function useVehicleController() {
16715
17357
  block: import_joi46.default.number().integer().optional().allow(0, null),
16716
17358
  level: import_joi46.default.string().optional().allow("", null),
16717
17359
  unit: import_joi46.default.string().optional().allow("", null),
16718
- plateNumber: import_joi46.default.string().optional().allow("", null)
17360
+ plateNumber: import_joi46.default.string().optional().allow("", null),
17361
+ nric: import_joi46.default.string().optional().allow("", null)
16719
17362
  });
16720
17363
  const _id = req.params.id;
16721
17364
  const payload = { ...req.body };
@@ -16736,23 +17379,23 @@ function useVehicleController() {
16736
17379
  }
16737
17380
  }
16738
17381
  async function deleteVehicle(req, res, next) {
16739
- const site = req.params.site ?? "";
16740
- const _id = req.params.id ?? "";
16741
- const recno = req.params.recno ?? "";
16742
- const bypass = req.query.bypass === "true";
17382
+ const _id = req.params.id;
16743
17383
  const deleteVehicleSchema = import_joi46.default.object({
16744
- _id: import_joi46.default.string().hex().required(),
17384
+ _id: import_joi46.default.string().hex().length(24).required(),
16745
17385
  recno: import_joi46.default.string().required(),
16746
- bypass: import_joi46.default.boolean().optional()
17386
+ site: import_joi46.default.string().hex().length(24).required(),
17387
+ type: import_joi46.default.string().valid("whitelist", "blocklist").required(),
17388
+ bypass: import_joi46.default.boolean().optional().default(true)
16747
17389
  });
16748
- const { error } = deleteVehicleSchema.validate({ _id, recno, bypass });
17390
+ const { error, value } = deleteVehicleSchema.validate({ _id, ...req.body });
16749
17391
  if (error) {
16750
17392
  import_node_server_utils84.logger.log({ level: "error", message: error.message });
16751
17393
  next(new import_node_server_utils84.BadRequestError(error.message));
16752
17394
  return;
16753
17395
  }
17396
+ const { recno, site, type, bypass } = value;
16754
17397
  try {
16755
- const data = await _deleteVehicle(_id, recno, site, bypass);
17398
+ const data = await _deleteVehicle(_id, recno, site, type, bypass);
16756
17399
  res.json({
16757
17400
  message: "Vehicle deleted successfully.",
16758
17401
  data
@@ -16763,13 +17406,83 @@ function useVehicleController() {
16763
17406
  return;
16764
17407
  }
16765
17408
  }
17409
+ async function approveVehicleById(req, res, next) {
17410
+ const validation = import_joi46.default.object({
17411
+ _id: import_joi46.default.string().hex().length(24).required(),
17412
+ org: import_joi46.default.string().hex().length(24).required(),
17413
+ site: import_joi46.default.string().hex().length(24).required()
17414
+ });
17415
+ const _id = req.params.id;
17416
+ const payload = { ...req.body };
17417
+ const { error, value } = validation.validate({ _id, ...payload });
17418
+ if (error) {
17419
+ import_node_server_utils84.logger.log({ level: "error", message: error.message });
17420
+ next(new import_node_server_utils84.BadRequestError(error.message));
17421
+ return;
17422
+ }
17423
+ try {
17424
+ await _approveVehicleById(value._id, value.org, value.site);
17425
+ res.json({ message: "Successfully approved and updated vehicle." });
17426
+ return;
17427
+ } catch (error2) {
17428
+ import_node_server_utils84.logger.log({ level: "error", message: error2.message });
17429
+ next(error2);
17430
+ return;
17431
+ }
17432
+ }
17433
+ async function getVehiclesByNRIC(req, res, next) {
17434
+ const allowedFields = ["start", "end"];
17435
+ const allowedOrder = ["asc", "desc"];
17436
+ const validation = import_joi46.default.object({
17437
+ nric: import_joi46.default.string().optional().allow("", null),
17438
+ page: import_joi46.default.number().integer().min(1).allow("", null).default(1),
17439
+ limit: import_joi46.default.number().integer().min(1).max(100).allow("", null).default(10),
17440
+ sort: import_joi46.default.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
17441
+ order: import_joi46.default.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder)
17442
+ });
17443
+ const query = { ...req.query };
17444
+ const { error } = validation.validate(query);
17445
+ if (error) {
17446
+ import_node_server_utils84.logger.log({ level: "error", message: error.message });
17447
+ next(new import_node_server_utils84.BadRequestError(error.message));
17448
+ return;
17449
+ }
17450
+ const nric = req.query.nric ?? "";
17451
+ const page = parseInt(req.query.page ?? "1");
17452
+ const limit = parseInt(req.query.limit ?? "10");
17453
+ const sortObj = {};
17454
+ const sortFields = String(req.query.sort).split(",");
17455
+ const sortOrders = String(req.query.order).split(",");
17456
+ sortFields.forEach((field, index) => {
17457
+ if (allowedFields.includes(field)) {
17458
+ const order = sortOrders[index] === "asc" ? 1 : -1;
17459
+ sortObj[field] = order;
17460
+ }
17461
+ });
17462
+ try {
17463
+ const data = await _getVehiclesByNRIC({
17464
+ nric,
17465
+ page,
17466
+ limit,
17467
+ sort: sortObj
17468
+ });
17469
+ res.json(data);
17470
+ return;
17471
+ } catch (error2) {
17472
+ import_node_server_utils84.logger.log({ level: "error", message: error2.message });
17473
+ next(error2);
17474
+ return;
17475
+ }
17476
+ }
16766
17477
  return {
16767
17478
  add,
16768
17479
  getVehicles,
16769
17480
  getSeasonPassTypes,
16770
17481
  getVehicleById,
16771
17482
  updateVehicle,
16772
- deleteVehicle
17483
+ deleteVehicle,
17484
+ approveVehicleById,
17485
+ getVehiclesByNRIC
16773
17486
  };
16774
17487
  }
16775
17488
 
@@ -16868,7 +17581,7 @@ function useSiteCameraController() {
16868
17581
  }
16869
17582
  async function getAll(req, res, next) {
16870
17583
  const query = req.query;
16871
- const allowedTypes2 = ["ip", "exit", "entry", "both"];
17584
+ const allowedTypes = ["ip", "exit", "entry", "both"];
16872
17585
  const validation = import_joi47.default.object({
16873
17586
  type: import_joi47.default.string().optional(),
16874
17587
  site: import_joi47.default.string().optional(),
@@ -19349,25 +20062,15 @@ function usePersonService() {
19349
20062
  }
19350
20063
  async function plateValidity(value) {
19351
20064
  const now = /* @__PURE__ */ new Date();
19352
- if (value.type !== "resident" && value.type !== "tenant") {
19353
- return { start: "", end: "" };
19354
- }
19355
- if (value.type === "resident") {
20065
+ if (["resident" /* RESIDENT */, "tenant" /* TENANT */].includes(
20066
+ value?.type
20067
+ )) {
19356
20068
  const end = new Date(now);
19357
20069
  end.setFullYear(end.getFullYear() + 10);
19358
- return { start: now.toISOString(), end: end.toISOString() };
19359
- }
19360
- if (value.type == "tenant") {
19361
- const unit = await _getUnitById(value.unit?.toString() || "");
19362
- if (!unit) {
19363
- throw new import_node_server_utils103.BadRequestError("Building unit not found for tenant.");
19364
- }
19365
- if (unit.leaseStart && unit.leaseEnd) {
19366
- return {
19367
- start: new Date(unit.leaseStart).toISOString(),
19368
- end: new Date(unit.leaseEnd).toISOString()
19369
- };
19370
- }
20070
+ return {
20071
+ start: now.toISOString(),
20072
+ end: end.toISOString()
20073
+ };
19371
20074
  }
19372
20075
  return { start: "", end: "" };
19373
20076
  }
@@ -19386,7 +20089,8 @@ function usePersonController() {
19386
20089
  getPersonByPhoneNumber: _getPersonByPhoneNumber,
19387
20090
  getPeopleByUnit: _getPeopleByUnit,
19388
20091
  getCompany: _getCompany,
19389
- getPeopleByPlateNumber: _getPeopleByPlateNumber
20092
+ getPeopleByPlateNumber: _getPeopleByPlateNumber,
20093
+ getPeopleByNRIC: _getPeopleByNRIC
19390
20094
  } = usePersonRepo();
19391
20095
  const { add: _add, updateById: _updateById } = usePersonService();
19392
20096
  async function add(req, res, next) {
@@ -19621,6 +20325,53 @@ function usePersonController() {
19621
20325
  return;
19622
20326
  }
19623
20327
  }
20328
+ async function getPeopleByNRIC(req, res, next) {
20329
+ const allowedFields = ["start", "end"];
20330
+ const allowedOrder = ["asc", "desc"];
20331
+ const validation = import_joi58.default.object({
20332
+ nric: import_joi58.default.string().optional().allow("", null),
20333
+ page: import_joi58.default.number().integer().min(1).allow("", null).default(1),
20334
+ limit: import_joi58.default.number().integer().min(1).max(100).allow("", null).default(10),
20335
+ sort: import_joi58.default.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
20336
+ order: import_joi58.default.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
20337
+ site: import_joi58.default.string().hex().length(24).required()
20338
+ });
20339
+ const query = { ...req.query };
20340
+ const { error } = validation.validate(query);
20341
+ if (error) {
20342
+ import_node_server_utils104.logger.log({ level: "error", message: error.message });
20343
+ next(new import_node_server_utils104.BadRequestError(error.message));
20344
+ return;
20345
+ }
20346
+ const nric = req.query.nric ?? "";
20347
+ const page = parseInt(req.query.page ?? "1");
20348
+ const limit = parseInt(req.query.limit ?? "10");
20349
+ const site = req.query.site ?? "";
20350
+ const sortObj = {};
20351
+ const sortFields = String(req.query.sort).split(",");
20352
+ const sortOrders = String(req.query.order).split(",");
20353
+ sortFields.forEach((field, index) => {
20354
+ if (allowedFields.includes(field)) {
20355
+ const order = sortOrders[index] === "asc" ? 1 : -1;
20356
+ sortObj[field] = order;
20357
+ }
20358
+ });
20359
+ try {
20360
+ const data = await _getPeopleByNRIC({
20361
+ nric,
20362
+ page,
20363
+ limit,
20364
+ sort: sortObj,
20365
+ site
20366
+ });
20367
+ res.json(data);
20368
+ return;
20369
+ } catch (error2) {
20370
+ import_node_server_utils104.logger.log({ level: "error", message: error2.message });
20371
+ next(error2);
20372
+ return;
20373
+ }
20374
+ }
19624
20375
  return {
19625
20376
  add,
19626
20377
  getAll,
@@ -19630,7 +20381,8 @@ function usePersonController() {
19630
20381
  getPersonByPhoneNumber,
19631
20382
  getPeopleByUnit,
19632
20383
  getCompany,
19633
- getPeopleByPlateNumber
20384
+ getPeopleByPlateNumber,
20385
+ getPeopleByNRIC
19634
20386
  };
19635
20387
  }
19636
20388
 
@@ -24228,20 +24980,19 @@ function useDocumentManagementController() {
24228
24980
  // src/models/bulletin-board.model.ts
24229
24981
  var import_joi75 = __toESM(require("joi"));
24230
24982
  var import_mongodb71 = require("mongodb");
24983
+ var BULLETIN_RECIPIENTS = [
24984
+ "resident",
24985
+ "security_agency",
24986
+ "cleaning_services",
24987
+ "mechanical_electrical",
24988
+ "property_management_agency"
24989
+ ];
24990
+ var STATUS_VALUES = ["active", "expired", "deleted"];
24231
24991
  var schemaBulletinBoard = import_joi75.default.object({
24232
24992
  _id: import_joi75.default.string().hex().optional().allow("", null),
24233
24993
  site: import_joi75.default.string().hex().optional().allow("", null),
24234
24994
  orgId: import_joi75.default.string().hex().optional().allow("", null),
24235
- recipients: import_joi75.default.array().items(
24236
- import_joi75.default.string().valid(
24237
- "admin",
24238
- "organization",
24239
- "site",
24240
- "service-provider",
24241
- "service-provider-member",
24242
- "resident"
24243
- )
24244
- ).optional(),
24995
+ recipients: import_joi75.default.array().items(import_joi75.default.string().valid(...BULLETIN_RECIPIENTS)).unique().optional(),
24245
24996
  title: import_joi75.default.string().optional().allow("", null),
24246
24997
  content: import_joi75.default.string().optional().allow("", null),
24247
24998
  file: import_joi75.default.array().items(
@@ -24254,23 +25005,14 @@ var schemaBulletinBoard = import_joi75.default.object({
24254
25005
  noExpiration: import_joi75.default.boolean().optional(),
24255
25006
  startDate: import_joi75.default.date().optional().allow("", null),
24256
25007
  endDate: import_joi75.default.date().optional().allow("", null),
24257
- status: import_joi75.default.string().optional().allow("", null),
25008
+ status: import_joi75.default.string().valid(...STATUS_VALUES).optional(),
24258
25009
  createdAt: import_joi75.default.date().optional().allow(null),
24259
25010
  updatedAt: import_joi75.default.date().optional().allow(null),
24260
25011
  deletedAt: import_joi75.default.date().optional().allow(null)
24261
25012
  });
24262
25013
  var schemaUpdateBulletinBoard = import_joi75.default.object({
24263
25014
  _id: import_joi75.default.string().hex().required(),
24264
- recipients: import_joi75.default.array().items(
24265
- import_joi75.default.string().valid(
24266
- "admin",
24267
- "organization",
24268
- "site",
24269
- "service-provider",
24270
- "service-provider-member",
24271
- "resident"
24272
- )
24273
- ).optional(),
25015
+ recipients: import_joi75.default.array().items(import_joi75.default.string().valid(...BULLETIN_RECIPIENTS)).unique().optional(),
24274
25016
  title: import_joi75.default.string().optional().allow("", null),
24275
25017
  content: import_joi75.default.string().optional().allow("", null),
24276
25018
  file: import_joi75.default.array().items(
@@ -24283,7 +25025,7 @@ var schemaUpdateBulletinBoard = import_joi75.default.object({
24283
25025
  noExpiration: import_joi75.default.boolean().optional(),
24284
25026
  startDate: import_joi75.default.date().optional().allow("", null),
24285
25027
  endDate: import_joi75.default.date().optional().allow("", null),
24286
- status: import_joi75.default.string().optional().allow("", null)
25028
+ status: import_joi75.default.string().valid(...STATUS_VALUES).optional()
24287
25029
  });
24288
25030
  function MBulletinBoard(value) {
24289
25031
  const { error } = schemaBulletinBoard.validate(value);
@@ -24374,7 +25116,8 @@ function useBulletinBoardRepo() {
24374
25116
  limit = 10,
24375
25117
  sort = {},
24376
25118
  site = "",
24377
- status = "active"
25119
+ status = "active",
25120
+ recipients = []
24378
25121
  }, session) {
24379
25122
  page = page > 0 ? page - 1 : 0;
24380
25123
  try {
@@ -24384,7 +25127,9 @@ function useBulletinBoardRepo() {
24384
25127
  }
24385
25128
  const query = {
24386
25129
  site,
24387
- status
25130
+ status,
25131
+ ...search && { $text: { $search: search } },
25132
+ ...recipients?.length && { recipients: { $in: recipients } }
24388
25133
  };
24389
25134
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
24390
25135
  const cacheOptions = {
@@ -24392,12 +25137,10 @@ function useBulletinBoardRepo() {
24392
25137
  status,
24393
25138
  sort: JSON.stringify(sort),
24394
25139
  page,
24395
- limit
25140
+ limit,
25141
+ ...search && { search },
25142
+ ...recipients?.length && { recipients: recipients.sort().join(",") }
24396
25143
  };
24397
- if (search) {
24398
- query.$text = { $search: search };
24399
- cacheOptions.search = search;
24400
- }
24401
25144
  const cacheKey = (0, import_node_server_utils131.makeCacheKey)(namespace_collection, cacheOptions);
24402
25145
  const cachedData = await getCache(cacheKey);
24403
25146
  if (cachedData) {
@@ -24489,7 +25232,7 @@ function useBulletinBoardRepo() {
24489
25232
  throw error;
24490
25233
  }
24491
25234
  }
24492
- async function deleteBulletinBoardById(_id) {
25235
+ async function deleteBulletinBoardById(_id, session) {
24493
25236
  try {
24494
25237
  _id = new import_mongodb72.ObjectId(_id);
24495
25238
  } catch (error) {
@@ -24501,7 +25244,11 @@ function useBulletinBoardRepo() {
24501
25244
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
24502
25245
  deletedAt: (/* @__PURE__ */ new Date()).toISOString()
24503
25246
  };
24504
- const res = await collection.updateOne({ _id }, { $set: updateValue });
25247
+ const res = await collection.updateOne(
25248
+ { _id },
25249
+ { $set: updateValue },
25250
+ { session }
25251
+ );
24505
25252
  if (res.modifiedCount === 0) {
24506
25253
  throw new import_node_server_utils131.InternalServerError("Unable to delete bulletin board.");
24507
25254
  }
@@ -24567,8 +25314,11 @@ function useBulletinBoardService() {
24567
25314
  const {
24568
25315
  add: _add,
24569
25316
  updateBulletinBoardById: _updateBulletinBoardById,
24570
- processExpiredBulletinBoards: _processExpiredBulletinBoards
25317
+ processExpiredBulletinBoards: _processExpiredBulletinBoards,
25318
+ deleteBulletinBoardById: _deleteBulletinBoardById,
25319
+ getBulletinBoardById: _getBulletinBoardById
24571
25320
  } = useBulletinBoardRepo();
25321
+ const { deleteFileById: _deleteFileById } = useFileRepo();
24572
25322
  async function add(value) {
24573
25323
  const session = import_node_server_utils132.useAtlas.getClient()?.startSession();
24574
25324
  session?.startTransaction();
@@ -24611,10 +25361,31 @@ function useBulletinBoardService() {
24611
25361
  session?.endSession();
24612
25362
  }
24613
25363
  }
25364
+ async function deleteBulletinBoardById(id) {
25365
+ const session = import_node_server_utils132.useAtlas.getClient()?.startSession();
25366
+ session?.startTransaction();
25367
+ try {
25368
+ const existingBulletinBoard = await _getBulletinBoardById(id);
25369
+ if (Array.isArray(existingBulletinBoard.file)) {
25370
+ for (const file of existingBulletinBoard.file) {
25371
+ await _deleteFileById(file, session);
25372
+ }
25373
+ }
25374
+ await _deleteBulletinBoardById(id, session);
25375
+ await session?.commitTransaction();
25376
+ return "Successfully deleted bulletin board.";
25377
+ } catch (error) {
25378
+ await session?.abortTransaction();
25379
+ throw error;
25380
+ } finally {
25381
+ session?.endSession();
25382
+ }
25383
+ }
24614
25384
  return {
24615
25385
  add,
24616
25386
  updateBulletinBoardById,
24617
- processExpiredBulletinBoards
25387
+ processExpiredBulletinBoards,
25388
+ deleteBulletinBoardById
24618
25389
  };
24619
25390
  }
24620
25391
 
@@ -24622,12 +25393,12 @@ function useBulletinBoardService() {
24622
25393
  var import_node_server_utils133 = require("@7365admin1/node-server-utils");
24623
25394
  var import_joi76 = __toESM(require("joi"));
24624
25395
  function useBulletinBoardController() {
24625
- const { add: _add, updateBulletinBoardById: _updateBulletinBoardById } = useBulletinBoardService();
24626
25396
  const {
24627
- getAll: _getAll,
24628
- getBulletinBoardById: _getBulletinBoardById,
25397
+ add: _add,
25398
+ updateBulletinBoardById: _updateBulletinBoardById,
24629
25399
  deleteBulletinBoardById: _deleteBulletinBoardById
24630
- } = useBulletinBoardRepo();
25400
+ } = useBulletinBoardService();
25401
+ const { getAll: _getAll, getBulletinBoardById: _getBulletinBoardById } = useBulletinBoardRepo();
24631
25402
  async function add(req, res, next) {
24632
25403
  const payload = { ...req.body };
24633
25404
  const { error } = schemaBulletinBoard.validate(payload, {
@@ -24659,12 +25430,23 @@ function useBulletinBoardController() {
24659
25430
  sort: import_joi76.default.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
24660
25431
  order: import_joi76.default.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
24661
25432
  site: import_joi76.default.string().hex().required(),
24662
- status: import_joi76.default.string().optional().allow(null, "")
25433
+ status: import_joi76.default.string().optional().allow(null, ""),
25434
+ recipients: import_joi76.default.string().optional().allow("", null).custom((value, helpers) => {
25435
+ const parsed = value.split(",").map((v) => v.trim()).filter(Boolean);
25436
+ for (const r of parsed) {
25437
+ if (!BULLETIN_RECIPIENTS.includes(r)) {
25438
+ return helpers.error("any.only");
25439
+ }
25440
+ }
25441
+ return value;
25442
+ }).messages({
25443
+ "any.only": `Recipients must be one of: ${BULLETIN_RECIPIENTS.join(
25444
+ ", "
25445
+ )}`
25446
+ })
24663
25447
  });
24664
25448
  const query = { ...req.query };
24665
- const { error } = validation.validate(query, {
24666
- abortEarly: false
24667
- });
25449
+ const { error } = validation.validate(query, { abortEarly: false });
24668
25450
  if (error) {
24669
25451
  const messages = error.details.map((d) => d.message).join(", ");
24670
25452
  import_node_server_utils133.logger.log({ level: "error", message: messages });
@@ -24676,9 +25458,15 @@ function useBulletinBoardController() {
24676
25458
  const limit = parseInt(req.query.limit ?? "10");
24677
25459
  const site = req.query.site ?? "";
24678
25460
  const status = req.query.status ?? "active";
25461
+ const recipientsRaw = req.query.recipients ?? "";
25462
+ const recipients = recipientsRaw && typeof recipientsRaw === "string" ? Array.from(
25463
+ new Set(
25464
+ recipientsRaw.split(",").map((r) => r.trim()).filter(Boolean)
25465
+ )
25466
+ ) : [];
24679
25467
  const sortObj = {};
24680
- const sortFields = String(req.query.sort).split(",");
24681
- const sortOrders = String(req.query.order).split(",");
25468
+ const sortFields = String(req.query.sort ?? "").split(",").filter(Boolean);
25469
+ const sortOrders = String(req.query.order ?? "").split(",").filter(Boolean);
24682
25470
  sortFields.forEach((field, index) => {
24683
25471
  if (allowedFields.includes(field)) {
24684
25472
  const order = sortOrders[index] === "asc" ? 1 : -1;
@@ -24692,7 +25480,8 @@ function useBulletinBoardController() {
24692
25480
  limit,
24693
25481
  sort: sortObj,
24694
25482
  site,
24695
- status
25483
+ status,
25484
+ recipients
24696
25485
  });
24697
25486
  res.status(200).json(data);
24698
25487
  return;
@@ -24775,6 +25564,16 @@ function useBulletinBoardController() {
24775
25564
  var import_node_server_utils134 = require("@7365admin1/node-server-utils");
24776
25565
  var import_mongodb73 = require("mongodb");
24777
25566
  var import_joi77 = __toESM(require("joi"));
25567
+
25568
+ // src/types/enums/billing-frequency.enum.ts
25569
+ var EBillingFrequency = /* @__PURE__ */ ((EBillingFrequency2) => {
25570
+ EBillingFrequency2["MONTHLY"] = "monthly";
25571
+ EBillingFrequency2["QAURTERLY"] = "quarterly";
25572
+ EBillingFrequency2["ANNUALLY"] = "annually";
25573
+ return EBillingFrequency2;
25574
+ })(EBillingFrequency || {});
25575
+
25576
+ // src/models/site-billing-item.model.ts
24778
25577
  var schemaUnits = import_joi77.default.object({
24779
25578
  _id: import_joi77.default.string().hex().optional(),
24780
25579
  name: import_joi77.default.string().optional()
@@ -24785,7 +25584,7 @@ var schemaBillingItem = import_joi77.default.object({
24785
25584
  org: import_joi77.default.string().hex().required(),
24786
25585
  name: import_joi77.default.string().required(),
24787
25586
  amount: import_joi77.default.number().required(),
24788
- frequency: import_joi77.default.string().valid("month", "quarter", "annual").required(),
25587
+ frequency: import_joi77.default.string().valid(...Object.values(EBillingFrequency)).required(),
24789
25588
  billingType: import_joi77.default.string().valid("recurring", "non-recurring").required(),
24790
25589
  dueInDays: import_joi77.default.number().optional().allow(null, ""),
24791
25590
  date: import_joi77.default.string().required(),
@@ -24810,7 +25609,7 @@ var schemaUpdateSiteBillingItem = import_joi77.default.object({
24810
25609
  org: import_joi77.default.string().hex().optional().allow(null, ""),
24811
25610
  name: import_joi77.default.string().optional().allow(null, ""),
24812
25611
  amount: import_joi77.default.number().optional().allow(null, ""),
24813
- frequency: import_joi77.default.string().valid("month", "quarter", "annual").optional().allow(null, ""),
25612
+ frequency: import_joi77.default.string().valid(...Object.values(EBillingFrequency)).optional().allow(null, ""),
24814
25613
  billingType: import_joi77.default.string().valid("recurring", "non-recurring").optional().allow(null, ""),
24815
25614
  dueInDays: import_joi77.default.number().optional().allow(null, ""),
24816
25615
  date: import_joi77.default.string().optional().allow(null, ""),
@@ -24955,7 +25754,16 @@ function useSiteBillingItemRepo() {
24955
25754
  ...search && {
24956
25755
  $or: [
24957
25756
  { name: { $regex: search, $options: "i" } },
24958
- { frequency: { $regex: search, $options: "i" } }
25757
+ { frequency: { $regex: search, $options: "i" } },
25758
+ {
25759
+ $expr: {
25760
+ $regexMatch: {
25761
+ input: { $toString: "$totalAmount" },
25762
+ regex: search,
25763
+ options: "i"
25764
+ }
25765
+ }
25766
+ }
24959
25767
  ]
24960
25768
  },
24961
25769
  ...import_mongodb74.ObjectId.isValid(site) && { site: new import_mongodb74.ObjectId(site) }
@@ -26105,7 +26913,8 @@ function useEventManagementRepo() {
26105
26913
  sort = {},
26106
26914
  site = "",
26107
26915
  status = "",
26108
- type = ""
26916
+ type = "",
26917
+ date = ""
26109
26918
  }, session) {
26110
26919
  page = page > 0 ? page - 1 : 0;
26111
26920
  try {
@@ -26116,7 +26925,13 @@ function useEventManagementRepo() {
26116
26925
  const baseQuery = {
26117
26926
  site,
26118
26927
  status: status ? status : { $ne: "deleted" },
26119
- ...type && { type }
26928
+ ...type && { type },
26929
+ ...date && {
26930
+ dateTime: {
26931
+ $gte: `${date}T00:00:00.000Z`,
26932
+ $lt: `${date}T23:59:59.999Z`
26933
+ }
26934
+ }
26120
26935
  };
26121
26936
  let query = { ...baseQuery };
26122
26937
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
@@ -26414,7 +27229,8 @@ function useEventManagementController() {
26414
27229
  order: import_joi82.default.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
26415
27230
  site: import_joi82.default.string().hex().required(),
26416
27231
  status: import_joi82.default.string().optional(),
26417
- type: import_joi82.default.string().optional().valid("TASK", "EVENT").allow(null, "")
27232
+ type: import_joi82.default.string().optional().valid("TASK", "EVENT").allow(null, ""),
27233
+ date: import_joi82.default.string().optional().allow(null, "")
26418
27234
  });
26419
27235
  const query = { ...req.query };
26420
27236
  const { error } = validation.validate(query, {
@@ -26432,6 +27248,7 @@ function useEventManagementController() {
26432
27248
  const site = req.query.site ?? "";
26433
27249
  const status = req.query.status ?? "";
26434
27250
  const type = req.query.type ?? "";
27251
+ const date = req.query.date ?? "";
26435
27252
  const sortObj = {};
26436
27253
  const sortFields = String(req.query.sort).split(",");
26437
27254
  const sortOrders = String(req.query.order).split(",");
@@ -26449,7 +27266,8 @@ function useEventManagementController() {
26449
27266
  sort: sortObj,
26450
27267
  site,
26451
27268
  status,
26452
- type
27269
+ type,
27270
+ date
26453
27271
  });
26454
27272
  res.status(200).json(data);
26455
27273
  return;
@@ -26766,6 +27584,7 @@ function useSiteUnitBillingRepo() {
26766
27584
  }
26767
27585
  };
26768
27586
  }
27587
+ const unitSearchRegex = search ? search.trim().replace(/\s+/g, "").replace(/\//g, "\\s*/\\s*") : null;
26769
27588
  const query = {
26770
27589
  paymentStatus,
26771
27590
  status,
@@ -26773,7 +27592,16 @@ function useSiteUnitBillingRepo() {
26773
27592
  $or: [
26774
27593
  { unitOwner: { $regex: search, $options: "i" } },
26775
27594
  { billName: { $regex: search, $options: "i" } },
26776
- { unit: { $regex: search, $options: "i" } }
27595
+ { unit: { $regex: unitSearchRegex, $options: "i" } },
27596
+ {
27597
+ $expr: {
27598
+ $regexMatch: {
27599
+ input: { $toString: "$amountPaid" },
27600
+ regex: search,
27601
+ options: "i"
27602
+ }
27603
+ }
27604
+ }
26777
27605
  ]
26778
27606
  },
26779
27607
  ...import_mongodb80.ObjectId.isValid(site) && { site: new import_mongodb80.ObjectId(site) },
@@ -27196,14 +28024,14 @@ function useSiteUnitBillingService() {
27196
28024
  function isBillingChecker(billing_item, todayMonth, todayDate) {
27197
28025
  const billingMonth = Number(billing_item.month);
27198
28026
  const billingDay = Number(billing_item.date);
27199
- if (billing_item.frequency === "month") {
28027
+ if (billing_item.frequency === "monthly" /* MONTHLY */) {
27200
28028
  return todayDate === billingDay;
27201
28029
  }
27202
- if (billing_item.frequency === "quarter") {
28030
+ if (billing_item.frequency === "quarterly" /* QAURTERLY */) {
27203
28031
  const monthDiff = todayMonth - billingMonth;
27204
28032
  return monthDiff % 3 === 0 && todayDate === billingDay;
27205
28033
  }
27206
- if (billing_item.frequency === "annual") {
28034
+ if (billing_item.frequency === "annually" /* ANNUALLY */) {
27207
28035
  return todayMonth === billingMonth && todayDate === billingDay;
27208
28036
  }
27209
28037
  return false;
@@ -27995,7 +28823,7 @@ function UseAccessManagementRepo() {
27995
28823
  {
27996
28824
  $match: {
27997
28825
  ...defaultQuery,
27998
- status: { $ne: "deleted" }
28826
+ status: { $eq: "active" }
27999
28827
  }
28000
28828
  },
28001
28829
  // ✅ Only project needed fields before heavy lookups
@@ -28115,7 +28943,7 @@ function UseAccessManagementRepo() {
28115
28943
  userType
28116
28944
  }
28117
28945
  },
28118
- { $project: { _id: 1, userId: 1, type: 1, cardNo: 1, isActivated: 1 } }
28946
+ { $project: { _id: 1, userId: 1, type: 1, cardNo: 1, isActivated: 1, replacementStatus: 1 } }
28119
28947
  ],
28120
28948
  as: "accessCards"
28121
28949
  }
@@ -28134,7 +28962,21 @@ function UseAccessManagementRepo() {
28134
28962
  $filter: {
28135
28963
  input: "$accessCards",
28136
28964
  as: "card",
28137
- cond: { $ne: ["$$card.userId", null] }
28965
+ cond: { $and: [{ $ne: ["$$card.userId", null] }, { $eq: ["$$card.isActivated", true] }] }
28966
+ }
28967
+ },
28968
+ f_replaced: {
28969
+ $filter: {
28970
+ input: "$accessCards",
28971
+ as: "card",
28972
+ cond: { $and: [{ $eq: ["$$card.isActivated", false] }, { $ne: ["$$card.replacementStatus", null] }] }
28973
+ }
28974
+ },
28975
+ f_deleted: {
28976
+ $filter: {
28977
+ input: "$accessCards",
28978
+ as: "card",
28979
+ cond: { $and: [{ $eq: ["$$card.isActivated", false] }, { $eq: ["$$card.replacementStatus", null] }] }
28138
28980
  }
28139
28981
  }
28140
28982
  }
@@ -28166,6 +29008,14 @@ function UseAccessManagementRepo() {
28166
29008
  non_physical: { $size: { $filter: { input: { $ifNull: ["$f_Assigned", []] }, as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } } }
28167
29009
  }
28168
29010
  },
29011
+ replaced: {
29012
+ physical: { $filter: { input: "$f_replaced", as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } },
29013
+ non_physical: { $filter: { input: "$f_replaced", as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } }
29014
+ },
29015
+ deleted: {
29016
+ physical: { $filter: { input: "$f_deleted", as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } },
29017
+ non_physical: { $filter: { input: "$f_deleted", as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } }
29018
+ },
28169
29019
  totalCardCount: {
28170
29020
  $add: [
28171
29021
  { $size: { $filter: { input: { $ifNull: ["$f_Available", []] }, as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } } },
@@ -28622,16 +29472,26 @@ function UseAccessManagementRepo() {
28622
29472
  async function cardReplacementRepo(params) {
28623
29473
  const session = import_node_server_utils149.useAtlas.getClient()?.startSession();
28624
29474
  try {
28625
- const { cardId, remarks } = params;
29475
+ const { cardId, remarks, issuedCardId, unitId, userId } = params;
28626
29476
  const id = new import_mongodb83.ObjectId(cardId);
29477
+ const newCardId = new import_mongodb83.ObjectId(issuedCardId);
29478
+ const unit = new import_mongodb83.ObjectId(unitId);
29479
+ const user = new import_mongodb83.ObjectId(userId);
28627
29480
  session?.startTransaction();
28628
- const card = await collection().findOneAndUpdate(
28629
- { _id: id },
28630
- { $set: { remarks, replacementStatus: "Pending", requestDate: /* @__PURE__ */ new Date(), isActivated: false } },
28631
- { returnDocument: "after", session }
28632
- );
29481
+ const sessionResult = await Promise.all([
29482
+ await collection().findOneAndUpdate(
29483
+ { _id: id },
29484
+ { $set: { remarks, replacementStatus: "Complete", requestDate: /* @__PURE__ */ new Date(), isActivated: false } },
29485
+ { returnDocument: "after", session }
29486
+ ),
29487
+ await collection().findOneAndUpdate(
29488
+ { _id: newCardId },
29489
+ { $set: { updatedAt: /* @__PURE__ */ new Date(), assignedUnit: unit, userId: user } },
29490
+ { returnDocument: "after", session }
29491
+ )
29492
+ ]);
28633
29493
  await session?.commitTransaction();
28634
- return card;
29494
+ return sessionResult;
28635
29495
  } catch (error) {
28636
29496
  await session?.abortTransaction();
28637
29497
  throw new Error(error.message);
@@ -28649,7 +29509,8 @@ function UseAccessManagementRepo() {
28649
29509
  const query = {
28650
29510
  site: siteId,
28651
29511
  assignedUnit: { $ne: null },
28652
- type: "NFC" /* NFC */
29512
+ type: "NFC" /* NFC */,
29513
+ isActivated: true
28653
29514
  };
28654
29515
  if (search) {
28655
29516
  query.$or = [{ accessLevel: { $regex: search, $options: "i" } }, { cardNo: { $regex: search, $options: "i" } }];
@@ -28657,7 +29518,7 @@ function UseAccessManagementRepo() {
28657
29518
  if (type) {
28658
29519
  query.userType = type;
28659
29520
  } else {
28660
- query.userType = { $ne: "Resident/Tenant" /* RESIDENT */ };
29521
+ query.userType = { $ne: "Visitor/Resident" /* DEFAULT */ };
28661
29522
  }
28662
29523
  const res = await collection().aggregate([
28663
29524
  {
@@ -28880,7 +29741,8 @@ function UseAccessManagementRepo() {
28880
29741
  rawItems.map(async (item) => {
28881
29742
  const date = new Date(item["startDate (format MM/DD/YYYY)"]);
28882
29743
  const endDate = new Date(date.setFullYear(date.getFullYear() + 10));
28883
- const cardNumber = item["cardNo (number 0-65535 ex. 301)"].toString().padStart(10, "0");
29744
+ const cardNumber = String(Number(item["cardNo (number 0-65535 ex. 301)"] || 0)).padStart(6, "0");
29745
+ const facilityCode = String(Number(item["facilityCode (number 0-255 ex. 11)"] || 0)).padStart(4, "0");
28884
29746
  const pin = item["pin (number 6 digits only)"] ? item["pin (number 6 digits only)"].toString().padStart(6, "0") : "123456";
28885
29747
  const match = item["accessLevel (number ex. 1)"];
28886
29748
  const accessLevel = match ? match : null;
@@ -28894,9 +29756,9 @@ function UseAccessManagementRepo() {
28894
29756
  accessLevel,
28895
29757
  accessGroup,
28896
29758
  accessType: "Normal" /* NORMAL */,
28897
- cardNo: cardNumber,
29759
+ cardNo: `${facilityCode}${cardNumber}`,
28898
29760
  pin,
28899
- qrData: await createQrData({ cardNumber }),
29761
+ qrData: await createQrData({ cardNumber: `${facilityCode}${cardNumber}` }),
28900
29762
  startDate: new Date(item["startDate (format MM/DD/YYYY)"]),
28901
29763
  endDate: new Date(item["endDate (format MM/DD/YYYY)"] || endDate),
28902
29764
  isActivated: true,
@@ -28970,6 +29832,112 @@ function UseAccessManagementRepo() {
28970
29832
  session?.endSession();
28971
29833
  }
28972
29834
  }
29835
+ async function deleteCardRepo(params) {
29836
+ try {
29837
+ const { cardId, remarks } = params;
29838
+ const id = new import_mongodb83.ObjectId(cardId);
29839
+ const result = await collection().findOneAndUpdate({ _id: id }, { $set: { isActivated: false, updatedAt: /* @__PURE__ */ new Date(), remarks, requestDate: /* @__PURE__ */ new Date() } }, { returnDocument: "after" });
29840
+ return result;
29841
+ } catch (error) {
29842
+ throw new Error(error.message);
29843
+ }
29844
+ }
29845
+ async function getCardDetailsRepo(params) {
29846
+ try {
29847
+ const { siteId, cardId } = params;
29848
+ const convertedSiteId = new import_mongodb83.ObjectId(siteId);
29849
+ const convertedCardId = new import_mongodb83.ObjectId(cardId);
29850
+ const card = await collection().findOne(
29851
+ {
29852
+ _id: convertedCardId,
29853
+ site: convertedSiteId,
29854
+ type: "NFC" /* NFC */,
29855
+ userType: "Visitor/Resident" /* DEFAULT */
29856
+ },
29857
+ {
29858
+ projection: {
29859
+ userId: 1,
29860
+ site: 1,
29861
+ type: 1,
29862
+ userType: 1,
29863
+ cardNo: 1,
29864
+ isActivated: 1,
29865
+ replacementStatus: 1,
29866
+ vmsRemarks: 1,
29867
+ remarks: 1,
29868
+ requestDate: 1,
29869
+ createdAt: 1,
29870
+ updatedAt: 1
29871
+ }
29872
+ }
29873
+ );
29874
+ if (!card)
29875
+ return null;
29876
+ const site = await collectionName("sites").findOne(
29877
+ { _id: card.site },
29878
+ { projection: { name: 1, status: 1 } }
29879
+ );
29880
+ const user = card.userId ? await collectionName("users").findOne(
29881
+ { _id: card.userId },
29882
+ { projection: { name: 1, email: 1 } }
29883
+ ) : null;
29884
+ const status = card.userId === null && card.isActivated === true ? "available" : card.userId !== null && card.isActivated === true ? "assigned" : card.isActivated === false && card.replacementStatus !== null ? "replaced" : "deleted";
29885
+ return {
29886
+ ...card,
29887
+ status,
29888
+ site,
29889
+ user
29890
+ };
29891
+ } catch (error) {
29892
+ throw new Error(error.message);
29893
+ }
29894
+ }
29895
+ async function addQrTagRepo(params) {
29896
+ try {
29897
+ const { site, payload } = params;
29898
+ const id = new import_mongodb83.ObjectId(site);
29899
+ const highestCardNo = await collection().aggregate([
29900
+ { $match: { site: id, type: "NFC" /* NFC */, userType: "Visitor/Resident" /* DEFAULT */ } },
29901
+ {
29902
+ $addFields: {
29903
+ qrTagCardNoNumeric: { $toInt: "$qrTagCardNo" }
29904
+ // Convert string to integer
29905
+ }
29906
+ },
29907
+ {
29908
+ $sort: { qrTagCardNoNumeric: -1 }
29909
+ // Sort in descending order
29910
+ },
29911
+ {
29912
+ $limit: 1
29913
+ // Get the highest
29914
+ }
29915
+ ]).toArray();
29916
+ let start = 0;
29917
+ if (highestCardNo.length > 0) {
29918
+ start = highestCardNo[0].qrTagCardNoNumeric || 0;
29919
+ }
29920
+ const nextCardNumbers = Array.from({ length: payload.length }, (_, i) => String(start + i + 1).padStart(5, "0"));
29921
+ const bulkOps = await Promise.all(
29922
+ payload.map(async (doc, index) => {
29923
+ const id2 = new import_mongodb83.ObjectId(doc._id);
29924
+ return {
29925
+ updateOne: {
29926
+ filter: { _id: id2 },
29927
+ update: { $set: { qrTag: doc.qrTag, qrTagCardNo: nextCardNumbers[index] } }
29928
+ }
29929
+ };
29930
+ })
29931
+ );
29932
+ let result;
29933
+ if (bulkOps.length > 0) {
29934
+ result = await collection().bulkWrite(bulkOps);
29935
+ }
29936
+ return result;
29937
+ } catch (error) {
29938
+ throw new Error(error.message);
29939
+ }
29940
+ }
28973
29941
  return {
28974
29942
  createIndexes,
28975
29943
  createIndexForEntrypass,
@@ -28990,7 +29958,10 @@ function UseAccessManagementRepo() {
28990
29958
  getCardReplacementRepo,
28991
29959
  getAccessManagementSettingsRepo,
28992
29960
  bulkPhysicalAccessCardRepo,
28993
- assignAccessCardToUnitRepo
29961
+ assignAccessCardToUnitRepo,
29962
+ deleteCardRepo,
29963
+ getCardDetailsRepo,
29964
+ addQrTagRepo
28994
29965
  };
28995
29966
  }
28996
29967
 
@@ -29022,7 +29993,10 @@ function useAccessManagementSvc() {
29022
29993
  getCardReplacementRepo,
29023
29994
  getAccessManagementSettingsRepo,
29024
29995
  bulkPhysicalAccessCardRepo,
29025
- assignAccessCardToUnitRepo
29996
+ assignAccessCardToUnitRepo,
29997
+ deleteCardRepo,
29998
+ getCardDetailsRepo,
29999
+ addQrTagRepo
29026
30000
  } = UseAccessManagementRepo();
29027
30001
  const addPhysicalCardSvc = async (payload) => {
29028
30002
  try {
@@ -29270,6 +30244,30 @@ function useAccessManagementSvc() {
29270
30244
  throw new Error(err.message);
29271
30245
  }
29272
30246
  };
30247
+ const deleteCardSvc = async (params) => {
30248
+ try {
30249
+ const response = await deleteCardRepo({ ...params });
30250
+ return response;
30251
+ } catch (err) {
30252
+ throw new Error(err.message);
30253
+ }
30254
+ };
30255
+ const getCardDetailsSvc = async (params) => {
30256
+ try {
30257
+ const response = await getCardDetailsRepo({ ...params });
30258
+ return response;
30259
+ } catch (err) {
30260
+ throw new Error(err.message);
30261
+ }
30262
+ };
30263
+ const addQrTagSvc = async (params) => {
30264
+ try {
30265
+ const response = await addQrTagRepo({ ...params });
30266
+ return response;
30267
+ } catch (err) {
30268
+ throw new Error(err.message);
30269
+ }
30270
+ };
29273
30271
  return {
29274
30272
  addPhysicalCardSvc,
29275
30273
  addNonPhysicalCardSvc,
@@ -29294,7 +30292,10 @@ function useAccessManagementSvc() {
29294
30292
  getAccessManagementSettingsSvc,
29295
30293
  convertBufferFile,
29296
30294
  bulkPhysicalAccessCardSvc,
29297
- assignAccessCardToUnitSvc
30295
+ assignAccessCardToUnitSvc,
30296
+ deleteCardSvc,
30297
+ getCardDetailsSvc,
30298
+ addQrTagSvc
29298
30299
  };
29299
30300
  }
29300
30301
 
@@ -29326,7 +30327,10 @@ function useAccessManagementController() {
29326
30327
  getAccessManagementSettingsSvc,
29327
30328
  convertBufferFile,
29328
30329
  bulkPhysicalAccessCardSvc,
29329
- assignAccessCardToUnitSvc
30330
+ assignAccessCardToUnitSvc,
30331
+ deleteCardSvc,
30332
+ getCardDetailsSvc,
30333
+ addQrTagSvc
29330
30334
  } = useAccessManagementSvc();
29331
30335
  const addPhysicalCard = async (req, res) => {
29332
30336
  try {
@@ -29555,11 +30559,24 @@ function useAccessManagementController() {
29555
30559
  userType: import_joi85.default.string().optional().allow("", null),
29556
30560
  type: import_joi85.default.string().optional().allow("", null)
29557
30561
  });
30562
+ const user = req.cookies?.sid;
29558
30563
  const { error } = schema2.validate({ site, userType, type });
29559
30564
  if (error) {
29560
30565
  return res.status(400).json({ message: error.message });
29561
30566
  }
30567
+ const key = `${namespace2}:${user}:available-access-cards`;
30568
+ const listKey = `${namespace2}:${user}:list`;
30569
+ const { redis } = (0, import_node_server_utils151.useCache)(key);
30570
+ const cachedData = await getCache({ key, redis });
30571
+ if (cachedData) {
30572
+ console.log("\u26A1 Cache hit:", key);
30573
+ redis.expire(key, 60).catch(console.error);
30574
+ redis.lrem(listKey, 0, key).then(() => redis.lpush(listKey, key)).then(() => redis.ltrim(listKey, 0, 9)).catch(console.error);
30575
+ return res.status(200).json({ message: "Success", data: cachedData });
30576
+ }
29562
30577
  const result = await availableAccessCardsSvc({ site, userType, type });
30578
+ await setCache({ key, data: result, ttlSeconds: 60, redis });
30579
+ redis.lrem(listKey, 0, key).then(() => redis.lpush(listKey, key)).then(() => redis.ltrim(listKey, 0, 9)).catch(console.error);
29563
30580
  return res.status(200).json({ message: "Success", data: result });
29564
30581
  } catch (error) {
29565
30582
  return res.status(400).json({
@@ -29741,16 +30758,19 @@ function useAccessManagementController() {
29741
30758
  };
29742
30759
  const cardReplacement = async (req, res) => {
29743
30760
  try {
29744
- const { cardId, remarks } = req.body;
30761
+ const { cardId, remarks, unitId, issuedCardId, userId } = req.body;
29745
30762
  const schema2 = import_joi85.default.object({
29746
30763
  cardId: import_joi85.default.string().required(),
29747
- remarks: import_joi85.default.string().optional().allow("", null)
30764
+ remarks: import_joi85.default.string().optional().allow("", null),
30765
+ unitId: import_joi85.default.string().hex().required(),
30766
+ issuedCardId: import_joi85.default.string().required(),
30767
+ userId: import_joi85.default.string().hex().required()
29748
30768
  });
29749
- const { error } = schema2.validate({ cardId, remarks });
30769
+ const { error } = schema2.validate({ cardId, remarks, unitId, issuedCardId, userId });
29750
30770
  if (error) {
29751
30771
  return res.status(400).json({ message: error.message });
29752
30772
  }
29753
- const result = await cardReplacementSvc({ cardId, remarks });
30773
+ const result = await cardReplacementSvc({ cardId, remarks, unitId, issuedCardId, userId });
29754
30774
  return res.status(200).json({ message: "Success", data: result });
29755
30775
  } catch (error) {
29756
30776
  return res.status(500).json({
@@ -29881,6 +30901,74 @@ function useAccessManagementController() {
29881
30901
  });
29882
30902
  }
29883
30903
  };
30904
+ const deleteCard = async (req, res) => {
30905
+ try {
30906
+ const { cardId, remarks } = req.body;
30907
+ const schema2 = import_joi85.default.object({
30908
+ cardId: import_joi85.default.string().hex().required(),
30909
+ remarks: import_joi85.default.string().optional().allow("", null)
30910
+ });
30911
+ const { error } = schema2.validate({ cardId, remarks });
30912
+ if (error) {
30913
+ return res.status(400).json({ message: error.message });
30914
+ }
30915
+ const result = await deleteCardSvc({ cardId, remarks });
30916
+ return res.status(200).json({ message: "Success", data: result });
30917
+ } catch (error) {
30918
+ return res.status(500).json({
30919
+ data: null,
30920
+ message: error.message
30921
+ });
30922
+ }
30923
+ };
30924
+ const getCardDetails = async (req, res) => {
30925
+ try {
30926
+ const { siteId, cardId } = req.query;
30927
+ const schema2 = import_joi85.default.object({
30928
+ siteId: import_joi85.default.string().hex().required(),
30929
+ cardId: import_joi85.default.string().hex().required()
30930
+ });
30931
+ const { error } = schema2.validate({ siteId, cardId });
30932
+ if (error) {
30933
+ return res.status(400).json({ message: error.message });
30934
+ }
30935
+ const result = await getCardDetailsSvc({ siteId, cardId });
30936
+ return res.status(200).json({ message: "Success", data: result });
30937
+ } catch (error) {
30938
+ return res.status(500).json({
30939
+ data: null,
30940
+ message: error.message
30941
+ });
30942
+ }
30943
+ };
30944
+ const addQrTag = async (req, res) => {
30945
+ try {
30946
+ const payload = req.body;
30947
+ const site = req.params.site;
30948
+ const schema2 = import_joi85.default.object({
30949
+ site: import_joi85.default.string().hex().required(),
30950
+ payload: import_joi85.default.array().items(
30951
+ import_joi85.default.object({
30952
+ _id: import_joi85.default.string().hex().required(),
30953
+ cardNo: import_joi85.default.string().required(),
30954
+ qrTag: import_joi85.default.string().required(),
30955
+ qrTagCardNo: import_joi85.default.string().required()
30956
+ })
30957
+ ).required()
30958
+ });
30959
+ const { error } = schema2.validate({ site, payload });
30960
+ if (error) {
30961
+ return res.status(400).json({ message: error.message });
30962
+ }
30963
+ const result = await addQrTagSvc({ payload, site });
30964
+ return res.status(200).json({ message: "Success", data: result });
30965
+ } catch (error) {
30966
+ return res.status(500).json({
30967
+ data: null,
30968
+ message: error.message
30969
+ });
30970
+ }
30971
+ };
29884
30972
  return {
29885
30973
  addPhysicalCard,
29886
30974
  addNonPhysicalCard,
@@ -29902,7 +30990,10 @@ function useAccessManagementController() {
29902
30990
  getCardReplacement,
29903
30991
  getAccessManagementSettings,
29904
30992
  bulkPhysicalAccessCard,
29905
- assignAccessCardToUnit
30993
+ assignAccessCardToUnit,
30994
+ deleteCard,
30995
+ getCardDetails,
30996
+ addQrTag
29906
30997
  };
29907
30998
  }
29908
30999
 
@@ -31619,9 +32710,9 @@ function useStatementOfAccountRepo() {
31619
32710
  page = page > 0 ? page - 1 : 0;
31620
32711
  let dateExpr = {};
31621
32712
  if (dateFrom && dateTo) {
31622
- const startDate = new Date(dateFrom);
32713
+ let startDate = new Date(dateFrom);
31623
32714
  startDate.setHours(0, 0, 0, 0);
31624
- const endDate = new Date(dateTo);
32715
+ let endDate = new Date(dateTo);
31625
32716
  endDate.setHours(23, 59, 59, 999);
31626
32717
  dateExpr = {
31627
32718
  $expr: {
@@ -31632,13 +32723,15 @@ function useStatementOfAccountRepo() {
31632
32723
  }
31633
32724
  };
31634
32725
  }
32726
+ const unitSearchRegex = search ? search.trim().replace(/\s+/g, "").replace(/\//g, "\\s*/\\s*") : null;
31635
32727
  const query = {
31636
32728
  ...status && status !== "all" && { status },
31637
32729
  ...search && {
31638
32730
  $or: [
31639
32731
  { unitOwner: { $regex: search, $options: "i" } },
31640
- { unit: { $regex: search, $options: "i" } },
31641
- { email: { $regex: search, $options: "i" } }
32732
+ { unit: { $regex: unitSearchRegex, $options: "i" } },
32733
+ { email: { $regex: search, $options: "i" } },
32734
+ { category: { $regex: search, $options: "i" } }
31642
32735
  ]
31643
32736
  },
31644
32737
  ...import_mongodb91.ObjectId.isValid(site) && { site: new import_mongodb91.ObjectId(site) },
@@ -33883,6 +34976,9 @@ function useIncidentReportRepo() {
33883
34976
  $regex: search,
33884
34977
  $options: "i"
33885
34978
  }
34979
+ },
34980
+ {
34981
+ approvedByName: { $regex: search, $options: "i" }
33886
34982
  }
33887
34983
  ]
33888
34984
  },
@@ -36396,7 +37492,9 @@ function useNfcPatrolLogController() {
36396
37492
  }
36397
37493
  // Annotate the CommonJS export names for ESM import in node:
36398
37494
  0 && (module.exports = {
37495
+ ANPRMode,
36399
37496
  AccessTypeProps,
37497
+ BULLETIN_RECIPIENTS,
36400
37498
  DEVICE_STATUS,
36401
37499
  EAccessCardTypes,
36402
37500
  EAccessCardUserTypes,
@@ -36455,12 +37553,15 @@ function useNfcPatrolLogController() {
36455
37553
  MVerification,
36456
37554
  MVisitorTransaction,
36457
37555
  MWorkOrder,
37556
+ OrgNature,
36458
37557
  PERSON_TYPES,
37558
+ STATUS_VALUES,
36459
37559
  UseAccessManagementRepo,
36460
- allowedCategories,
37560
+ VehicleCategory,
37561
+ VehicleStatus,
37562
+ VehicleType,
36461
37563
  allowedFieldsSite,
36462
37564
  allowedNatures,
36463
- allowedTypes,
36464
37565
  attendanceSchema,
36465
37566
  attendanceSettingsSchema,
36466
37567
  chatSchema,