@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.mjs CHANGED
@@ -1388,7 +1388,9 @@ var schemaOccurrenceEntry = Joi5.object({
1388
1388
  subject: Joi5.string().hex().optional().allow(null, ""),
1389
1389
  occurrence: Joi5.string().optional().allow(null, ""),
1390
1390
  signature: Joi5.string().hex().optional().allow(null, ""),
1391
- incidentReportId: Joi5.string().hex().optional().allow(null, "")
1391
+ incidentReportId: Joi5.string().hex().optional().allow(null, ""),
1392
+ eSignature: Joi5.string().required(),
1393
+ createdByName: Joi5.string().optional().allow(null, "")
1392
1394
  });
1393
1395
  var schemaUpdateOccurrenceEntry = Joi5.object({
1394
1396
  _id: Joi5.string().hex().required(),
@@ -1397,7 +1399,9 @@ var schemaUpdateOccurrenceEntry = Joi5.object({
1397
1399
  subject: Joi5.string().hex().optional().allow(null, ""),
1398
1400
  occurrence: Joi5.string().optional().allow(null, ""),
1399
1401
  signature: Joi5.string().hex().optional().allow(null, ""),
1400
- incidentReportId: Joi5.string().hex().optional().allow(null, "")
1402
+ incidentReportId: Joi5.string().hex().optional().allow(null, ""),
1403
+ eSignature: Joi5.string().required(),
1404
+ createdByName: Joi5.string().optional().allow(null, "")
1401
1405
  });
1402
1406
  function MOccurrenceEntry(value) {
1403
1407
  if (value._id && typeof value._id === "string") {
@@ -1454,6 +1458,8 @@ function MOccurrenceEntry(value) {
1454
1458
  incidentReportId: value.incidentReportId,
1455
1459
  signature: value.signature,
1456
1460
  userName: value.userName,
1461
+ eSignature: value.eSignature,
1462
+ createdByName: value.createdByName,
1457
1463
  date: value.date,
1458
1464
  createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1459
1465
  updatedAt: value.updatedAt,
@@ -6424,12 +6430,12 @@ function useUserController() {
6424
6430
  }
6425
6431
  }
6426
6432
  async function createUserByVerification(req, res, next) {
6427
- const allowedTypes2 = ["user-sign-up", "user-invite"];
6433
+ const allowedTypes = ["user-sign-up", "user-invite"];
6428
6434
  const validation = Joi14.object({
6429
6435
  id: Joi14.string().hex().required(),
6430
6436
  name: Joi14.string().required(),
6431
6437
  password: Joi14.string().required(),
6432
- type: Joi14.string().required().valid(...allowedTypes2)
6438
+ type: Joi14.string().required().valid(...allowedTypes)
6433
6439
  });
6434
6440
  const id = req.params.id;
6435
6441
  const payload = { ...req.body };
@@ -10900,7 +10906,7 @@ function useFeedbackController() {
10900
10906
  }
10901
10907
  async function deleteFeedback(req, res, next) {
10902
10908
  const validation = Joi28.string().hex().required();
10903
- const _id = req.query.id;
10909
+ const _id = req.params.id;
10904
10910
  const { error } = validation.validate(_id);
10905
10911
  if (error) {
10906
10912
  logger39.log({ level: "error", message: error.message });
@@ -11264,8 +11270,7 @@ function useWorkOrderController() {
11264
11270
  }
11265
11271
  async function deleteWorkOrder(req, res, next) {
11266
11272
  const validation = Joi29.string().hex().required();
11267
- const _id = req.query.id;
11268
- console.log(_id);
11273
+ const _id = req.params.id;
11269
11274
  const { error } = validation.validate(_id);
11270
11275
  if (error) {
11271
11276
  logger40.log({ level: "error", message: error.message });
@@ -15065,11 +15070,14 @@ function usePersonRepo() {
15065
15070
  const namespace_collection = "site.people";
15066
15071
  const collection = db.collection(namespace_collection);
15067
15072
  const { delNamespace, getCache, setCache } = useCache27(namespace_collection);
15068
- async function createIndex() {
15073
+ async function createIndexes() {
15069
15074
  try {
15070
- await collection.createIndex([{ nric: 1 }, { contact: 1 }]);
15075
+ await collection.createIndexes([
15076
+ { key: { contact: 1 } },
15077
+ { key: { nric: 1 } }
15078
+ ]);
15071
15079
  } catch (error) {
15072
- throw new InternalServerError27("Failed to create index.");
15080
+ throw new InternalServerError27("Failed to create index on site people.");
15073
15081
  }
15074
15082
  }
15075
15083
  async function createTextIndex() {
@@ -15459,6 +15467,47 @@ function usePersonRepo() {
15459
15467
  throw new InternalServerError27("Failed to fetch people by plate number.");
15460
15468
  }
15461
15469
  }
15470
+ async function getPeopleByNRIC({
15471
+ page = 1,
15472
+ limit = 10,
15473
+ nric = "",
15474
+ sort = {},
15475
+ site = ""
15476
+ }) {
15477
+ page = page > 0 ? page - 1 : 0;
15478
+ const normalizedNric = nric.trim();
15479
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
15480
+ const query = {
15481
+ ...nric && { nric: normalizedNric },
15482
+ site: typeof site === "string" ? new ObjectId44(site) : site
15483
+ };
15484
+ const cacheOptions = {
15485
+ site,
15486
+ ...nric && { nric: normalizedNric },
15487
+ page: String(page),
15488
+ limit: String(limit),
15489
+ sort: JSON.stringify(sort)
15490
+ };
15491
+ try {
15492
+ const cacheKey = makeCacheKey25(namespace_collection, cacheOptions);
15493
+ const cachedData = await getCache(cacheKey);
15494
+ if (cachedData) {
15495
+ logger60.info(`Cache hit for key: ${cacheKey}`);
15496
+ return cachedData;
15497
+ }
15498
+ const items = await collection.find(query).sort(sort).skip(page * limit).limit(limit).toArray();
15499
+ const length = await collection.countDocuments(query);
15500
+ const data = paginate20(items, page, limit, length);
15501
+ setCache(cacheKey, data, 15 * 60).then(() => {
15502
+ logger60.info(`Cache set for key: ${cacheKey}`);
15503
+ }).catch((err) => {
15504
+ logger60.error(`Failed to set cache for key: ${cacheKey}`, err);
15505
+ });
15506
+ return data;
15507
+ } catch (error) {
15508
+ throw new InternalServerError27("Failed to retrieve person by NRIC.");
15509
+ }
15510
+ }
15462
15511
  return {
15463
15512
  add,
15464
15513
  getAll,
@@ -15468,11 +15517,12 @@ function usePersonRepo() {
15468
15517
  createTextIndex,
15469
15518
  getPersonByPlateNumber,
15470
15519
  getByNRIC,
15471
- createIndex,
15520
+ createIndexes,
15472
15521
  getPersonByPhoneNumber,
15473
15522
  getPeopleByUnit,
15474
15523
  getCompany,
15475
- getPeopleByPlateNumber
15524
+ getPeopleByPlateNumber,
15525
+ getPeopleByNRIC
15476
15526
  };
15477
15527
  }
15478
15528
 
@@ -15493,12 +15543,45 @@ import {
15493
15543
  import { BadRequestError as BadRequestError77, logger as logger61 } from "@7365admin1/node-server-utils";
15494
15544
  import Joi43 from "joi";
15495
15545
  import { ObjectId as ObjectId45 } from "mongodb";
15496
- var allowedTypes = ["whitelist", "blacklist"];
15497
- var allowedCategories = ["resident", "visitor"];
15546
+ var VehicleType = /* @__PURE__ */ ((VehicleType2) => {
15547
+ VehicleType2["WHITELIST"] = "whitelist";
15548
+ VehicleType2["BLOCKLIST"] = "blocklist";
15549
+ return VehicleType2;
15550
+ })(VehicleType || {});
15551
+ var VehicleCategory = /* @__PURE__ */ ((VehicleCategory2) => {
15552
+ VehicleCategory2["RESIDENT"] = "resident";
15553
+ VehicleCategory2["VISITOR"] = "visitor";
15554
+ return VehicleCategory2;
15555
+ })(VehicleCategory || {});
15556
+ var VehicleStatus = /* @__PURE__ */ ((VehicleStatus2) => {
15557
+ VehicleStatus2["PENDING"] = "pending";
15558
+ VehicleStatus2["ACTIVE"] = "active";
15559
+ VehicleStatus2["INACTIVE"] = "inactive";
15560
+ VehicleStatus2["DELETED"] = "deleted";
15561
+ return VehicleStatus2;
15562
+ })(VehicleStatus || {});
15563
+ var OrgNature = /* @__PURE__ */ ((OrgNature2) => {
15564
+ OrgNature2["PROPERTY_MANAGEMENT_AGENCY"] = "property_management_agency";
15565
+ OrgNature2["SECURITY_AGENCY"] = "security_agency";
15566
+ return OrgNature2;
15567
+ })(OrgNature || {});
15568
+ var ANPRMode = /* @__PURE__ */ ((ANPRMode2) => {
15569
+ ANPRMode2["TRAFFIC_REDLIST"] = "TrafficRedList";
15570
+ ANPRMode2["TRAFFIC_BLACKLIST"] = "TrafficBlackList";
15571
+ return ANPRMode2;
15572
+ })(ANPRMode || {});
15498
15573
  var vehicleSchema = Joi43.object({
15499
- plateNumber: Joi43.string().required(),
15500
- type: Joi43.string().required().allow(...allowedTypes),
15501
- category: Joi43.string().required().allow(...allowedCategories),
15574
+ plateNumber: Joi43.alternatives().try(
15575
+ Joi43.array().items(Joi43.string().trim().min(1)).min(1),
15576
+ Joi43.string().trim().min(1)
15577
+ ).custom((value) => {
15578
+ if (typeof value === "string") {
15579
+ return value.split(",").map((p) => p.trim()).filter(Boolean);
15580
+ }
15581
+ return value;
15582
+ }).required(),
15583
+ type: Joi43.string().required().valid(...Object.values(VehicleType)),
15584
+ category: Joi43.string().required().valid(...Object.values(VehicleCategory)),
15502
15585
  name: Joi43.string().required(),
15503
15586
  phoneNumber: Joi43.string().optional().allow("", null),
15504
15587
  recNo: Joi43.string().optional().allow("", null),
@@ -15512,7 +15595,8 @@ var vehicleSchema = Joi43.object({
15512
15595
  seasonPassType: Joi43.string().optional().allow("", null),
15513
15596
  start: Joi43.date().optional().allow("", null),
15514
15597
  end: Joi43.date().optional().allow("", null),
15515
- unitName: Joi43.string().optional().allow("", null)
15598
+ unitName: Joi43.string().optional().allow("", null),
15599
+ status: Joi43.string().optional().valid(...Object.values(VehicleStatus)).allow("")
15516
15600
  });
15517
15601
  function MVehicle(value) {
15518
15602
  const { error } = vehicleSchema.validate(value);
@@ -15541,6 +15625,11 @@ function MVehicle(value) {
15541
15625
  throw new BadRequestError77("Invalid building unit ID format.");
15542
15626
  }
15543
15627
  }
15628
+ const createdAtDate = value.createdAt ? new Date(value.createdAt) : /* @__PURE__ */ new Date();
15629
+ const expiredDate = new Date(createdAtDate);
15630
+ expiredDate.setFullYear(expiredDate.getFullYear() + 10);
15631
+ const createdAt = createdAtDate.toISOString();
15632
+ const expiredAt = value.end ?? expiredDate.toISOString();
15544
15633
  return {
15545
15634
  plateNumber: value.plateNumber ?? "",
15546
15635
  type: value.type ?? "",
@@ -15556,11 +15645,11 @@ function MVehicle(value) {
15556
15645
  nric: value.nric ?? "",
15557
15646
  remarks: value.remarks ?? "",
15558
15647
  seasonPassType: value.seasonPassType ?? "",
15559
- start: value.start ?? "",
15560
- end: value.end ?? "",
15561
- status: value.status ?? "active",
15648
+ start: value.start ?? createdAt,
15649
+ end: value.end ?? expiredAt,
15650
+ status: value.status ?? "active" /* ACTIVE */,
15562
15651
  unitName: value.unitName ?? "",
15563
- createdAt: value.createdAt ?? /* @__PURE__ */ new Date(),
15652
+ createdAt,
15564
15653
  updatedAt: value.updatedAt ?? "",
15565
15654
  deletedAt: value.deletedAt ?? ""
15566
15655
  };
@@ -15618,7 +15707,7 @@ function useVehicleRepo() {
15618
15707
  }
15619
15708
  const namespace_collection = "vehicles";
15620
15709
  const collection = db.collection(namespace_collection);
15621
- const { delNamespace, setCache, getCache, delCache } = useCache28(namespace_collection);
15710
+ const { delNamespace, setCache, getCache } = useCache28(namespace_collection);
15622
15711
  async function createIndex() {
15623
15712
  try {
15624
15713
  await collection.createIndexes([
@@ -15630,6 +15719,22 @@ function useVehicleRepo() {
15630
15719
  throw new InternalServerError28("Failed to create index on vehicle.");
15631
15720
  }
15632
15721
  }
15722
+ async function createTextIndex() {
15723
+ try {
15724
+ await collection.createIndex({
15725
+ name: "text",
15726
+ plateNumber: "text",
15727
+ level: "text",
15728
+ unitName: "text",
15729
+ phoneNumber: "text",
15730
+ nric: "text"
15731
+ });
15732
+ } catch (error) {
15733
+ throw new InternalServerError28(
15734
+ "Failed to create text index on visitor transaction."
15735
+ );
15736
+ }
15737
+ }
15633
15738
  async function add(value, session) {
15634
15739
  try {
15635
15740
  value = MVehicle(value);
@@ -15661,73 +15766,178 @@ function useVehicleRepo() {
15661
15766
  search = "",
15662
15767
  sort = {},
15663
15768
  type = "",
15664
- category = ""
15769
+ category = "",
15770
+ status = ""
15665
15771
  }) {
15666
15772
  page = page > 0 ? page - 1 : 0;
15667
- const query = { status: "active" };
15773
+ const baseQuery = {
15774
+ ...status && { status },
15775
+ ...type && { type },
15776
+ ...category && { category }
15777
+ };
15778
+ let query = { ...baseQuery };
15779
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
15668
15780
  const cacheOptions = {
15669
- status: "active",
15781
+ ...status && { status },
15782
+ ...type && { type },
15783
+ ...category && { category },
15670
15784
  page: String(page),
15671
- limit: String(limit)
15785
+ limit: String(limit),
15786
+ sort: JSON.stringify(sort)
15672
15787
  };
15673
- sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
15674
- cacheOptions.sort = JSON.stringify(sort);
15675
15788
  if (search) {
15676
- query.$or = [{ name: { $regex: search, $options: "i" } }];
15789
+ query.$text = { $search: search };
15677
15790
  cacheOptions.search = search;
15678
15791
  }
15679
- if (type) {
15680
- query.type = type;
15681
- cacheOptions.type = type;
15682
- }
15683
- if (category) {
15684
- query.category = category;
15685
- cacheOptions.category = category;
15686
- }
15687
15792
  const cacheKey = makeCacheKey26(namespace_collection, cacheOptions);
15688
15793
  const cachedData = await getCache(cacheKey);
15689
15794
  if (cachedData) {
15690
15795
  logger62.info(`Cache hit for key: ${cacheKey}`);
15691
15796
  return cachedData;
15692
15797
  }
15693
- try {
15694
- const items = await collection.aggregate([
15695
- { $match: query },
15696
- { $sort: sort },
15697
- { $skip: page * limit },
15698
- { $limit: limit },
15699
- {
15700
- $lookup: {
15701
- from: "building-units",
15702
- localField: "unit",
15703
- foreignField: "_id",
15704
- as: "units",
15705
- pipeline: [{ $project: { name: 1 } }]
15798
+ const escapeRegex = (input) => input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15799
+ const buildGroupedPipeline = (matchQuery) => [
15800
+ { $match: matchQuery },
15801
+ {
15802
+ $lookup: {
15803
+ from: "building-units",
15804
+ localField: "unit",
15805
+ foreignField: "_id",
15806
+ as: "units",
15807
+ pipeline: [{ $project: { name: 1 } }]
15808
+ }
15809
+ },
15810
+ { $unwind: { path: "$units", preserveNullAndEmptyArrays: true } },
15811
+ {
15812
+ $project: {
15813
+ _id: 1,
15814
+ name: 1,
15815
+ type: 1,
15816
+ category: 1,
15817
+ status: 1,
15818
+ phoneNumber: 1,
15819
+ block: 1,
15820
+ level: 1,
15821
+ unit: "$units.name",
15822
+ recNo: 1,
15823
+ nric: 1,
15824
+ plateNumber: 1
15825
+ }
15826
+ },
15827
+ {
15828
+ $group: {
15829
+ _id: {
15830
+ $cond: [
15831
+ {
15832
+ $and: [{ $ne: ["$nric", null] }, { $ne: ["$nric", ""] }]
15833
+ },
15834
+ {
15835
+ nric: "$nric",
15836
+ block: "$block",
15837
+ level: "$level",
15838
+ unit: "$unit"
15839
+ },
15840
+ "$_id"
15841
+ ]
15842
+ },
15843
+ vehicleId: { $first: "$_id" },
15844
+ name: { $first: "$name" },
15845
+ category: { $first: "$category" },
15846
+ phoneNumber: { $first: "$phoneNumber" },
15847
+ block: { $first: "$block" },
15848
+ level: { $first: "$level" },
15849
+ unit: { $first: "$unit" },
15850
+ nric: { $first: "$nric" },
15851
+ plates: {
15852
+ $addToSet: {
15853
+ plateNumber: "$plateNumber",
15854
+ recNo: "$recNo",
15855
+ status: "$status",
15856
+ type: "$type"
15857
+ }
15706
15858
  }
15707
- },
15708
- { $unwind: { path: "$units", preserveNullAndEmptyArrays: true } },
15709
- {
15710
- $project: {
15711
- name: 1,
15712
- type: 1,
15713
- category: 1,
15714
- status: 1,
15715
- phoneNumber: 1,
15716
- block: 1,
15717
- level: 1,
15718
- unit: "$units.name",
15719
- plateNumber: 1,
15720
- recNo: 1
15859
+ }
15860
+ },
15861
+ {
15862
+ $project: {
15863
+ _id: "$vehicleId",
15864
+ name: 1,
15865
+ type: 1,
15866
+ category: 1,
15867
+ status: 1,
15868
+ phoneNumber: 1,
15869
+ block: 1,
15870
+ level: 1,
15871
+ unit: 1,
15872
+ nric: 1,
15873
+ plates: {
15874
+ $filter: {
15875
+ input: "$plates",
15876
+ as: "plate",
15877
+ cond: {
15878
+ $and: [
15879
+ { $ne: ["$$plate.plateNumber", null] },
15880
+ { $ne: ["$$plate.plateNumber", ""] }
15881
+ ]
15882
+ }
15883
+ }
15721
15884
  }
15722
15885
  }
15723
- ]).toArray();
15724
- const length = await collection.countDocuments(query);
15886
+ },
15887
+ { $sort: sort },
15888
+ { $skip: page * limit },
15889
+ { $limit: limit }
15890
+ ];
15891
+ const buildGroupedCountPipeline = (matchQuery) => [
15892
+ { $match: matchQuery },
15893
+ {
15894
+ $group: {
15895
+ _id: {
15896
+ $cond: [
15897
+ {
15898
+ $and: [{ $ne: ["$nric", null] }, { $ne: ["$nric", ""] }]
15899
+ },
15900
+ {
15901
+ nric: "$nric",
15902
+ block: "$block",
15903
+ level: "$level",
15904
+ unit: "$unit"
15905
+ },
15906
+ "$_id"
15907
+ ]
15908
+ }
15909
+ }
15910
+ },
15911
+ { $count: "total" }
15912
+ ];
15913
+ try {
15914
+ let items = [];
15915
+ let length = 0;
15916
+ items = await collection.aggregate(buildGroupedPipeline(query)).toArray();
15917
+ const countResult = await collection.aggregate(buildGroupedCountPipeline(query)).toArray();
15918
+ length = countResult[0]?.total || 0;
15919
+ if ((!items || items.length === 0) && search) {
15920
+ const escaped = escapeRegex(search);
15921
+ const regexQuery = {
15922
+ ...baseQuery,
15923
+ $or: [
15924
+ { name: { $regex: escaped, $options: "i" } },
15925
+ { plateNumber: { $regex: escaped, $options: "i" } },
15926
+ { company: { $regex: escaped, $options: "i" } },
15927
+ { level: { $regex: escaped, $options: "i" } },
15928
+ { unitName: { $regex: escaped, $options: "i" } },
15929
+ { contact: { $regex: escaped, $options: "i" } },
15930
+ { nric: { $regex: escaped, $options: "i" } }
15931
+ ]
15932
+ };
15933
+ items = await collection.aggregate(buildGroupedPipeline(regexQuery)).toArray();
15934
+ const regexCountResult = await collection.aggregate(buildGroupedCountPipeline(regexQuery)).toArray();
15935
+ length = regexCountResult[0]?.total || 0;
15936
+ }
15725
15937
  const data = paginate21(items, page, limit, length);
15726
- setCache(cacheKey, data, 15 * 60).then(() => {
15727
- logger62.info(`Cache set for key: ${cacheKey}`);
15728
- }).catch((err) => {
15729
- logger62.error(`Failed to set cache for key: ${cacheKey}`, err);
15730
- });
15938
+ setCache(cacheKey, data, 15 * 60).then(() => logger62.info(`Cache set for key: ${cacheKey}`)).catch(
15939
+ (err) => logger62.error(`Failed to set cache for key: ${cacheKey}`, err)
15940
+ );
15731
15941
  return data;
15732
15942
  } catch (error) {
15733
15943
  throw error;
@@ -15800,40 +16010,176 @@ function useVehicleRepo() {
15800
16010
  }
15801
16011
  const data = await collection.aggregate([
15802
16012
  { $match: { _id } },
16013
+ { $limit: 1 },
16014
+ {
16015
+ $project: {
16016
+ _id: 1,
16017
+ nric: 1,
16018
+ block: 1,
16019
+ level: 1,
16020
+ unit: 1
16021
+ }
16022
+ },
15803
16023
  {
15804
16024
  $lookup: {
15805
- from: "building-units",
15806
- localField: "unit",
15807
- foreignField: "_id",
15808
- as: "units",
15809
- pipeline: [{ $project: { name: 1 } }]
16025
+ from: collection.collectionName,
16026
+ let: {
16027
+ vehicleId: "$_id",
16028
+ vehicleNric: "$nric",
16029
+ vehicleBlock: "$block",
16030
+ vehicleLevel: "$level",
16031
+ vehicleUnit: "$unit"
16032
+ },
16033
+ pipeline: [
16034
+ {
16035
+ $match: {
16036
+ $expr: {
16037
+ $cond: [
16038
+ {
16039
+ $and: [
16040
+ { $ne: ["$$vehicleNric", null] },
16041
+ { $ne: ["$$vehicleNric", ""] }
16042
+ ]
16043
+ },
16044
+ {
16045
+ $and: [
16046
+ { $eq: ["$nric", "$$vehicleNric"] },
16047
+ { $eq: ["$block", "$$vehicleBlock"] },
16048
+ { $eq: ["$level", "$$vehicleLevel"] },
16049
+ { $eq: ["$unit", "$$vehicleUnit"] }
16050
+ ]
16051
+ },
16052
+ { $eq: ["$_id", "$$vehicleId"] }
16053
+ ]
16054
+ }
16055
+ }
16056
+ },
16057
+ {
16058
+ $lookup: {
16059
+ from: "building-units",
16060
+ localField: "unit",
16061
+ foreignField: "_id",
16062
+ as: "units",
16063
+ pipeline: [{ $project: { name: 1 } }]
16064
+ }
16065
+ },
16066
+ {
16067
+ $unwind: { path: "$units", preserveNullAndEmptyArrays: true }
16068
+ },
16069
+ {
16070
+ $project: {
16071
+ _id: 1,
16072
+ name: 1,
16073
+ phoneNumber: 1,
16074
+ plateNumber: 1,
16075
+ nric: 1,
16076
+ block: 1,
16077
+ level: 1,
16078
+ rawUnit: "$unit",
16079
+ unit: "$units.name",
16080
+ type: 1,
16081
+ category: 1,
16082
+ status: 1,
16083
+ recNo: 1,
16084
+ start: 1,
16085
+ end: 1,
16086
+ seasonPassType: 1
16087
+ }
16088
+ },
16089
+ {
16090
+ $group: {
16091
+ _id: {
16092
+ $cond: [
16093
+ {
16094
+ $and: [
16095
+ { $ne: ["$nric", null] },
16096
+ { $ne: ["$nric", ""] }
16097
+ ]
16098
+ },
16099
+ {
16100
+ nric: "$nric",
16101
+ block: "$block",
16102
+ level: "$level",
16103
+ unit: "$unit"
16104
+ },
16105
+ "$_id"
16106
+ ]
16107
+ },
16108
+ vehicleId: { $first: "$_id" },
16109
+ name: { $first: "$name" },
16110
+ category: { $first: "$category" },
16111
+ phoneNumber: { $first: "$phoneNumber" },
16112
+ block: { $first: "$block" },
16113
+ level: { $first: "$level" },
16114
+ unit: { $first: "$unit" },
16115
+ nric: { $first: "$nric" },
16116
+ start: { $first: "$start" },
16117
+ end: { $first: "$end" },
16118
+ seasonPassType: { $first: "$seasonPassType" },
16119
+ plates: {
16120
+ $addToSet: {
16121
+ plateNumber: "$plateNumber",
16122
+ recNo: "$recNo",
16123
+ status: "$status",
16124
+ type: "$type"
16125
+ }
16126
+ }
16127
+ }
16128
+ },
16129
+ {
16130
+ $project: {
16131
+ _id: "$vehicleId",
16132
+ name: 1,
16133
+ phoneNumber: 1,
16134
+ nric: 1,
16135
+ block: 1,
16136
+ level: 1,
16137
+ unit: 1,
16138
+ type: 1,
16139
+ category: 1,
16140
+ status: 1,
16141
+ start: 1,
16142
+ end: 1,
16143
+ seasonPassType: 1,
16144
+ plates: {
16145
+ $filter: {
16146
+ input: "$plates",
16147
+ as: "plate",
16148
+ cond: {
16149
+ $and: [
16150
+ { $ne: ["$$plate.plateNumber", null] },
16151
+ { $ne: ["$$plate.plateNumber", ""] }
16152
+ ]
16153
+ }
16154
+ }
16155
+ }
16156
+ }
16157
+ }
16158
+ ],
16159
+ as: "vehicle"
15810
16160
  }
15811
16161
  },
15812
- { $unwind: { path: "$units", preserveNullAndEmptyArrays: true } },
15813
16162
  {
15814
16163
  $project: {
15815
- name: 1,
15816
- phoneNumber: 1,
15817
- plateNumber: 1,
15818
- block: 1,
15819
- level: 1,
15820
- unit: "$units.name",
15821
- type: 1,
15822
- category: 1,
15823
- status: 1,
15824
- recNo: 1
16164
+ vehicle: { $arrayElemAt: ["$vehicle", 0] }
16165
+ }
16166
+ },
16167
+ {
16168
+ $replaceRoot: {
16169
+ newRoot: "$vehicle"
15825
16170
  }
15826
16171
  }
15827
16172
  ]).toArray();
15828
16173
  if (!data || !data.length) {
15829
16174
  throw new NotFoundError19("Vehicle not found.");
15830
16175
  }
15831
- setCache(cacheKey, data[0], 15 * 60).then(() => {
16176
+ const result = data[0];
16177
+ setCache(cacheKey, result, 15 * 60).then(() => {
15832
16178
  logger62.info(`Cache set for key: ${cacheKey}`);
15833
16179
  }).catch((err) => {
15834
16180
  logger62.error(`Failed to set cache for key: ${cacheKey}`, err);
15835
16181
  });
15836
- return data[0];
16182
+ return result;
15837
16183
  } catch (error) {
15838
16184
  throw error;
15839
16185
  }
@@ -15870,7 +16216,7 @@ function useVehicleRepo() {
15870
16216
  throw error2;
15871
16217
  }
15872
16218
  }
15873
- async function updateVehicle(_id, value) {
16219
+ async function updateVehicle(_id, value, session) {
15874
16220
  try {
15875
16221
  _id = new ObjectId46(_id);
15876
16222
  } catch (error) {
@@ -15881,7 +16227,11 @@ function useVehicleRepo() {
15881
16227
  ...value,
15882
16228
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15883
16229
  };
15884
- const res = await collection.updateOne({ _id }, { $set: updateValue });
16230
+ const res = await collection.updateOne(
16231
+ { _id },
16232
+ { $set: updateValue },
16233
+ { session }
16234
+ );
15885
16235
  if (res.modifiedCount === 0) {
15886
16236
  throw new InternalServerError28("Unable to update vehicle.");
15887
16237
  }
@@ -15960,8 +16310,94 @@ function useVehicleRepo() {
15960
16310
  );
15961
16311
  }
15962
16312
  }
16313
+ async function getVehiclesByNRIC({
16314
+ page = 1,
16315
+ limit = 10,
16316
+ nric = "",
16317
+ sort = {}
16318
+ }) {
16319
+ page = page > 0 ? page - 1 : 0;
16320
+ if (!nric) {
16321
+ throw new BadRequestError78("NRIC is required.");
16322
+ }
16323
+ const _nric = nric.trim();
16324
+ const query = {
16325
+ deletedAt: "",
16326
+ nric: _nric
16327
+ };
16328
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
16329
+ const cacheOptions = {
16330
+ deletedAt: "",
16331
+ nric: _nric,
16332
+ page: String(page),
16333
+ limit: String(limit),
16334
+ sort: JSON.stringify(sort)
16335
+ };
16336
+ const cacheKey = makeCacheKey26(namespace_collection, cacheOptions);
16337
+ const cachedData = await getCache(cacheKey);
16338
+ if (cachedData) {
16339
+ logger62.info(`Cache hit for key: ${cacheKey}`);
16340
+ return cachedData;
16341
+ }
16342
+ try {
16343
+ const items = await collection.aggregate([
16344
+ { $match: query },
16345
+ { $sort: sort },
16346
+ { $skip: page * limit },
16347
+ { $limit: limit },
16348
+ {
16349
+ $project: {
16350
+ name: 1,
16351
+ phoneNumber: 1,
16352
+ plateNumber: 1,
16353
+ recNo: 1,
16354
+ nric: 1,
16355
+ status: 1,
16356
+ type: 1
16357
+ }
16358
+ }
16359
+ ]).toArray();
16360
+ const length = await collection.countDocuments(query);
16361
+ const data = paginate21(items, page, limit, length);
16362
+ setCache(cacheKey, data, 15 * 60).then(() => logger62.info(`Cache set for key: ${cacheKey}`)).catch(
16363
+ (err) => logger62.error(`Failed to set cache for key: ${cacheKey}`, err)
16364
+ );
16365
+ return data;
16366
+ } catch (error) {
16367
+ throw error;
16368
+ }
16369
+ }
16370
+ async function deleteExpiredVehicles(session) {
16371
+ try {
16372
+ const tenYearsAgo = /* @__PURE__ */ new Date();
16373
+ tenYearsAgo.setFullYear(tenYearsAgo.getFullYear() - 10);
16374
+ const res = await collection.updateMany(
16375
+ {
16376
+ status: { $ne: "deleted" },
16377
+ end: { $exists: true, $lte: tenYearsAgo }
16378
+ // check only end
16379
+ },
16380
+ { $set: { status: "deleted", deletedAt: /* @__PURE__ */ new Date() } },
16381
+ { session }
16382
+ );
16383
+ if (res.modifiedCount === 0)
16384
+ throw new InternalServerError28("Unable to delete vehicle.");
16385
+ delNamespace().then(() => {
16386
+ logger62.info(`Cache cleared for namespace: ${namespace_collection}`);
16387
+ }).catch((err) => {
16388
+ logger62.error(
16389
+ `Failed to clear cache for namespace: ${namespace_collection}`,
16390
+ err
16391
+ );
16392
+ });
16393
+ return res.modifiedCount;
16394
+ } catch (error) {
16395
+ throw error;
16396
+ }
16397
+ }
15963
16398
  return {
15964
16399
  createIndex,
16400
+ createTextIndex,
15965
16401
  add,
15966
16402
  getVehicles,
15967
16403
  getSeasonPassTypes,
@@ -15969,7 +16405,9 @@ function useVehicleRepo() {
15969
16405
  updateVehicle,
15970
16406
  deleteVehicle,
15971
16407
  getByPlaceNumber,
15972
- getVehicleByPlateNumber
16408
+ getVehicleByPlateNumber,
16409
+ getVehiclesByNRIC,
16410
+ deleteExpiredVehicles
15973
16411
  };
15974
16412
  }
15975
16413
 
@@ -16355,7 +16793,7 @@ function useDahuaService() {
16355
16793
  username: Joi45.string().required(),
16356
16794
  password: Joi45.string().required(),
16357
16795
  plateNumber: Joi45.string().required(),
16358
- mode: Joi45.string().valid("TrafficBlackList", "TrafficRedList").required(),
16796
+ mode: Joi45.string().valid(...Object.values(ANPRMode)).required(),
16359
16797
  start: Joi45.string().isoDate().optional().allow("", null),
16360
16798
  end: Joi45.string().isoDate().optional().allow("", null),
16361
16799
  owner: Joi45.string().optional().allow("", null)
@@ -16427,18 +16865,154 @@ import {
16427
16865
  logger as logger64,
16428
16866
  useAtlas as useAtlas39
16429
16867
  } from "@7365admin1/node-server-utils";
16868
+ function formatDahuaDate(date) {
16869
+ const pad = (n) => String(n).padStart(2, "0");
16870
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(
16871
+ date.getDate()
16872
+ )} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(
16873
+ date.getSeconds()
16874
+ )}`;
16875
+ }
16430
16876
  function useVehicleService() {
16431
- const { add: _add, deleteVehicle: _deleteVehicle } = useVehicleRepo();
16877
+ const {
16878
+ add: _add,
16879
+ deleteVehicle: _deleteVehicle,
16880
+ updateVehicle: _updateVehicle,
16881
+ getVehicleById: _getVehicleById,
16882
+ deleteExpiredVehicles: _deleteExpiredVehicles
16883
+ } = useVehicleRepo();
16432
16884
  const {
16433
16885
  addPlateNumber: _addPlateNumber,
16434
16886
  removePlateNumber: _removePlateNumber
16435
16887
  } = useDahuaService();
16436
16888
  const { getAllCameraWithPassword: _getAllSiteCameras } = useSiteCameraRepo();
16889
+ const { getById: _getById } = useOrgRepo();
16437
16890
  async function add(value) {
16438
16891
  const session = useAtlas39.getClient()?.startSession();
16439
16892
  if (!session) {
16440
16893
  throw new Error("Unable to start session for vehicle service.");
16441
16894
  }
16895
+ const org = await _getById(value.org);
16896
+ if (!org)
16897
+ throw new BadRequestError80("Org not found");
16898
+ if (!Object.values(OrgNature).includes(org.nature)) {
16899
+ throw new BadRequestError80(
16900
+ "This organization is not allowed to add vehicles."
16901
+ );
16902
+ }
16903
+ value.status = "active" /* ACTIVE */;
16904
+ const addedBySecurity = org.nature === "security_agency" /* SECURITY_AGENCY */;
16905
+ const addedByPM = org.nature === "property_management_agency" /* PROPERTY_MANAGEMENT_AGENCY */;
16906
+ if (addedBySecurity)
16907
+ value.status = "pending" /* PENDING */;
16908
+ const isBlocklist = value.type === "blocklist" /* BLOCKLIST */;
16909
+ if (isBlocklist)
16910
+ value.status = "inactive" /* INACTIVE */;
16911
+ const _type = value.type;
16912
+ const _mode = isBlocklist ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
16913
+ const hasStart = typeof value.start === "string" ? value.start.trim() !== "" : !!value.start;
16914
+ const hasEnd = typeof value.end === "string" ? value.end.trim() !== "" : !!value.end;
16915
+ let startDate = null;
16916
+ let endDate = null;
16917
+ let start = "";
16918
+ let end = "";
16919
+ let startDateDahua;
16920
+ let endDateDahua;
16921
+ if (addedByPM && !hasStart && !hasEnd) {
16922
+ startDate = /* @__PURE__ */ new Date();
16923
+ endDate = new Date(startDate);
16924
+ endDate.setFullYear(endDate.getFullYear() + 10);
16925
+ start = startDate.toISOString();
16926
+ end = endDate.toISOString();
16927
+ startDateDahua = formatDahuaDate(startDate);
16928
+ endDateDahua = formatDahuaDate(endDate);
16929
+ } else {
16930
+ if (hasStart) {
16931
+ startDate = new Date(value.start);
16932
+ start = startDate.toISOString();
16933
+ startDateDahua = formatDahuaDate(startDate);
16934
+ }
16935
+ if (hasEnd) {
16936
+ endDate = new Date(value.end);
16937
+ end = endDate.toISOString();
16938
+ endDateDahua = formatDahuaDate(endDate);
16939
+ }
16940
+ }
16941
+ const owner = String(value.name ?? "").substring(0, 15);
16942
+ const plateNumbers = value.plateNumber;
16943
+ try {
16944
+ session.startTransaction();
16945
+ let message = "Vehicle plate number needs approval from property management.";
16946
+ let siteCameras = [];
16947
+ if (value.status && value.status !== "pending" /* PENDING */) {
16948
+ let page = 1;
16949
+ let pages = 1;
16950
+ const limit = 20;
16951
+ do {
16952
+ const siteCameraReq = await _getAllSiteCameras({
16953
+ site: value.site,
16954
+ type: "anpr",
16955
+ direction: ["both", "entry"],
16956
+ page,
16957
+ limit
16958
+ });
16959
+ pages = siteCameraReq.pages || 1;
16960
+ siteCameras.push(...siteCameraReq.items);
16961
+ page++;
16962
+ } while (page <= pages);
16963
+ if (!siteCameras.length) {
16964
+ throw new BadRequestError80("No site cameras found.");
16965
+ }
16966
+ }
16967
+ for (const plateNumber of plateNumbers) {
16968
+ const vehicleValue = {
16969
+ ...value,
16970
+ plateNumber,
16971
+ start,
16972
+ end
16973
+ };
16974
+ if (vehicleValue.status && vehicleValue.status !== "pending" /* PENDING */) {
16975
+ for (const camera of siteCameras) {
16976
+ const { host, username, password } = camera;
16977
+ const dahuaPayload = {
16978
+ host,
16979
+ username,
16980
+ password,
16981
+ plateNumber,
16982
+ mode: _mode,
16983
+ owner,
16984
+ ...startDateDahua ? { start: startDateDahua } : {},
16985
+ ...endDateDahua ? { end: endDateDahua } : {}
16986
+ };
16987
+ const dahuaResponse = await _addPlateNumber(dahuaPayload);
16988
+ if (dahuaResponse?.statusCode !== 200) {
16989
+ throw new BadRequestError80(
16990
+ `Failed to add plate number to ANPR ${_type}`
16991
+ );
16992
+ }
16993
+ const responseData = dahuaResponse?.data?.toString("utf-8") ?? "";
16994
+ vehicleValue.recNo = responseData.split("=")[1]?.trim();
16995
+ }
16996
+ message = `Vehicle plate number added to ${_type} successfully.`;
16997
+ }
16998
+ await _add(vehicleValue, session);
16999
+ }
17000
+ await session.commitTransaction();
17001
+ return message;
17002
+ } catch (error) {
17003
+ logger64.error("Error in vehicle service add:", error);
17004
+ await session.abortTransaction();
17005
+ throw error;
17006
+ } finally {
17007
+ session.endSession();
17008
+ }
17009
+ }
17010
+ async function deleteVehicle(_id, recno, site, type, bypass = false) {
17011
+ const session = useAtlas39.getClient()?.startSession();
17012
+ if (!session) {
17013
+ throw new Error("Unable to start session for vehicle service.");
17014
+ }
17015
+ const _mode = type !== "whitelist" /* WHITELIST */ ? "TrafficBlackList" /* TRAFFIC_BLACKLIST */ : "TrafficRedList" /* TRAFFIC_REDLIST */;
16442
17016
  try {
16443
17017
  session.startTransaction();
16444
17018
  const siteCameras = [];
@@ -16447,9 +17021,8 @@ function useVehicleService() {
16447
17021
  const limit = 20;
16448
17022
  do {
16449
17023
  const siteCameraReq = await _getAllSiteCameras({
16450
- site: value.site,
17024
+ site,
16451
17025
  type: "anpr",
16452
- direction: ["both", "entry"],
16453
17026
  page,
16454
17027
  limit
16455
17028
  });
@@ -16461,55 +17034,82 @@ function useVehicleService() {
16461
17034
  throw new BadRequestError80("No site cameras found.");
16462
17035
  }
16463
17036
  for (const camera of siteCameras) {
16464
- const { host, username, password } = camera;
17037
+ const host = camera.host;
17038
+ const username = camera.username;
17039
+ const password = camera.password;
16465
17040
  const dahuaPayload = {
16466
17041
  host,
16467
17042
  username,
16468
17043
  password,
16469
- plateNumber: value.plateNumber,
16470
- mode: "TrafficRedList",
16471
- start: value.start ? new Date(value.start).toISOString() : "",
16472
- end: value.end ? new Date(value.end).toISOString() : "",
16473
- owner: value.name
17044
+ recno,
17045
+ mode: _mode
16474
17046
  };
16475
- const dahuaResponse = await _addPlateNumber(dahuaPayload);
16476
- if (dahuaResponse?.statusCode !== 200) {
16477
- throw new BadRequestError80("Failed to add plate number to ANPR");
17047
+ const dahuaResponse = await _removePlateNumber(dahuaPayload);
17048
+ if (!bypass && dahuaResponse?.statusCode !== 200) {
17049
+ throw new BadRequestError80(
17050
+ `Failed to remove plate number to ANPR from ${type}`
17051
+ );
16478
17052
  }
16479
- const responseData = dahuaResponse?.data.toString("utf-8");
16480
- value.recNo = responseData.split("=")[1]?.trim();
16481
- const formattedValue = {
16482
- ...value,
16483
- start: value.start ? new Date(value.start).toISOString() : "",
16484
- end: value.end ? new Date(value.end).toISOString() : ""
16485
- };
16486
- await _add(formattedValue, session);
16487
17053
  }
17054
+ await _deleteVehicle(_id, session);
16488
17055
  await session.commitTransaction();
16489
- return "Vehicle plate number added successfully.";
17056
+ return `Vehicle plate number deleted from ${type} record successfully.`;
16490
17057
  } catch (error) {
16491
- logger64.error("Error in vehicle service add:", error);
16492
17058
  await session.abortTransaction();
16493
- throw error;
17059
+ if (error instanceof AppError14)
17060
+ throw error;
17061
+ throw new InternalServerError29("Failed to delete vehicle");
16494
17062
  } finally {
16495
17063
  session.endSession();
16496
17064
  }
16497
17065
  }
16498
- async function deleteVehicle(_id, recno, site, bypass = false) {
17066
+ async function approveVehicleById(id, orgId, siteId) {
16499
17067
  const session = useAtlas39.getClient()?.startSession();
16500
17068
  if (!session) {
16501
17069
  throw new Error("Unable to start session for vehicle service.");
16502
17070
  }
17071
+ const org = await _getById(orgId);
17072
+ if (!org)
17073
+ throw new BadRequestError80("Org not found");
17074
+ const allowedNatures2 = ["property_management_agency"];
17075
+ if (!allowedNatures2.includes(org.nature)) {
17076
+ throw new BadRequestError80(
17077
+ "Only property management can approve vehicles."
17078
+ );
17079
+ }
17080
+ const vehicle = await _getVehicleById(id);
17081
+ if (!vehicle) {
17082
+ throw new BadRequestError80("Vehicle not found");
17083
+ }
17084
+ const owner = vehicle.name.substring(0, 15);
17085
+ const hasStart = typeof vehicle.start === "string" ? vehicle.start.trim() !== "" : !!vehicle.start;
17086
+ const hasEnd = typeof vehicle.end === "string" ? vehicle.end.trim() !== "" : !!vehicle.end;
17087
+ let startDate = null;
17088
+ let endDate = null;
17089
+ if (!hasStart && !hasEnd) {
17090
+ startDate = /* @__PURE__ */ new Date();
17091
+ endDate = new Date(startDate);
17092
+ endDate.setFullYear(endDate.getFullYear() + 10);
17093
+ } else {
17094
+ startDate = hasStart ? new Date(vehicle.start) : null;
17095
+ endDate = hasEnd ? new Date(vehicle.end) : null;
17096
+ }
17097
+ const startIso = startDate ? startDate.toISOString() : "";
17098
+ const endIso = endDate ? endDate.toISOString() : "";
17099
+ const startDahua = startDate ? formatDahuaDate(startDate) : "";
17100
+ const endDahua = endDate ? formatDahuaDate(endDate) : "";
16503
17101
  try {
16504
17102
  session.startTransaction();
16505
17103
  const siteCameras = [];
16506
17104
  let page = 1;
16507
17105
  let pages = 1;
16508
17106
  const limit = 20;
17107
+ let value = {};
16509
17108
  do {
16510
17109
  const siteCameraReq = await _getAllSiteCameras({
16511
- site,
17110
+ site: siteId,
16512
17111
  type: "anpr",
17112
+ direction: ["both", "entry"],
16513
17113
  page,
16514
17114
  limit
16515
17115
  });
@@ -16521,24 +17121,51 @@ function useVehicleService() {
16521
17121
  throw new BadRequestError80("No site cameras found.");
16522
17122
  }
16523
17123
  for (const camera of siteCameras) {
16524
- const host = camera.host;
16525
- const username = camera.username;
16526
- const password = camera.password;
17124
+ const { host, username, password } = camera;
16527
17125
  const dahuaPayload = {
16528
17126
  host,
16529
17127
  username,
16530
17128
  password,
16531
- recno,
16532
- mode: "TrafficRedList"
17129
+ plateNumber: vehicle.plateNumber,
17130
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
17131
+ start: startDahua,
17132
+ end: endDahua,
17133
+ owner
16533
17134
  };
16534
- const dahuaResponse = await _removePlateNumber(dahuaPayload);
16535
- if (!bypass && dahuaResponse?.statusCode !== 200) {
16536
- throw new BadRequestError80("Failed to remove plate number to ANPR");
17135
+ const dahuaResponse = await _addPlateNumber(dahuaPayload);
17136
+ if (dahuaResponse?.statusCode !== 200) {
17137
+ throw new BadRequestError80("Failed to add plate number to ANPR");
16537
17138
  }
17139
+ const responseData = dahuaResponse?.data.toString("utf-8");
17140
+ value.recNo = responseData.split("=")[1]?.trim();
16538
17141
  }
16539
- await _deleteVehicle(_id, session);
17142
+ value.status = "active";
17143
+ const formattedValue = {
17144
+ ...value,
17145
+ start: startIso,
17146
+ end: endIso
17147
+ };
17148
+ await _updateVehicle(id, formattedValue, session);
16540
17149
  await session.commitTransaction();
16541
- return "Vehicle plate number deleted successfully.";
17150
+ return "Vehicle plate number approved and added successfully.";
17151
+ } catch (error) {
17152
+ logger64.error("Error in vehicle service add:", error);
17153
+ await session.abortTransaction();
17154
+ throw error;
17155
+ } finally {
17156
+ session.endSession();
17157
+ }
17158
+ }
17159
+ async function processDeletingExpiredVehicles() {
17160
+ const session = useAtlas39.getClient()?.startSession();
17161
+ if (!session) {
17162
+ throw new Error("Unable to start session for vehicle service.");
17163
+ }
17164
+ try {
17165
+ session.startTransaction();
17166
+ await _deleteExpiredVehicles();
17167
+ await session.commitTransaction();
17168
+ return `Expired Vehicle plate numbers deleted successfully.`;
16542
17169
  } catch (error) {
16543
17170
  await session.abortTransaction();
16544
17171
  if (error instanceof AppError14)
@@ -16550,7 +17177,9 @@ function useVehicleService() {
16550
17177
  }
16551
17178
  return {
16552
17179
  add,
16553
- deleteVehicle
17180
+ deleteVehicle,
17181
+ approveVehicleById,
17182
+ processDeletingExpiredVehicles
16554
17183
  };
16555
17184
  }
16556
17185
 
@@ -16558,23 +17187,28 @@ function useVehicleService() {
16558
17187
  import { BadRequestError as BadRequestError81, logger as logger65 } from "@7365admin1/node-server-utils";
16559
17188
  import Joi46 from "joi";
16560
17189
  function useVehicleController() {
16561
- const { add: _add, deleteVehicle: _deleteVehicle } = useVehicleService();
17190
+ const {
17191
+ add: _add,
17192
+ deleteVehicle: _deleteVehicle,
17193
+ approveVehicleById: _approveVehicleById
17194
+ } = useVehicleService();
16562
17195
  const {
16563
17196
  getSeasonPassTypes: _getSeasonPassTypes,
16564
17197
  getVehicles: _getVehicles,
16565
17198
  getVehicleById: _getVehicleById,
16566
- updateVehicle: _updateVehicle
17199
+ updateVehicle: _updateVehicle,
17200
+ getVehiclesByNRIC: _getVehiclesByNRIC
16567
17201
  } = useVehicleRepo();
16568
17202
  async function add(req, res, next) {
16569
17203
  const payload = req.body;
16570
- const { error } = vehicleSchema.validate(payload);
17204
+ const { error, value } = vehicleSchema.validate(payload);
16571
17205
  if (error) {
16572
17206
  logger65.log({ level: "error", message: error.message });
16573
17207
  next(new BadRequestError81(error.message));
16574
17208
  return;
16575
17209
  }
16576
17210
  try {
16577
- const data = await _add(payload);
17211
+ const data = await _add(value);
16578
17212
  res.status(201).json({
16579
17213
  message: "Vehicle added successfully.",
16580
17214
  data
@@ -16594,8 +17228,9 @@ function useVehicleController() {
16594
17228
  limit: Joi46.number().integer().min(1).max(100).allow("", null).default(10),
16595
17229
  sort: Joi46.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
16596
17230
  order: Joi46.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
16597
- type: Joi46.string().optional().allow("", ...allowedTypes),
16598
- category: Joi46.string().optional().allow("", ...allowedCategories)
17231
+ type: Joi46.string().optional().valid(...Object.values(VehicleType)).allow(null, ""),
17232
+ category: Joi46.string().optional().valid(...Object.values(VehicleCategory)).allow(null, ""),
17233
+ status: Joi46.string().optional().valid(...Object.values(VehicleStatus)).allow(null, "")
16599
17234
  });
16600
17235
  const query = { ...req.query };
16601
17236
  const { error } = validation.validate(query);
@@ -16609,6 +17244,7 @@ function useVehicleController() {
16609
17244
  const limit = parseInt(req.query.limit ?? "10");
16610
17245
  const type = req.query.type ?? "";
16611
17246
  const category = req.query.category ?? "";
17247
+ const status = req.query.status ?? "";
16612
17248
  const sortObj = {};
16613
17249
  const sortFields = String(req.query.sort).split(",");
16614
17250
  const sortOrders = String(req.query.order).split(",");
@@ -16625,7 +17261,8 @@ function useVehicleController() {
16625
17261
  limit,
16626
17262
  sort: sortObj,
16627
17263
  type,
16628
- category
17264
+ category,
17265
+ status
16629
17266
  });
16630
17267
  res.json(data);
16631
17268
  return;
@@ -16681,7 +17318,8 @@ function useVehicleController() {
16681
17318
  block: Joi46.number().integer().optional().allow(0, null),
16682
17319
  level: Joi46.string().optional().allow("", null),
16683
17320
  unit: Joi46.string().optional().allow("", null),
16684
- plateNumber: Joi46.string().optional().allow("", null)
17321
+ plateNumber: Joi46.string().optional().allow("", null),
17322
+ nric: Joi46.string().optional().allow("", null)
16685
17323
  });
16686
17324
  const _id = req.params.id;
16687
17325
  const payload = { ...req.body };
@@ -16702,23 +17340,23 @@ function useVehicleController() {
16702
17340
  }
16703
17341
  }
16704
17342
  async function deleteVehicle(req, res, next) {
16705
- const site = req.params.site ?? "";
16706
- const _id = req.params.id ?? "";
16707
- const recno = req.params.recno ?? "";
16708
- const bypass = req.query.bypass === "true";
17343
+ const _id = req.params.id;
16709
17344
  const deleteVehicleSchema = Joi46.object({
16710
- _id: Joi46.string().hex().required(),
17345
+ _id: Joi46.string().hex().length(24).required(),
16711
17346
  recno: Joi46.string().required(),
16712
- bypass: Joi46.boolean().optional()
17347
+ site: Joi46.string().hex().length(24).required(),
17348
+ type: Joi46.string().valid("whitelist", "blocklist").required(),
17349
+ bypass: Joi46.boolean().optional().default(true)
16713
17350
  });
16714
- const { error } = deleteVehicleSchema.validate({ _id, recno, bypass });
17351
+ const { error, value } = deleteVehicleSchema.validate({ _id, ...req.body });
16715
17352
  if (error) {
16716
17353
  logger65.log({ level: "error", message: error.message });
16717
17354
  next(new BadRequestError81(error.message));
16718
17355
  return;
16719
17356
  }
17357
+ const { recno, site, type, bypass } = value;
16720
17358
  try {
16721
- const data = await _deleteVehicle(_id, recno, site, bypass);
17359
+ const data = await _deleteVehicle(_id, recno, site, type, bypass);
16722
17360
  res.json({
16723
17361
  message: "Vehicle deleted successfully.",
16724
17362
  data
@@ -16729,13 +17367,83 @@ function useVehicleController() {
16729
17367
  return;
16730
17368
  }
16731
17369
  }
17370
+ async function approveVehicleById(req, res, next) {
17371
+ const validation = Joi46.object({
17372
+ _id: Joi46.string().hex().length(24).required(),
17373
+ org: Joi46.string().hex().length(24).required(),
17374
+ site: Joi46.string().hex().length(24).required()
17375
+ });
17376
+ const _id = req.params.id;
17377
+ const payload = { ...req.body };
17378
+ const { error, value } = validation.validate({ _id, ...payload });
17379
+ if (error) {
17380
+ logger65.log({ level: "error", message: error.message });
17381
+ next(new BadRequestError81(error.message));
17382
+ return;
17383
+ }
17384
+ try {
17385
+ await _approveVehicleById(value._id, value.org, value.site);
17386
+ res.json({ message: "Successfully approved and updated vehicle." });
17387
+ return;
17388
+ } catch (error2) {
17389
+ logger65.log({ level: "error", message: error2.message });
17390
+ next(error2);
17391
+ return;
17392
+ }
17393
+ }
17394
+ async function getVehiclesByNRIC(req, res, next) {
17395
+ const allowedFields = ["start", "end"];
17396
+ const allowedOrder = ["asc", "desc"];
17397
+ const validation = Joi46.object({
17398
+ nric: Joi46.string().optional().allow("", null),
17399
+ page: Joi46.number().integer().min(1).allow("", null).default(1),
17400
+ limit: Joi46.number().integer().min(1).max(100).allow("", null).default(10),
17401
+ sort: Joi46.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
17402
+ order: Joi46.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder)
17403
+ });
17404
+ const query = { ...req.query };
17405
+ const { error } = validation.validate(query);
17406
+ if (error) {
17407
+ logger65.log({ level: "error", message: error.message });
17408
+ next(new BadRequestError81(error.message));
17409
+ return;
17410
+ }
17411
+ const nric = req.query.nric ?? "";
17412
+ const page = parseInt(req.query.page ?? "1");
17413
+ const limit = parseInt(req.query.limit ?? "10");
17414
+ const sortObj = {};
17415
+ const sortFields = String(req.query.sort).split(",");
17416
+ const sortOrders = String(req.query.order).split(",");
17417
+ sortFields.forEach((field, index) => {
17418
+ if (allowedFields.includes(field)) {
17419
+ const order = sortOrders[index] === "asc" ? 1 : -1;
17420
+ sortObj[field] = order;
17421
+ }
17422
+ });
17423
+ try {
17424
+ const data = await _getVehiclesByNRIC({
17425
+ nric,
17426
+ page,
17427
+ limit,
17428
+ sort: sortObj
17429
+ });
17430
+ res.json(data);
17431
+ return;
17432
+ } catch (error2) {
17433
+ logger65.log({ level: "error", message: error2.message });
17434
+ next(error2);
17435
+ return;
17436
+ }
17437
+ }
16732
17438
  return {
16733
17439
  add,
16734
17440
  getVehicles,
16735
17441
  getSeasonPassTypes,
16736
17442
  getVehicleById,
16737
17443
  updateVehicle,
16738
- deleteVehicle
17444
+ deleteVehicle,
17445
+ approveVehicleById,
17446
+ getVehiclesByNRIC
16739
17447
  };
16740
17448
  }
16741
17449
 
@@ -16837,7 +17545,7 @@ function useSiteCameraController() {
16837
17545
  }
16838
17546
  async function getAll(req, res, next) {
16839
17547
  const query = req.query;
16840
- const allowedTypes2 = ["ip", "exit", "entry", "both"];
17548
+ const allowedTypes = ["ip", "exit", "entry", "both"];
16841
17549
  const validation = Joi47.object({
16842
17550
  type: Joi47.string().optional(),
16843
17551
  site: Joi47.string().optional(),
@@ -19363,25 +20071,15 @@ function usePersonService() {
19363
20071
  }
19364
20072
  async function plateValidity(value) {
19365
20073
  const now = /* @__PURE__ */ new Date();
19366
- if (value.type !== "resident" && value.type !== "tenant") {
19367
- return { start: "", end: "" };
19368
- }
19369
- if (value.type === "resident") {
20074
+ if (["resident" /* RESIDENT */, "tenant" /* TENANT */].includes(
20075
+ value?.type
20076
+ )) {
19370
20077
  const end = new Date(now);
19371
20078
  end.setFullYear(end.getFullYear() + 10);
19372
- return { start: now.toISOString(), end: end.toISOString() };
19373
- }
19374
- if (value.type == "tenant") {
19375
- const unit = await _getUnitById(value.unit?.toString() || "");
19376
- if (!unit) {
19377
- throw new BadRequestError97("Building unit not found for tenant.");
19378
- }
19379
- if (unit.leaseStart && unit.leaseEnd) {
19380
- return {
19381
- start: new Date(unit.leaseStart).toISOString(),
19382
- end: new Date(unit.leaseEnd).toISOString()
19383
- };
19384
- }
20079
+ return {
20080
+ start: now.toISOString(),
20081
+ end: end.toISOString()
20082
+ };
19385
20083
  }
19386
20084
  return { start: "", end: "" };
19387
20085
  }
@@ -19400,7 +20098,8 @@ function usePersonController() {
19400
20098
  getPersonByPhoneNumber: _getPersonByPhoneNumber,
19401
20099
  getPeopleByUnit: _getPeopleByUnit,
19402
20100
  getCompany: _getCompany,
19403
- getPeopleByPlateNumber: _getPeopleByPlateNumber
20101
+ getPeopleByPlateNumber: _getPeopleByPlateNumber,
20102
+ getPeopleByNRIC: _getPeopleByNRIC
19404
20103
  } = usePersonRepo();
19405
20104
  const { add: _add, updateById: _updateById } = usePersonService();
19406
20105
  async function add(req, res, next) {
@@ -19635,6 +20334,53 @@ function usePersonController() {
19635
20334
  return;
19636
20335
  }
19637
20336
  }
20337
+ async function getPeopleByNRIC(req, res, next) {
20338
+ const allowedFields = ["start", "end"];
20339
+ const allowedOrder = ["asc", "desc"];
20340
+ const validation = Joi58.object({
20341
+ nric: Joi58.string().optional().allow("", null),
20342
+ page: Joi58.number().integer().min(1).allow("", null).default(1),
20343
+ limit: Joi58.number().integer().min(1).max(100).allow("", null).default(10),
20344
+ sort: Joi58.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
20345
+ order: Joi58.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
20346
+ site: Joi58.string().hex().length(24).required()
20347
+ });
20348
+ const query = { ...req.query };
20349
+ const { error } = validation.validate(query);
20350
+ if (error) {
20351
+ logger81.log({ level: "error", message: error.message });
20352
+ next(new BadRequestError98(error.message));
20353
+ return;
20354
+ }
20355
+ const nric = req.query.nric ?? "";
20356
+ const page = parseInt(req.query.page ?? "1");
20357
+ const limit = parseInt(req.query.limit ?? "10");
20358
+ const site = req.query.site ?? "";
20359
+ const sortObj = {};
20360
+ const sortFields = String(req.query.sort).split(",");
20361
+ const sortOrders = String(req.query.order).split(",");
20362
+ sortFields.forEach((field, index) => {
20363
+ if (allowedFields.includes(field)) {
20364
+ const order = sortOrders[index] === "asc" ? 1 : -1;
20365
+ sortObj[field] = order;
20366
+ }
20367
+ });
20368
+ try {
20369
+ const data = await _getPeopleByNRIC({
20370
+ nric,
20371
+ page,
20372
+ limit,
20373
+ sort: sortObj,
20374
+ site
20375
+ });
20376
+ res.json(data);
20377
+ return;
20378
+ } catch (error2) {
20379
+ logger81.log({ level: "error", message: error2.message });
20380
+ next(error2);
20381
+ return;
20382
+ }
20383
+ }
19638
20384
  return {
19639
20385
  add,
19640
20386
  getAll,
@@ -19644,7 +20390,8 @@ function usePersonController() {
19644
20390
  getPersonByPhoneNumber,
19645
20391
  getPeopleByUnit,
19646
20392
  getCompany,
19647
- getPeopleByPlateNumber
20393
+ getPeopleByPlateNumber,
20394
+ getPeopleByNRIC
19648
20395
  };
19649
20396
  }
19650
20397
 
@@ -24322,20 +25069,19 @@ function useDocumentManagementController() {
24322
25069
  // src/models/bulletin-board.model.ts
24323
25070
  import Joi75 from "joi";
24324
25071
  import { ObjectId as ObjectId71 } from "mongodb";
25072
+ var BULLETIN_RECIPIENTS = [
25073
+ "resident",
25074
+ "security_agency",
25075
+ "cleaning_services",
25076
+ "mechanical_electrical",
25077
+ "property_management_agency"
25078
+ ];
25079
+ var STATUS_VALUES = ["active", "expired", "deleted"];
24325
25080
  var schemaBulletinBoard = Joi75.object({
24326
25081
  _id: Joi75.string().hex().optional().allow("", null),
24327
25082
  site: Joi75.string().hex().optional().allow("", null),
24328
25083
  orgId: Joi75.string().hex().optional().allow("", null),
24329
- recipients: Joi75.array().items(
24330
- Joi75.string().valid(
24331
- "admin",
24332
- "organization",
24333
- "site",
24334
- "service-provider",
24335
- "service-provider-member",
24336
- "resident"
24337
- )
24338
- ).optional(),
25084
+ recipients: Joi75.array().items(Joi75.string().valid(...BULLETIN_RECIPIENTS)).unique().optional(),
24339
25085
  title: Joi75.string().optional().allow("", null),
24340
25086
  content: Joi75.string().optional().allow("", null),
24341
25087
  file: Joi75.array().items(
@@ -24348,23 +25094,14 @@ var schemaBulletinBoard = Joi75.object({
24348
25094
  noExpiration: Joi75.boolean().optional(),
24349
25095
  startDate: Joi75.date().optional().allow("", null),
24350
25096
  endDate: Joi75.date().optional().allow("", null),
24351
- status: Joi75.string().optional().allow("", null),
25097
+ status: Joi75.string().valid(...STATUS_VALUES).optional(),
24352
25098
  createdAt: Joi75.date().optional().allow(null),
24353
25099
  updatedAt: Joi75.date().optional().allow(null),
24354
25100
  deletedAt: Joi75.date().optional().allow(null)
24355
25101
  });
24356
25102
  var schemaUpdateBulletinBoard = Joi75.object({
24357
25103
  _id: Joi75.string().hex().required(),
24358
- recipients: Joi75.array().items(
24359
- Joi75.string().valid(
24360
- "admin",
24361
- "organization",
24362
- "site",
24363
- "service-provider",
24364
- "service-provider-member",
24365
- "resident"
24366
- )
24367
- ).optional(),
25104
+ recipients: Joi75.array().items(Joi75.string().valid(...BULLETIN_RECIPIENTS)).unique().optional(),
24368
25105
  title: Joi75.string().optional().allow("", null),
24369
25106
  content: Joi75.string().optional().allow("", null),
24370
25107
  file: Joi75.array().items(
@@ -24377,7 +25114,7 @@ var schemaUpdateBulletinBoard = Joi75.object({
24377
25114
  noExpiration: Joi75.boolean().optional(),
24378
25115
  startDate: Joi75.date().optional().allow("", null),
24379
25116
  endDate: Joi75.date().optional().allow("", null),
24380
- status: Joi75.string().optional().allow("", null)
25117
+ status: Joi75.string().valid(...STATUS_VALUES).optional()
24381
25118
  });
24382
25119
  function MBulletinBoard(value) {
24383
25120
  const { error } = schemaBulletinBoard.validate(value);
@@ -24477,7 +25214,8 @@ function useBulletinBoardRepo() {
24477
25214
  limit = 10,
24478
25215
  sort = {},
24479
25216
  site = "",
24480
- status = "active"
25217
+ status = "active",
25218
+ recipients = []
24481
25219
  }, session) {
24482
25220
  page = page > 0 ? page - 1 : 0;
24483
25221
  try {
@@ -24487,7 +25225,9 @@ function useBulletinBoardRepo() {
24487
25225
  }
24488
25226
  const query = {
24489
25227
  site,
24490
- status
25228
+ status,
25229
+ ...search && { $text: { $search: search } },
25230
+ ...recipients?.length && { recipients: { $in: recipients } }
24491
25231
  };
24492
25232
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
24493
25233
  const cacheOptions = {
@@ -24495,12 +25235,10 @@ function useBulletinBoardRepo() {
24495
25235
  status,
24496
25236
  sort: JSON.stringify(sort),
24497
25237
  page,
24498
- limit
25238
+ limit,
25239
+ ...search && { search },
25240
+ ...recipients?.length && { recipients: recipients.sort().join(",") }
24499
25241
  };
24500
- if (search) {
24501
- query.$text = { $search: search };
24502
- cacheOptions.search = search;
24503
- }
24504
25242
  const cacheKey = makeCacheKey39(namespace_collection, cacheOptions);
24505
25243
  const cachedData = await getCache(cacheKey);
24506
25244
  if (cachedData) {
@@ -24592,7 +25330,7 @@ function useBulletinBoardRepo() {
24592
25330
  throw error;
24593
25331
  }
24594
25332
  }
24595
- async function deleteBulletinBoardById(_id) {
25333
+ async function deleteBulletinBoardById(_id, session) {
24596
25334
  try {
24597
25335
  _id = new ObjectId72(_id);
24598
25336
  } catch (error) {
@@ -24604,7 +25342,11 @@ function useBulletinBoardRepo() {
24604
25342
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
24605
25343
  deletedAt: (/* @__PURE__ */ new Date()).toISOString()
24606
25344
  };
24607
- const res = await collection.updateOne({ _id }, { $set: updateValue });
25345
+ const res = await collection.updateOne(
25346
+ { _id },
25347
+ { $set: updateValue },
25348
+ { session }
25349
+ );
24608
25350
  if (res.modifiedCount === 0) {
24609
25351
  throw new InternalServerError42("Unable to delete bulletin board.");
24610
25352
  }
@@ -24670,8 +25412,11 @@ function useBulletinBoardService() {
24670
25412
  const {
24671
25413
  add: _add,
24672
25414
  updateBulletinBoardById: _updateBulletinBoardById,
24673
- processExpiredBulletinBoards: _processExpiredBulletinBoards
25415
+ processExpiredBulletinBoards: _processExpiredBulletinBoards,
25416
+ deleteBulletinBoardById: _deleteBulletinBoardById,
25417
+ getBulletinBoardById: _getBulletinBoardById
24674
25418
  } = useBulletinBoardRepo();
25419
+ const { deleteFileById: _deleteFileById } = useFileRepo();
24675
25420
  async function add(value) {
24676
25421
  const session = useAtlas65.getClient()?.startSession();
24677
25422
  session?.startTransaction();
@@ -24714,10 +25459,31 @@ function useBulletinBoardService() {
24714
25459
  session?.endSession();
24715
25460
  }
24716
25461
  }
25462
+ async function deleteBulletinBoardById(id) {
25463
+ const session = useAtlas65.getClient()?.startSession();
25464
+ session?.startTransaction();
25465
+ try {
25466
+ const existingBulletinBoard = await _getBulletinBoardById(id);
25467
+ if (Array.isArray(existingBulletinBoard.file)) {
25468
+ for (const file of existingBulletinBoard.file) {
25469
+ await _deleteFileById(file, session);
25470
+ }
25471
+ }
25472
+ await _deleteBulletinBoardById(id, session);
25473
+ await session?.commitTransaction();
25474
+ return "Successfully deleted bulletin board.";
25475
+ } catch (error) {
25476
+ await session?.abortTransaction();
25477
+ throw error;
25478
+ } finally {
25479
+ session?.endSession();
25480
+ }
25481
+ }
24717
25482
  return {
24718
25483
  add,
24719
25484
  updateBulletinBoardById,
24720
- processExpiredBulletinBoards
25485
+ processExpiredBulletinBoards,
25486
+ deleteBulletinBoardById
24721
25487
  };
24722
25488
  }
24723
25489
 
@@ -24725,12 +25491,12 @@ function useBulletinBoardService() {
24725
25491
  import { BadRequestError as BadRequestError123, logger as logger104 } from "@7365admin1/node-server-utils";
24726
25492
  import Joi76 from "joi";
24727
25493
  function useBulletinBoardController() {
24728
- const { add: _add, updateBulletinBoardById: _updateBulletinBoardById } = useBulletinBoardService();
24729
25494
  const {
24730
- getAll: _getAll,
24731
- getBulletinBoardById: _getBulletinBoardById,
25495
+ add: _add,
25496
+ updateBulletinBoardById: _updateBulletinBoardById,
24732
25497
  deleteBulletinBoardById: _deleteBulletinBoardById
24733
- } = useBulletinBoardRepo();
25498
+ } = useBulletinBoardService();
25499
+ const { getAll: _getAll, getBulletinBoardById: _getBulletinBoardById } = useBulletinBoardRepo();
24734
25500
  async function add(req, res, next) {
24735
25501
  const payload = { ...req.body };
24736
25502
  const { error } = schemaBulletinBoard.validate(payload, {
@@ -24762,12 +25528,23 @@ function useBulletinBoardController() {
24762
25528
  sort: Joi76.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
24763
25529
  order: Joi76.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
24764
25530
  site: Joi76.string().hex().required(),
24765
- status: Joi76.string().optional().allow(null, "")
25531
+ status: Joi76.string().optional().allow(null, ""),
25532
+ recipients: Joi76.string().optional().allow("", null).custom((value, helpers) => {
25533
+ const parsed = value.split(",").map((v) => v.trim()).filter(Boolean);
25534
+ for (const r of parsed) {
25535
+ if (!BULLETIN_RECIPIENTS.includes(r)) {
25536
+ return helpers.error("any.only");
25537
+ }
25538
+ }
25539
+ return value;
25540
+ }).messages({
25541
+ "any.only": `Recipients must be one of: ${BULLETIN_RECIPIENTS.join(
25542
+ ", "
25543
+ )}`
25544
+ })
24766
25545
  });
24767
25546
  const query = { ...req.query };
24768
- const { error } = validation.validate(query, {
24769
- abortEarly: false
24770
- });
25547
+ const { error } = validation.validate(query, { abortEarly: false });
24771
25548
  if (error) {
24772
25549
  const messages = error.details.map((d) => d.message).join(", ");
24773
25550
  logger104.log({ level: "error", message: messages });
@@ -24779,9 +25556,15 @@ function useBulletinBoardController() {
24779
25556
  const limit = parseInt(req.query.limit ?? "10");
24780
25557
  const site = req.query.site ?? "";
24781
25558
  const status = req.query.status ?? "active";
25559
+ const recipientsRaw = req.query.recipients ?? "";
25560
+ const recipients = recipientsRaw && typeof recipientsRaw === "string" ? Array.from(
25561
+ new Set(
25562
+ recipientsRaw.split(",").map((r) => r.trim()).filter(Boolean)
25563
+ )
25564
+ ) : [];
24782
25565
  const sortObj = {};
24783
- const sortFields = String(req.query.sort).split(",");
24784
- const sortOrders = String(req.query.order).split(",");
25566
+ const sortFields = String(req.query.sort ?? "").split(",").filter(Boolean);
25567
+ const sortOrders = String(req.query.order ?? "").split(",").filter(Boolean);
24785
25568
  sortFields.forEach((field, index) => {
24786
25569
  if (allowedFields.includes(field)) {
24787
25570
  const order = sortOrders[index] === "asc" ? 1 : -1;
@@ -24795,7 +25578,8 @@ function useBulletinBoardController() {
24795
25578
  limit,
24796
25579
  sort: sortObj,
24797
25580
  site,
24798
- status
25581
+ status,
25582
+ recipients
24799
25583
  });
24800
25584
  res.status(200).json(data);
24801
25585
  return;
@@ -24878,6 +25662,16 @@ function useBulletinBoardController() {
24878
25662
  import { BadRequestError as BadRequestError124, logger as logger105 } from "@7365admin1/node-server-utils";
24879
25663
  import { ObjectId as ObjectId73 } from "mongodb";
24880
25664
  import Joi77 from "joi";
25665
+
25666
+ // src/types/enums/billing-frequency.enum.ts
25667
+ var EBillingFrequency = /* @__PURE__ */ ((EBillingFrequency2) => {
25668
+ EBillingFrequency2["MONTHLY"] = "monthly";
25669
+ EBillingFrequency2["QAURTERLY"] = "quarterly";
25670
+ EBillingFrequency2["ANNUALLY"] = "annually";
25671
+ return EBillingFrequency2;
25672
+ })(EBillingFrequency || {});
25673
+
25674
+ // src/models/site-billing-item.model.ts
24881
25675
  var schemaUnits = Joi77.object({
24882
25676
  _id: Joi77.string().hex().optional(),
24883
25677
  name: Joi77.string().optional()
@@ -24888,7 +25682,7 @@ var schemaBillingItem = Joi77.object({
24888
25682
  org: Joi77.string().hex().required(),
24889
25683
  name: Joi77.string().required(),
24890
25684
  amount: Joi77.number().required(),
24891
- frequency: Joi77.string().valid("month", "quarter", "annual").required(),
25685
+ frequency: Joi77.string().valid(...Object.values(EBillingFrequency)).required(),
24892
25686
  billingType: Joi77.string().valid("recurring", "non-recurring").required(),
24893
25687
  dueInDays: Joi77.number().optional().allow(null, ""),
24894
25688
  date: Joi77.string().required(),
@@ -24913,7 +25707,7 @@ var schemaUpdateSiteBillingItem = Joi77.object({
24913
25707
  org: Joi77.string().hex().optional().allow(null, ""),
24914
25708
  name: Joi77.string().optional().allow(null, ""),
24915
25709
  amount: Joi77.number().optional().allow(null, ""),
24916
- frequency: Joi77.string().valid("month", "quarter", "annual").optional().allow(null, ""),
25710
+ frequency: Joi77.string().valid(...Object.values(EBillingFrequency)).optional().allow(null, ""),
24917
25711
  billingType: Joi77.string().valid("recurring", "non-recurring").optional().allow(null, ""),
24918
25712
  dueInDays: Joi77.number().optional().allow(null, ""),
24919
25713
  date: Joi77.string().optional().allow(null, ""),
@@ -25067,7 +25861,16 @@ function useSiteBillingItemRepo() {
25067
25861
  ...search && {
25068
25862
  $or: [
25069
25863
  { name: { $regex: search, $options: "i" } },
25070
- { frequency: { $regex: search, $options: "i" } }
25864
+ { frequency: { $regex: search, $options: "i" } },
25865
+ {
25866
+ $expr: {
25867
+ $regexMatch: {
25868
+ input: { $toString: "$totalAmount" },
25869
+ regex: search,
25870
+ options: "i"
25871
+ }
25872
+ }
25873
+ }
25071
25874
  ]
25072
25875
  },
25073
25876
  ...ObjectId74.isValid(site) && { site: new ObjectId74(site) }
@@ -26243,7 +27046,8 @@ function useEventManagementRepo() {
26243
27046
  sort = {},
26244
27047
  site = "",
26245
27048
  status = "",
26246
- type = ""
27049
+ type = "",
27050
+ date = ""
26247
27051
  }, session) {
26248
27052
  page = page > 0 ? page - 1 : 0;
26249
27053
  try {
@@ -26254,7 +27058,13 @@ function useEventManagementRepo() {
26254
27058
  const baseQuery = {
26255
27059
  site,
26256
27060
  status: status ? status : { $ne: "deleted" },
26257
- ...type && { type }
27061
+ ...type && { type },
27062
+ ...date && {
27063
+ dateTime: {
27064
+ $gte: `${date}T00:00:00.000Z`,
27065
+ $lt: `${date}T23:59:59.999Z`
27066
+ }
27067
+ }
26258
27068
  };
26259
27069
  let query = { ...baseQuery };
26260
27070
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
@@ -26552,7 +27362,8 @@ function useEventManagementController() {
26552
27362
  order: Joi82.string().pattern(/^(asc|desc)(,(asc|desc))*$/).optional().allow("", ...allowedOrder),
26553
27363
  site: Joi82.string().hex().required(),
26554
27364
  status: Joi82.string().optional(),
26555
- type: Joi82.string().optional().valid("TASK", "EVENT").allow(null, "")
27365
+ type: Joi82.string().optional().valid("TASK", "EVENT").allow(null, ""),
27366
+ date: Joi82.string().optional().allow(null, "")
26556
27367
  });
26557
27368
  const query = { ...req.query };
26558
27369
  const { error } = validation.validate(query, {
@@ -26570,6 +27381,7 @@ function useEventManagementController() {
26570
27381
  const site = req.query.site ?? "";
26571
27382
  const status = req.query.status ?? "";
26572
27383
  const type = req.query.type ?? "";
27384
+ const date = req.query.date ?? "";
26573
27385
  const sortObj = {};
26574
27386
  const sortFields = String(req.query.sort).split(",");
26575
27387
  const sortOrders = String(req.query.order).split(",");
@@ -26587,7 +27399,8 @@ function useEventManagementController() {
26587
27399
  sort: sortObj,
26588
27400
  site,
26589
27401
  status,
26590
- type
27402
+ type,
27403
+ date
26591
27404
  });
26592
27405
  res.status(200).json(data);
26593
27406
  return;
@@ -26913,6 +27726,7 @@ function useSiteUnitBillingRepo() {
26913
27726
  }
26914
27727
  };
26915
27728
  }
27729
+ const unitSearchRegex = search ? search.trim().replace(/\s+/g, "").replace(/\//g, "\\s*/\\s*") : null;
26916
27730
  const query = {
26917
27731
  paymentStatus,
26918
27732
  status,
@@ -26920,7 +27734,16 @@ function useSiteUnitBillingRepo() {
26920
27734
  $or: [
26921
27735
  { unitOwner: { $regex: search, $options: "i" } },
26922
27736
  { billName: { $regex: search, $options: "i" } },
26923
- { unit: { $regex: search, $options: "i" } }
27737
+ { unit: { $regex: unitSearchRegex, $options: "i" } },
27738
+ {
27739
+ $expr: {
27740
+ $regexMatch: {
27741
+ input: { $toString: "$amountPaid" },
27742
+ regex: search,
27743
+ options: "i"
27744
+ }
27745
+ }
27746
+ }
26924
27747
  ]
26925
27748
  },
26926
27749
  ...ObjectId80.isValid(site) && { site: new ObjectId80(site) },
@@ -27347,14 +28170,14 @@ function useSiteUnitBillingService() {
27347
28170
  function isBillingChecker(billing_item, todayMonth, todayDate) {
27348
28171
  const billingMonth = Number(billing_item.month);
27349
28172
  const billingDay = Number(billing_item.date);
27350
- if (billing_item.frequency === "month") {
28173
+ if (billing_item.frequency === "monthly" /* MONTHLY */) {
27351
28174
  return todayDate === billingDay;
27352
28175
  }
27353
- if (billing_item.frequency === "quarter") {
28176
+ if (billing_item.frequency === "quarterly" /* QAURTERLY */) {
27354
28177
  const monthDiff = todayMonth - billingMonth;
27355
28178
  return monthDiff % 3 === 0 && todayDate === billingDay;
27356
28179
  }
27357
- if (billing_item.frequency === "annual") {
28180
+ if (billing_item.frequency === "annually" /* ANNUALLY */) {
27358
28181
  return todayMonth === billingMonth && todayDate === billingDay;
27359
28182
  }
27360
28183
  return false;
@@ -28150,7 +28973,7 @@ function UseAccessManagementRepo() {
28150
28973
  {
28151
28974
  $match: {
28152
28975
  ...defaultQuery,
28153
- status: { $ne: "deleted" }
28976
+ status: { $eq: "active" }
28154
28977
  }
28155
28978
  },
28156
28979
  // ✅ Only project needed fields before heavy lookups
@@ -28270,7 +29093,7 @@ function UseAccessManagementRepo() {
28270
29093
  userType
28271
29094
  }
28272
29095
  },
28273
- { $project: { _id: 1, userId: 1, type: 1, cardNo: 1, isActivated: 1 } }
29096
+ { $project: { _id: 1, userId: 1, type: 1, cardNo: 1, isActivated: 1, replacementStatus: 1 } }
28274
29097
  ],
28275
29098
  as: "accessCards"
28276
29099
  }
@@ -28289,7 +29112,21 @@ function UseAccessManagementRepo() {
28289
29112
  $filter: {
28290
29113
  input: "$accessCards",
28291
29114
  as: "card",
28292
- cond: { $ne: ["$$card.userId", null] }
29115
+ cond: { $and: [{ $ne: ["$$card.userId", null] }, { $eq: ["$$card.isActivated", true] }] }
29116
+ }
29117
+ },
29118
+ f_replaced: {
29119
+ $filter: {
29120
+ input: "$accessCards",
29121
+ as: "card",
29122
+ cond: { $and: [{ $eq: ["$$card.isActivated", false] }, { $ne: ["$$card.replacementStatus", null] }] }
29123
+ }
29124
+ },
29125
+ f_deleted: {
29126
+ $filter: {
29127
+ input: "$accessCards",
29128
+ as: "card",
29129
+ cond: { $and: [{ $eq: ["$$card.isActivated", false] }, { $eq: ["$$card.replacementStatus", null] }] }
28293
29130
  }
28294
29131
  }
28295
29132
  }
@@ -28321,6 +29158,14 @@ function UseAccessManagementRepo() {
28321
29158
  non_physical: { $size: { $filter: { input: { $ifNull: ["$f_Assigned", []] }, as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } } }
28322
29159
  }
28323
29160
  },
29161
+ replaced: {
29162
+ physical: { $filter: { input: "$f_replaced", as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } },
29163
+ non_physical: { $filter: { input: "$f_replaced", as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } }
29164
+ },
29165
+ deleted: {
29166
+ physical: { $filter: { input: "$f_deleted", as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } },
29167
+ non_physical: { $filter: { input: "$f_deleted", as: "c", cond: { $eq: ["$$c.type", "QRCODE" /* QR */] } } }
29168
+ },
28324
29169
  totalCardCount: {
28325
29170
  $add: [
28326
29171
  { $size: { $filter: { input: { $ifNull: ["$f_Available", []] }, as: "c", cond: { $eq: ["$$c.type", "NFC" /* NFC */] } } } },
@@ -28777,16 +29622,26 @@ function UseAccessManagementRepo() {
28777
29622
  async function cardReplacementRepo(params) {
28778
29623
  const session = useAtlas74.getClient()?.startSession();
28779
29624
  try {
28780
- const { cardId, remarks } = params;
29625
+ const { cardId, remarks, issuedCardId, unitId, userId } = params;
28781
29626
  const id = new ObjectId83(cardId);
29627
+ const newCardId = new ObjectId83(issuedCardId);
29628
+ const unit = new ObjectId83(unitId);
29629
+ const user = new ObjectId83(userId);
28782
29630
  session?.startTransaction();
28783
- const card = await collection().findOneAndUpdate(
28784
- { _id: id },
28785
- { $set: { remarks, replacementStatus: "Pending", requestDate: /* @__PURE__ */ new Date(), isActivated: false } },
28786
- { returnDocument: "after", session }
28787
- );
29631
+ const sessionResult = await Promise.all([
29632
+ await collection().findOneAndUpdate(
29633
+ { _id: id },
29634
+ { $set: { remarks, replacementStatus: "Complete", requestDate: /* @__PURE__ */ new Date(), isActivated: false } },
29635
+ { returnDocument: "after", session }
29636
+ ),
29637
+ await collection().findOneAndUpdate(
29638
+ { _id: newCardId },
29639
+ { $set: { updatedAt: /* @__PURE__ */ new Date(), assignedUnit: unit, userId: user } },
29640
+ { returnDocument: "after", session }
29641
+ )
29642
+ ]);
28788
29643
  await session?.commitTransaction();
28789
- return card;
29644
+ return sessionResult;
28790
29645
  } catch (error) {
28791
29646
  await session?.abortTransaction();
28792
29647
  throw new Error(error.message);
@@ -28804,7 +29659,8 @@ function UseAccessManagementRepo() {
28804
29659
  const query = {
28805
29660
  site: siteId,
28806
29661
  assignedUnit: { $ne: null },
28807
- type: "NFC" /* NFC */
29662
+ type: "NFC" /* NFC */,
29663
+ isActivated: true
28808
29664
  };
28809
29665
  if (search) {
28810
29666
  query.$or = [{ accessLevel: { $regex: search, $options: "i" } }, { cardNo: { $regex: search, $options: "i" } }];
@@ -28812,7 +29668,7 @@ function UseAccessManagementRepo() {
28812
29668
  if (type) {
28813
29669
  query.userType = type;
28814
29670
  } else {
28815
- query.userType = { $ne: "Resident/Tenant" /* RESIDENT */ };
29671
+ query.userType = { $ne: "Visitor/Resident" /* DEFAULT */ };
28816
29672
  }
28817
29673
  const res = await collection().aggregate([
28818
29674
  {
@@ -29035,7 +29891,8 @@ function UseAccessManagementRepo() {
29035
29891
  rawItems.map(async (item) => {
29036
29892
  const date = new Date(item["startDate (format MM/DD/YYYY)"]);
29037
29893
  const endDate = new Date(date.setFullYear(date.getFullYear() + 10));
29038
- const cardNumber = item["cardNo (number 0-65535 ex. 301)"].toString().padStart(10, "0");
29894
+ const cardNumber = String(Number(item["cardNo (number 0-65535 ex. 301)"] || 0)).padStart(6, "0");
29895
+ const facilityCode = String(Number(item["facilityCode (number 0-255 ex. 11)"] || 0)).padStart(4, "0");
29039
29896
  const pin = item["pin (number 6 digits only)"] ? item["pin (number 6 digits only)"].toString().padStart(6, "0") : "123456";
29040
29897
  const match = item["accessLevel (number ex. 1)"];
29041
29898
  const accessLevel = match ? match : null;
@@ -29049,9 +29906,9 @@ function UseAccessManagementRepo() {
29049
29906
  accessLevel,
29050
29907
  accessGroup,
29051
29908
  accessType: "Normal" /* NORMAL */,
29052
- cardNo: cardNumber,
29909
+ cardNo: `${facilityCode}${cardNumber}`,
29053
29910
  pin,
29054
- qrData: await createQrData({ cardNumber }),
29911
+ qrData: await createQrData({ cardNumber: `${facilityCode}${cardNumber}` }),
29055
29912
  startDate: new Date(item["startDate (format MM/DD/YYYY)"]),
29056
29913
  endDate: new Date(item["endDate (format MM/DD/YYYY)"] || endDate),
29057
29914
  isActivated: true,
@@ -29125,6 +29982,112 @@ function UseAccessManagementRepo() {
29125
29982
  session?.endSession();
29126
29983
  }
29127
29984
  }
29985
+ async function deleteCardRepo(params) {
29986
+ try {
29987
+ const { cardId, remarks } = params;
29988
+ const id = new ObjectId83(cardId);
29989
+ const result = await collection().findOneAndUpdate({ _id: id }, { $set: { isActivated: false, updatedAt: /* @__PURE__ */ new Date(), remarks, requestDate: /* @__PURE__ */ new Date() } }, { returnDocument: "after" });
29990
+ return result;
29991
+ } catch (error) {
29992
+ throw new Error(error.message);
29993
+ }
29994
+ }
29995
+ async function getCardDetailsRepo(params) {
29996
+ try {
29997
+ const { siteId, cardId } = params;
29998
+ const convertedSiteId = new ObjectId83(siteId);
29999
+ const convertedCardId = new ObjectId83(cardId);
30000
+ const card = await collection().findOne(
30001
+ {
30002
+ _id: convertedCardId,
30003
+ site: convertedSiteId,
30004
+ type: "NFC" /* NFC */,
30005
+ userType: "Visitor/Resident" /* DEFAULT */
30006
+ },
30007
+ {
30008
+ projection: {
30009
+ userId: 1,
30010
+ site: 1,
30011
+ type: 1,
30012
+ userType: 1,
30013
+ cardNo: 1,
30014
+ isActivated: 1,
30015
+ replacementStatus: 1,
30016
+ vmsRemarks: 1,
30017
+ remarks: 1,
30018
+ requestDate: 1,
30019
+ createdAt: 1,
30020
+ updatedAt: 1
30021
+ }
30022
+ }
30023
+ );
30024
+ if (!card)
30025
+ return null;
30026
+ const site = await collectionName("sites").findOne(
30027
+ { _id: card.site },
30028
+ { projection: { name: 1, status: 1 } }
30029
+ );
30030
+ const user = card.userId ? await collectionName("users").findOne(
30031
+ { _id: card.userId },
30032
+ { projection: { name: 1, email: 1 } }
30033
+ ) : null;
30034
+ const status = card.userId === null && card.isActivated === true ? "available" : card.userId !== null && card.isActivated === true ? "assigned" : card.isActivated === false && card.replacementStatus !== null ? "replaced" : "deleted";
30035
+ return {
30036
+ ...card,
30037
+ status,
30038
+ site,
30039
+ user
30040
+ };
30041
+ } catch (error) {
30042
+ throw new Error(error.message);
30043
+ }
30044
+ }
30045
+ async function addQrTagRepo(params) {
30046
+ try {
30047
+ const { site, payload } = params;
30048
+ const id = new ObjectId83(site);
30049
+ const highestCardNo = await collection().aggregate([
30050
+ { $match: { site: id, type: "NFC" /* NFC */, userType: "Visitor/Resident" /* DEFAULT */ } },
30051
+ {
30052
+ $addFields: {
30053
+ qrTagCardNoNumeric: { $toInt: "$qrTagCardNo" }
30054
+ // Convert string to integer
30055
+ }
30056
+ },
30057
+ {
30058
+ $sort: { qrTagCardNoNumeric: -1 }
30059
+ // Sort in descending order
30060
+ },
30061
+ {
30062
+ $limit: 1
30063
+ // Get the highest
30064
+ }
30065
+ ]).toArray();
30066
+ let start = 0;
30067
+ if (highestCardNo.length > 0) {
30068
+ start = highestCardNo[0].qrTagCardNoNumeric || 0;
30069
+ }
30070
+ const nextCardNumbers = Array.from({ length: payload.length }, (_, i) => String(start + i + 1).padStart(5, "0"));
30071
+ const bulkOps = await Promise.all(
30072
+ payload.map(async (doc, index) => {
30073
+ const id2 = new ObjectId83(doc._id);
30074
+ return {
30075
+ updateOne: {
30076
+ filter: { _id: id2 },
30077
+ update: { $set: { qrTag: doc.qrTag, qrTagCardNo: nextCardNumbers[index] } }
30078
+ }
30079
+ };
30080
+ })
30081
+ );
30082
+ let result;
30083
+ if (bulkOps.length > 0) {
30084
+ result = await collection().bulkWrite(bulkOps);
30085
+ }
30086
+ return result;
30087
+ } catch (error) {
30088
+ throw new Error(error.message);
30089
+ }
30090
+ }
29128
30091
  return {
29129
30092
  createIndexes,
29130
30093
  createIndexForEntrypass,
@@ -29145,7 +30108,10 @@ function UseAccessManagementRepo() {
29145
30108
  getCardReplacementRepo,
29146
30109
  getAccessManagementSettingsRepo,
29147
30110
  bulkPhysicalAccessCardRepo,
29148
- assignAccessCardToUnitRepo
30111
+ assignAccessCardToUnitRepo,
30112
+ deleteCardRepo,
30113
+ getCardDetailsRepo,
30114
+ addQrTagRepo
29149
30115
  };
29150
30116
  }
29151
30117
 
@@ -29177,7 +30143,10 @@ function useAccessManagementSvc() {
29177
30143
  getCardReplacementRepo,
29178
30144
  getAccessManagementSettingsRepo,
29179
30145
  bulkPhysicalAccessCardRepo,
29180
- assignAccessCardToUnitRepo
30146
+ assignAccessCardToUnitRepo,
30147
+ deleteCardRepo,
30148
+ getCardDetailsRepo,
30149
+ addQrTagRepo
29181
30150
  } = UseAccessManagementRepo();
29182
30151
  const addPhysicalCardSvc = async (payload) => {
29183
30152
  try {
@@ -29425,6 +30394,30 @@ function useAccessManagementSvc() {
29425
30394
  throw new Error(err.message);
29426
30395
  }
29427
30396
  };
30397
+ const deleteCardSvc = async (params) => {
30398
+ try {
30399
+ const response = await deleteCardRepo({ ...params });
30400
+ return response;
30401
+ } catch (err) {
30402
+ throw new Error(err.message);
30403
+ }
30404
+ };
30405
+ const getCardDetailsSvc = async (params) => {
30406
+ try {
30407
+ const response = await getCardDetailsRepo({ ...params });
30408
+ return response;
30409
+ } catch (err) {
30410
+ throw new Error(err.message);
30411
+ }
30412
+ };
30413
+ const addQrTagSvc = async (params) => {
30414
+ try {
30415
+ const response = await addQrTagRepo({ ...params });
30416
+ return response;
30417
+ } catch (err) {
30418
+ throw new Error(err.message);
30419
+ }
30420
+ };
29428
30421
  return {
29429
30422
  addPhysicalCardSvc,
29430
30423
  addNonPhysicalCardSvc,
@@ -29449,7 +30442,10 @@ function useAccessManagementSvc() {
29449
30442
  getAccessManagementSettingsSvc,
29450
30443
  convertBufferFile,
29451
30444
  bulkPhysicalAccessCardSvc,
29452
- assignAccessCardToUnitSvc
30445
+ assignAccessCardToUnitSvc,
30446
+ deleteCardSvc,
30447
+ getCardDetailsSvc,
30448
+ addQrTagSvc
29453
30449
  };
29454
30450
  }
29455
30451
 
@@ -29481,7 +30477,10 @@ function useAccessManagementController() {
29481
30477
  getAccessManagementSettingsSvc,
29482
30478
  convertBufferFile,
29483
30479
  bulkPhysicalAccessCardSvc,
29484
- assignAccessCardToUnitSvc
30480
+ assignAccessCardToUnitSvc,
30481
+ deleteCardSvc,
30482
+ getCardDetailsSvc,
30483
+ addQrTagSvc
29485
30484
  } = useAccessManagementSvc();
29486
30485
  const addPhysicalCard = async (req, res) => {
29487
30486
  try {
@@ -29710,11 +30709,24 @@ function useAccessManagementController() {
29710
30709
  userType: Joi85.string().optional().allow("", null),
29711
30710
  type: Joi85.string().optional().allow("", null)
29712
30711
  });
30712
+ const user = req.cookies?.sid;
29713
30713
  const { error } = schema2.validate({ site, userType, type });
29714
30714
  if (error) {
29715
30715
  return res.status(400).json({ message: error.message });
29716
30716
  }
30717
+ const key = `${namespace2}:${user}:available-access-cards`;
30718
+ const listKey = `${namespace2}:${user}:list`;
30719
+ const { redis } = useCache48(key);
30720
+ const cachedData = await getCache({ key, redis });
30721
+ if (cachedData) {
30722
+ console.log("\u26A1 Cache hit:", key);
30723
+ redis.expire(key, 60).catch(console.error);
30724
+ redis.lrem(listKey, 0, key).then(() => redis.lpush(listKey, key)).then(() => redis.ltrim(listKey, 0, 9)).catch(console.error);
30725
+ return res.status(200).json({ message: "Success", data: cachedData });
30726
+ }
29717
30727
  const result = await availableAccessCardsSvc({ site, userType, type });
30728
+ await setCache({ key, data: result, ttlSeconds: 60, redis });
30729
+ redis.lrem(listKey, 0, key).then(() => redis.lpush(listKey, key)).then(() => redis.ltrim(listKey, 0, 9)).catch(console.error);
29718
30730
  return res.status(200).json({ message: "Success", data: result });
29719
30731
  } catch (error) {
29720
30732
  return res.status(400).json({
@@ -29896,16 +30908,19 @@ function useAccessManagementController() {
29896
30908
  };
29897
30909
  const cardReplacement = async (req, res) => {
29898
30910
  try {
29899
- const { cardId, remarks } = req.body;
30911
+ const { cardId, remarks, unitId, issuedCardId, userId } = req.body;
29900
30912
  const schema2 = Joi85.object({
29901
30913
  cardId: Joi85.string().required(),
29902
- remarks: Joi85.string().optional().allow("", null)
30914
+ remarks: Joi85.string().optional().allow("", null),
30915
+ unitId: Joi85.string().hex().required(),
30916
+ issuedCardId: Joi85.string().required(),
30917
+ userId: Joi85.string().hex().required()
29903
30918
  });
29904
- const { error } = schema2.validate({ cardId, remarks });
30919
+ const { error } = schema2.validate({ cardId, remarks, unitId, issuedCardId, userId });
29905
30920
  if (error) {
29906
30921
  return res.status(400).json({ message: error.message });
29907
30922
  }
29908
- const result = await cardReplacementSvc({ cardId, remarks });
30923
+ const result = await cardReplacementSvc({ cardId, remarks, unitId, issuedCardId, userId });
29909
30924
  return res.status(200).json({ message: "Success", data: result });
29910
30925
  } catch (error) {
29911
30926
  return res.status(500).json({
@@ -30036,6 +31051,74 @@ function useAccessManagementController() {
30036
31051
  });
30037
31052
  }
30038
31053
  };
31054
+ const deleteCard = async (req, res) => {
31055
+ try {
31056
+ const { cardId, remarks } = req.body;
31057
+ const schema2 = Joi85.object({
31058
+ cardId: Joi85.string().hex().required(),
31059
+ remarks: Joi85.string().optional().allow("", null)
31060
+ });
31061
+ const { error } = schema2.validate({ cardId, remarks });
31062
+ if (error) {
31063
+ return res.status(400).json({ message: error.message });
31064
+ }
31065
+ const result = await deleteCardSvc({ cardId, remarks });
31066
+ return res.status(200).json({ message: "Success", data: result });
31067
+ } catch (error) {
31068
+ return res.status(500).json({
31069
+ data: null,
31070
+ message: error.message
31071
+ });
31072
+ }
31073
+ };
31074
+ const getCardDetails = async (req, res) => {
31075
+ try {
31076
+ const { siteId, cardId } = req.query;
31077
+ const schema2 = Joi85.object({
31078
+ siteId: Joi85.string().hex().required(),
31079
+ cardId: Joi85.string().hex().required()
31080
+ });
31081
+ const { error } = schema2.validate({ siteId, cardId });
31082
+ if (error) {
31083
+ return res.status(400).json({ message: error.message });
31084
+ }
31085
+ const result = await getCardDetailsSvc({ siteId, cardId });
31086
+ return res.status(200).json({ message: "Success", data: result });
31087
+ } catch (error) {
31088
+ return res.status(500).json({
31089
+ data: null,
31090
+ message: error.message
31091
+ });
31092
+ }
31093
+ };
31094
+ const addQrTag = async (req, res) => {
31095
+ try {
31096
+ const payload = req.body;
31097
+ const site = req.params.site;
31098
+ const schema2 = Joi85.object({
31099
+ site: Joi85.string().hex().required(),
31100
+ payload: Joi85.array().items(
31101
+ Joi85.object({
31102
+ _id: Joi85.string().hex().required(),
31103
+ cardNo: Joi85.string().required(),
31104
+ qrTag: Joi85.string().required(),
31105
+ qrTagCardNo: Joi85.string().required()
31106
+ })
31107
+ ).required()
31108
+ });
31109
+ const { error } = schema2.validate({ site, payload });
31110
+ if (error) {
31111
+ return res.status(400).json({ message: error.message });
31112
+ }
31113
+ const result = await addQrTagSvc({ payload, site });
31114
+ return res.status(200).json({ message: "Success", data: result });
31115
+ } catch (error) {
31116
+ return res.status(500).json({
31117
+ data: null,
31118
+ message: error.message
31119
+ });
31120
+ }
31121
+ };
30039
31122
  return {
30040
31123
  addPhysicalCard,
30041
31124
  addNonPhysicalCard,
@@ -30057,7 +31140,10 @@ function useAccessManagementController() {
30057
31140
  getCardReplacement,
30058
31141
  getAccessManagementSettings,
30059
31142
  bulkPhysicalAccessCard,
30060
- assignAccessCardToUnit
31143
+ assignAccessCardToUnit,
31144
+ deleteCard,
31145
+ getCardDetails,
31146
+ addQrTag
30061
31147
  };
30062
31148
  }
30063
31149
 
@@ -31809,9 +32895,9 @@ function useStatementOfAccountRepo() {
31809
32895
  page = page > 0 ? page - 1 : 0;
31810
32896
  let dateExpr = {};
31811
32897
  if (dateFrom && dateTo) {
31812
- const startDate = new Date(dateFrom);
32898
+ let startDate = new Date(dateFrom);
31813
32899
  startDate.setHours(0, 0, 0, 0);
31814
- const endDate = new Date(dateTo);
32900
+ let endDate = new Date(dateTo);
31815
32901
  endDate.setHours(23, 59, 59, 999);
31816
32902
  dateExpr = {
31817
32903
  $expr: {
@@ -31822,13 +32908,15 @@ function useStatementOfAccountRepo() {
31822
32908
  }
31823
32909
  };
31824
32910
  }
32911
+ const unitSearchRegex = search ? search.trim().replace(/\s+/g, "").replace(/\//g, "\\s*/\\s*") : null;
31825
32912
  const query = {
31826
32913
  ...status && status !== "all" && { status },
31827
32914
  ...search && {
31828
32915
  $or: [
31829
32916
  { unitOwner: { $regex: search, $options: "i" } },
31830
- { unit: { $regex: search, $options: "i" } },
31831
- { email: { $regex: search, $options: "i" } }
32917
+ { unit: { $regex: unitSearchRegex, $options: "i" } },
32918
+ { email: { $regex: search, $options: "i" } },
32919
+ { category: { $regex: search, $options: "i" } }
31832
32920
  ]
31833
32921
  },
31834
32922
  ...ObjectId91.isValid(site) && { site: new ObjectId91(site) },
@@ -34122,6 +35210,9 @@ function useIncidentReportRepo() {
34122
35210
  $regex: search,
34123
35211
  $options: "i"
34124
35212
  }
35213
+ },
35214
+ {
35215
+ approvedByName: { $regex: search, $options: "i" }
34125
35216
  }
34126
35217
  ]
34127
35218
  },
@@ -36677,7 +37768,9 @@ function useNfcPatrolLogController() {
36677
37768
  };
36678
37769
  }
36679
37770
  export {
37771
+ ANPRMode,
36680
37772
  AccessTypeProps,
37773
+ BULLETIN_RECIPIENTS,
36681
37774
  DEVICE_STATUS,
36682
37775
  EAccessCardTypes,
36683
37776
  EAccessCardUserTypes,
@@ -36736,12 +37829,15 @@ export {
36736
37829
  MVerification,
36737
37830
  MVisitorTransaction,
36738
37831
  MWorkOrder,
37832
+ OrgNature,
36739
37833
  PERSON_TYPES,
37834
+ STATUS_VALUES,
36740
37835
  UseAccessManagementRepo,
36741
- allowedCategories,
37836
+ VehicleCategory,
37837
+ VehicleStatus,
37838
+ VehicleType,
36742
37839
  allowedFieldsSite,
36743
37840
  allowedNatures,
36744
- allowedTypes,
36745
37841
  attendanceSchema,
36746
37842
  attendanceSettingsSchema,
36747
37843
  chatSchema,