@7365admin1/core 3.36.0 → 3.38.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
@@ -24955,6 +24955,16 @@ import {
24955
24955
  toObjectId as toObjectId10
24956
24956
  } from "@7365admin1/node-server-utils";
24957
24957
  import { ObjectId as ObjectId45 } from "mongodb";
24958
+
24959
+ // src/utils/sanitizer.ts
24960
+ var makeContainsRegex = (input) => {
24961
+ if (!input)
24962
+ return "";
24963
+ const sanitized = input.replace(/\s+/g, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24964
+ return sanitized;
24965
+ };
24966
+
24967
+ // src/repositories/person.repo.ts
24958
24968
  var site_people_namespace_collection = "site.people";
24959
24969
  function usePersonRepo() {
24960
24970
  const db = useAtlas33.getDb();
@@ -25027,7 +25037,10 @@ function usePersonRepo() {
25027
25037
  dateTo,
25028
25038
  org = "",
25029
25039
  site = "",
25030
- type = ""
25040
+ type = "",
25041
+ nric = "",
25042
+ contact = "",
25043
+ plateNumber = ""
25031
25044
  }, session) {
25032
25045
  page = page > 0 ? page - 1 : 0;
25033
25046
  const start = dateFrom ? { $gte: new Date(dateFrom).toISOString() } : void 0;
@@ -25046,12 +25059,28 @@ function usePersonRepo() {
25046
25059
  { name: { $regex: search, $options: "i" } },
25047
25060
  { email: { $regex: search, $options: "i" } },
25048
25061
  { nric: { $regex: search, $options: "i" } },
25049
- { "plates.plateNumber": { $regex: search, $options: "i" } }
25062
+ { "plates.plateNumber": { $regex: search, $options: "i" } },
25063
+ { contact: { $regex: makeContainsRegex(contact) } },
25064
+ { contacts: { $regex: makeContainsRegex(contact) } },
25065
+ { plateNumbers: { $regex: makeContainsRegex(plateNumber) } }
25050
25066
  ]
25051
25067
  },
25052
25068
  ...ObjectId45.isValid(org) && { org: new ObjectId45(org) },
25053
25069
  ...ObjectId45.isValid(site) && { site: new ObjectId45(site) },
25054
- ...PERSON_TYPES.includes(type) && { type }
25070
+ ...PERSON_TYPES.includes(type) && { type },
25071
+ ...nric && { nric: { $regex: makeContainsRegex(nric), $options: "i" } },
25072
+ ...contact && {
25073
+ $or: [
25074
+ { contact: { $regex: makeContainsRegex(contact) } },
25075
+ { contacts: { $regex: makeContainsRegex(contact) } }
25076
+ ]
25077
+ },
25078
+ ...plateNumber && {
25079
+ $or: [
25080
+ { "plates.plateNumber": { $regex: makeContainsRegex(plateNumber) } },
25081
+ { plateNumbers: { $regex: makeContainsRegex(plateNumber) } }
25082
+ ]
25083
+ }
25055
25084
  };
25056
25085
  sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
25057
25086
  try {
@@ -25129,6 +25158,9 @@ function usePersonRepo() {
25129
25158
  throw new BadRequestError72("Invalid person transaction ID format.");
25130
25159
  }
25131
25160
  value.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
25161
+ if (value.updatedBy) {
25162
+ value.updatedBy = await convertObjectIdUtil2(value.updatedBy, "updatedBy");
25163
+ }
25132
25164
  try {
25133
25165
  const result = await collection.updateOne(
25134
25166
  { _id },
@@ -25637,6 +25669,31 @@ function usePersonRepo() {
25637
25669
  throw error;
25638
25670
  }
25639
25671
  }
25672
+ async function findFirstTen(query, session) {
25673
+ try {
25674
+ query.site = await convertObjectIdUtil2(query.site, "site");
25675
+ const data = await collection.find(query, { session }).limit(10).toArray();
25676
+ return data;
25677
+ } catch (error) {
25678
+ throw new InternalServerError26("Failed to find persons" + error);
25679
+ }
25680
+ }
25681
+ async function addFromVisitor(value, session) {
25682
+ value.site = await convertObjectIdUtil2(value.site, "site");
25683
+ if (value.updatedBy) {
25684
+ value.updatedBy = await convertObjectIdUtil2(value.updatedBy, "updatedBy");
25685
+ }
25686
+ if (value.createdBy) {
25687
+ value.createdBy = await convertObjectIdUtil2(value.createdBy, "createdBy");
25688
+ }
25689
+ const res = await collection.insertOne({
25690
+ ...value,
25691
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
25692
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
25693
+ }, { session });
25694
+ console.log("person.repo addFromVisitor res", res);
25695
+ return res;
25696
+ }
25640
25697
  return {
25641
25698
  add,
25642
25699
  getAll,
@@ -25656,7 +25713,9 @@ function usePersonRepo() {
25656
25713
  pushVehicleById,
25657
25714
  pullVehicleByRecNo,
25658
25715
  getByUserId,
25659
- reviewResidentPerson
25716
+ reviewResidentPerson,
25717
+ addFromVisitor,
25718
+ findFirstTen
25660
25719
  };
25661
25720
  }
25662
25721
 
@@ -26830,8 +26889,7 @@ function useVehicleService() {
26830
26889
  throw new Error("Unable to start session for vehicle service.");
26831
26890
  }
26832
26891
  }
26833
- const plateNumbers = Array.isArray(value.plateNumber) ? value.plateNumber : value.plateNumber ? [value.plateNumber] : [];
26834
- const [_vehiclePlateNumber] = plateNumbers;
26892
+ const [_vehiclePlateNumber] = value.plateNumber;
26835
26893
  const [existingPlateNumber, org] = await Promise.all([
26836
26894
  _getVehicleByPlateNumber(_vehiclePlateNumber),
26837
26895
  _getById(value.org)
@@ -26896,6 +26954,7 @@ function useVehicleService() {
26896
26954
  }
26897
26955
  }
26898
26956
  const owner = value.name;
26957
+ const plateNumbers = value.plateNumber;
26899
26958
  try {
26900
26959
  if (!isExternalSession) {
26901
26960
  session.startTransaction();
@@ -27112,10 +27171,7 @@ function useVehicleService() {
27112
27171
  const dahuaResponse = await _addPlateNumber(dahuaPayload);
27113
27172
  if (dahuaResponse?.statusCode != 200) {
27114
27173
  console.log("dahuaResponse", dahuaResponse);
27115
- console.log(
27116
- "approveVehicleById dahuaResponse dahuaResponse?.statusCode != 200",
27117
- dahuaResponse?.statusCode
27118
- );
27174
+ console.log("approveVehicleById dahuaResponse dahuaResponse?.statusCode != 200", dahuaResponse?.statusCode);
27119
27175
  throw new BadRequestError75("Failed to add plate number to ANPR");
27120
27176
  }
27121
27177
  const responseData = dahuaResponse?.data.toString("utf-8");
@@ -27322,19 +27378,7 @@ function useVehicleService() {
27322
27378
  if (value.peopleId) {
27323
27379
  value.peopleId = new ObjectId49(value.peopleId);
27324
27380
  }
27325
- const {
27326
- name,
27327
- plateNumber,
27328
- start,
27329
- end,
27330
- recNo,
27331
- type,
27332
- unit,
27333
- site,
27334
- block,
27335
- level,
27336
- ...rest
27337
- } = value;
27381
+ const { name, plateNumber, start, end, recNo, type, unit, site, block, level, ...rest } = value;
27338
27382
  if (_name != name || _plateNumber != plateNumber) {
27339
27383
  const startDahua = value.start ? formatDahuaDate(new Date(value.start)) : formatDahuaDate(new Date(_start));
27340
27384
  const endDahua = value.end ? formatDahuaDate(new Date(value.end)) : formatDahuaDate(new Date(_end));
@@ -27361,11 +27405,7 @@ function useVehicleService() {
27361
27405
  for (const camera of siteCameras) {
27362
27406
  const { host, username, password } = camera;
27363
27407
  if (_plateNumber != plateNumber) {
27364
- console.log(
27365
- "updateVehicleById _plateNumber != plateNumber",
27366
- _plateNumber,
27367
- plateNumber
27368
- );
27408
+ console.log("updateVehicleById _plateNumber != plateNumber", _plateNumber, plateNumber);
27369
27409
  const removePlateNumber = {
27370
27410
  host,
27371
27411
  username,
@@ -27412,11 +27452,7 @@ function useVehicleService() {
27412
27452
  );
27413
27453
  }
27414
27454
  } else {
27415
- console.log(
27416
- "updateVehicleById _plateNumber == plateNumber",
27417
- _plateNumber,
27418
- plateNumber
27419
- );
27455
+ console.log("updateVehicleById _plateNumber == plateNumber", _plateNumber, plateNumber);
27420
27456
  const dahuaPayload = {
27421
27457
  host,
27422
27458
  username,
@@ -33593,294 +33629,856 @@ function useAttendanceController() {
33593
33629
  }
33594
33630
 
33595
33631
  // src/services/visitor-transaction.service.ts
33632
+ import {
33633
+ BadRequestError as BadRequestError101,
33634
+ logger as logger81,
33635
+ useAtlas as useAtlas52,
33636
+ useMailer as useMailer3
33637
+ } from "@7365admin1/node-server-utils";
33638
+ import { ObjectId as ObjectId64 } from "mongodb";
33639
+
33640
+ // src/services/person.service.ts
33596
33641
  import {
33597
33642
  BadRequestError as BadRequestError100,
33598
33643
  logger as logger80,
33599
- useAtlas as useAtlas51,
33600
- useMailer as useMailer2
33644
+ NotFoundError as NotFoundError23,
33645
+ useAtlas as useAtlas50,
33646
+ hashPassword as hashPassword3,
33647
+ useMailer as useMailer2,
33648
+ compileHandlebar as compileHandlebar2,
33649
+ getDirectory as getDirectory2
33601
33650
  } from "@7365admin1/node-server-utils";
33602
- import { ObjectId as ObjectId63 } from "mongodb";
33603
-
33604
- // src/repositories/key.repo.ts
33605
- import { ObjectId as ObjectId62 } from "mongodb";
33606
-
33607
- // src/utils/mongo.ts
33608
- import { useAtlas as useAtlas50 } from "@7365admin1/node-server-utils";
33609
- var getDB = () => {
33610
- const db = useAtlas50.getDb();
33611
- if (!db) {
33612
- throw new Error("Database not initialized! Check if useAtlas.initialize finished.");
33613
- }
33614
- return db;
33615
- };
33616
-
33617
- // src/models/key.model.ts
33618
33651
  import { ObjectId as ObjectId61 } from "mongodb";
33619
- var MKeyLog = class {
33620
- constructor({
33621
- _id = new ObjectId61(),
33622
- name = "",
33623
- codes = [],
33624
- passType = "",
33625
- passId = "",
33626
- code = "",
33627
- // isTaken = false,
33628
- site = "",
33629
- organization = "",
33630
- serviceProvider = "",
33631
- createdAt = /* @__PURE__ */ new Date(),
33632
- updatedAt = /* @__PURE__ */ new Date(),
33633
- template,
33634
- status = "Available",
33635
- updatedBy = "",
33636
- qty,
33637
- description,
33638
- location,
33639
- prefix,
33640
- keyNo,
33641
- parentId
33642
- } = {}) {
33643
- this._id = _id;
33644
- this.name = name;
33645
- this.codes = codes;
33646
- this.passType = passType;
33647
- this.passId = passId;
33648
- this.code = code;
33649
- this.site = site;
33650
- this.organization = organization;
33651
- this.serviceProvider = serviceProvider;
33652
- this.createdAt = createdAt;
33653
- this.updatedAt = updatedAt;
33654
- this.template = template;
33655
- this.status = status;
33656
- this.updatedBy = updatedBy;
33657
- this.qty = qty;
33658
- this.description = description;
33659
- this.location = location;
33660
- this.prefix = prefix;
33661
- this.parentId = parentId;
33662
- this.keyNo = keyNo;
33663
- }
33664
- };
33665
-
33666
- // src/utils/pagination.util.ts
33667
- function paginationUtil(items, page, limit, length) {
33668
- const startIndex = length ? page * limit + 1 : 0;
33669
- const endIndex = Math.min(startIndex + limit - 1, length);
33670
- return {
33671
- items,
33672
- pages: Math.ceil(length / limit),
33673
- pageRange: `${startIndex}-${endIndex} of ${length}`
33652
+ function usePersonService() {
33653
+ const MailerConfig = {
33654
+ host: MAILER_TRANSPORT_HOST,
33655
+ port: MAILER_TRANSPORT_PORT,
33656
+ secure: MAILER_TRANSPORT_SECURE,
33657
+ email: MAILER_EMAIL,
33658
+ from: MAILER_FROM_EMAIL,
33659
+ password: MAILER_PASSWORD
33674
33660
  };
33675
- }
33676
-
33677
- // src/repositories/key.repo.ts
33678
- var KeyRepo = class {
33679
- static collection() {
33680
- return getDB().collection("keys");
33681
- }
33682
- static collectionDynamic(collection) {
33683
- return getDB().collection(collection);
33684
- }
33685
- static async getNextKeyNo(site) {
33686
- try {
33687
- site = await convertObjectIdUtil(site);
33688
- return await this.collection().countDocuments({
33689
- site,
33690
- passType: "pass-key",
33691
- parentId: { $not: { $type: "objectId" } }
33692
- });
33693
- } catch (error) {
33694
- return Promise.reject("Server internal error. " + error);
33661
+ const mailer = new useMailer2(MailerConfig);
33662
+ const {
33663
+ add: _add,
33664
+ getById: _getById,
33665
+ updateById: _updateById,
33666
+ getByUserId: _getByUserId,
33667
+ reviewResidentPerson: _reviewResidentPerson,
33668
+ addFromVisitor: _addFromVisitor,
33669
+ findFirstTen,
33670
+ getApprovedPersonByPlateNumber
33671
+ } = usePersonRepo();
33672
+ const { addPlateNumber: _addPlateNumber } = useDahuaService();
33673
+ const {
33674
+ createUser: addUser,
33675
+ getUserByEmail,
33676
+ updateUserFieldById: _updateUserFieldById,
33677
+ getUserById,
33678
+ updateUserOrgById: _updateUserOrgById,
33679
+ updateUserUnitById: _updateUserUnitById
33680
+ } = useUserRepo();
33681
+ const { add: addMember } = useMemberRepo();
33682
+ const { getById: _getUnitById, updateById: updateUnitById } = useBuildingUnitRepo();
33683
+ const { getAllCameraWithPassword: _getAllSiteCameras, getCamerasBySiteId } = useSiteCameraRepo();
33684
+ const { updateStatusById } = useFileRepo();
33685
+ const { getById: getOrgById } = useOrgRepo();
33686
+ const { getSiteById } = useSiteRepo();
33687
+ const { add: addVehicle } = useVehicleService();
33688
+ async function add(value) {
33689
+ const session = useAtlas50.getClient()?.startSession();
33690
+ if (!session) {
33691
+ throw new Error("Unable to start session for people service.");
33695
33692
  }
33696
- }
33697
- static async getKeyWithSequenceNumber(site, _id) {
33698
33693
  try {
33699
- site = await convertObjectIdUtil(site);
33700
- const results = await this.collection().find({
33701
- site,
33702
- passType: "pass-key",
33703
- parentId: { $not: { $type: "objectId" } }
33704
- }).sort({ createdAt: 1 }).toArray();
33705
- for (let i = 0; i < results.length; i++) {
33706
- if (results[i]._id.toString() == _id) {
33707
- results[i].keyNo = (i + 1).toString().padStart(2, "0");
33708
- return results[i];
33694
+ session.startTransaction();
33695
+ const personFiles = value?.files ?? [];
33696
+ if (personFiles.length > 0) {
33697
+ for (const personFile of personFiles) {
33698
+ const file = await updateStatusById(
33699
+ personFile.id,
33700
+ { status: "active" },
33701
+ session
33702
+ );
33703
+ if (!file) {
33704
+ throw new NotFoundError23("File not found.");
33705
+ }
33709
33706
  }
33710
33707
  }
33711
- return Promise.reject("Key Not Found");
33712
- } catch (error) {
33713
- return Promise.reject("Server internal error. " + error);
33714
- }
33715
- }
33716
- static async countByTemplateId(template, passType, site) {
33717
- try {
33718
- template = await convertObjectIdUtil(template);
33719
- site = await convertObjectIdUtil(site);
33720
- let passTypeQuery = { passType };
33721
- if (passType != "pass-key")
33722
- passTypeQuery = {
33723
- passType: { $in: ["visitor-pass", "contractor-pass", "agent-pass"] }
33724
- };
33725
- const query = [
33726
- { $match: { template, ...passTypeQuery, site } },
33727
- { $addFields: { nameAsNumber: { $toDouble: "$name" } } },
33728
- { $sort: { nameAsNumber: -1 } },
33729
- { $limit: 1 },
33730
- { $project: { nameAsNumber: 1 } }
33731
- ];
33732
- const result = await this.collection().aggregate(query).toArray();
33733
- const highestName = result.length > 0 && result[0].nameAsNumber > 0 ? result[0].nameAsNumber : 0;
33734
- return highestName;
33735
- } catch (error) {
33736
- return Promise.reject("Server internal error. " + error);
33737
- }
33738
- }
33739
- static async createIndexes() {
33740
- try {
33741
- await this.collection().createIndex(
33742
- { passType: "text", name: "text" },
33743
- { name: "key_search" }
33708
+ const isValidType = ["resident" /* RESIDENT */, "tenant" /* TENANT */].includes(
33709
+ value?.type
33744
33710
  );
33745
- return Promise.resolve("User indexes created.");
33746
- } catch (error) {
33747
- return Promise.reject("Failed to create user indexes.");
33748
- }
33749
- }
33750
- static async createKeyLog(keyLog, session) {
33751
- try {
33752
- if (keyLog.site)
33753
- keyLog.site = await convertObjectIdUtil(keyLog.site);
33754
- if (keyLog.parentId)
33755
- keyLog.parentId = await convertObjectIdUtil(keyLog.parentId);
33756
- if (keyLog.organization)
33757
- keyLog.organization = await convertObjectIdUtil(keyLog.organization);
33758
- if (keyLog.serviceProvider)
33759
- keyLog.serviceProvider = await convertObjectIdUtil(
33760
- keyLog.serviceProvider
33761
- );
33762
- if (keyLog.template)
33763
- keyLog.template = await convertObjectIdUtil(keyLog.template);
33764
- if (keyLog.updatedBy)
33765
- keyLog.updatedBy = await convertObjectIdUtil(keyLog.updatedBy);
33766
- return await this.collection().insertOne(new MKeyLog(keyLog), {
33767
- session
33768
- });
33711
+ if (value.email && value.password && value.name && isValidType) {
33712
+ const _user = await getUserByEmail(value.email);
33713
+ if (_user) {
33714
+ throw new BadRequestError100(`User already exists: ${value.email}.`);
33715
+ }
33716
+ const hashedPassword = await hashPassword3(value.password);
33717
+ const user = {
33718
+ email: value.email,
33719
+ password: hashedPassword,
33720
+ name: value.name,
33721
+ status: value.platform == "mobile" ? "pending" : "active",
33722
+ defaultOrg: value.org?.toString() || "",
33723
+ block: value.block,
33724
+ level: value.level,
33725
+ type: value.type,
33726
+ unitName: value.unitName,
33727
+ contact: value.contact,
33728
+ site: value.site?.toString() || "",
33729
+ unitId: value.unit?.toString() || ""
33730
+ };
33731
+ const userId = await addUser(user, session);
33732
+ value.user = userId.toString();
33733
+ let org = null;
33734
+ if (userId) {
33735
+ if (value?.org) {
33736
+ org = await getOrgById(value.org);
33737
+ if (!org) {
33738
+ value.org = "";
33739
+ }
33740
+ }
33741
+ let site;
33742
+ if (value.site) {
33743
+ site = await getSiteById(value.site);
33744
+ }
33745
+ await addMember(
33746
+ {
33747
+ org: value.org?.toString() || "",
33748
+ orgName: org?.name || "",
33749
+ user: userId.toString(),
33750
+ name: value.name,
33751
+ role: "",
33752
+ type: "resident",
33753
+ siteId: value.site?.toString() || "",
33754
+ siteName: site?.name || ""
33755
+ },
33756
+ session
33757
+ );
33758
+ }
33759
+ }
33760
+ if (value.isOwner && value.unit) {
33761
+ const unit = await _getUnitById(value.unit.toString());
33762
+ await updateUnitById(
33763
+ unit?._id?.toString() || "",
33764
+ { ownerName: value.name, owner: value._id },
33765
+ session
33766
+ );
33767
+ }
33768
+ await _add(value, session);
33769
+ await session.commitTransaction();
33770
+ if (value.email && isValidType && value.platform === "mobile") {
33771
+ const emailContent = compileHandlebar2({
33772
+ context: { name: value.name },
33773
+ filePath: getDirectory2(
33774
+ __dirname,
33775
+ "./public/handlebars/registration-pending"
33776
+ )
33777
+ });
33778
+ await mailer.sendMail({
33779
+ to: value.email,
33780
+ subject: "Registration Received \u2013 Pending Review",
33781
+ html: emailContent,
33782
+ sender: "iService365"
33783
+ }).catch((error) => {
33784
+ logger80.error(`Failed to send registration pending email: ${error}`);
33785
+ });
33786
+ }
33787
+ return "People added successfully.";
33769
33788
  } catch (error) {
33770
- return Promise.reject(error || error.message || "Server internal error.");
33789
+ logger80.error("Error in people service add:", error);
33790
+ await session.abortTransaction();
33791
+ throw error;
33792
+ } finally {
33793
+ session.endSession();
33771
33794
  }
33772
33795
  }
33773
- static async getByPageSearch2(query, pagination, search, prefixAndName, sortBy) {
33796
+ async function updateById(_id, value) {
33797
+ const session = useAtlas50.getClient()?.startSession();
33798
+ if (!session) {
33799
+ throw new BadRequestError100("Database session not available.");
33800
+ }
33774
33801
  try {
33775
- let { page, limit } = pagination;
33776
- page = page ? page - 1 : 0;
33777
- limit = limit ? limit : 10;
33778
- const pipeline = [
33779
- {
33780
- $match: query
33781
- },
33782
- {
33783
- $lookup: {
33784
- from: "qr-code-templates",
33785
- localField: "template",
33786
- foreignField: "_id",
33787
- as: "QRTemplateInfo"
33788
- }
33789
- },
33790
- {
33791
- $unwind: {
33792
- path: "$QRTemplateInfo",
33793
- preserveNullAndEmptyArrays: true
33794
- }
33795
- },
33796
- {
33797
- $addFields: {
33798
- prefixPass: { $toUpper: "$QRTemplateInfo.prefixPass" },
33799
- prefixKey: { $toUpper: "$QRTemplateInfo.prefixKey" },
33800
- templateName: "$QRTemplateInfo.name"
33801
- }
33802
- },
33803
- {
33804
- $addFields: {
33805
- prefixAndName: {
33806
- $cond: {
33807
- if: { $or: [{ $not: "$prefixPass" }, { $not: "$prefixKey" }] },
33808
- then: "$name",
33809
- else: {
33810
- $cond: {
33811
- if: { $eq: ["$passType", "pass-key"] },
33812
- then: { $concat: ["$prefixKey", "$name"] },
33813
- else: { $concat: ["$prefixPass", "$name"] }
33814
- }
33815
- }
33816
- }
33817
- }
33802
+ await session.startTransaction();
33803
+ const person = await _getById(_id.toString());
33804
+ if (!person) {
33805
+ throw new BadRequestError100("Person not found.");
33806
+ }
33807
+ const isNameUpdated = typeof value.name === "string" && value.name.trim() !== person.name;
33808
+ const isOwnerChanged = value.isOwner !== person.isOwner;
33809
+ if (value.org && typeof value.org === "string") {
33810
+ value.org = new ObjectId61(value.org);
33811
+ }
33812
+ if (value.site && typeof value.site === "string") {
33813
+ value.site = new ObjectId61(value.site);
33814
+ }
33815
+ if (value.block && typeof value.block === "string") {
33816
+ value.block = new ObjectId61(value.block);
33817
+ }
33818
+ if (value.level && typeof value.level === "string") {
33819
+ value.level = new ObjectId61(value.level);
33820
+ }
33821
+ if (value.unit && typeof value.unit === "string") {
33822
+ value.unit = new ObjectId61(value.unit);
33823
+ }
33824
+ const isOrgChanged = value.org && person.org?.toString() !== value.org.toString();
33825
+ const toKey = (v) => v === null || v === void 0 || v === "" ? "" : v.toString();
33826
+ const isBlockChanged = "block" in value && toKey(value.block) !== toKey(person.block);
33827
+ const isLevelChanged = "level" in value && toKey(value.level) !== toKey(person.level);
33828
+ const isUnitChanged = "unit" in value && toKey(value.unit) !== toKey(person.unit);
33829
+ await _updateById(_id, value, session);
33830
+ if (isOrgChanged && person.user) {
33831
+ await _updateUserOrgById(
33832
+ person.user.toString(),
33833
+ value.org,
33834
+ session
33835
+ );
33836
+ }
33837
+ if ((isBlockChanged || isLevelChanged || isUnitChanged) && person.user) {
33838
+ const userUnitPayload = {};
33839
+ if (isBlockChanged) {
33840
+ userUnitPayload.block = value.block ?? null;
33841
+ }
33842
+ if (isLevelChanged) {
33843
+ userUnitPayload.level = value.level ?? null;
33844
+ }
33845
+ if (isUnitChanged) {
33846
+ userUnitPayload.unitId = value.unit ?? null;
33847
+ if ("unitName" in value) {
33848
+ userUnitPayload.unitName = value.unitName ?? "";
33818
33849
  }
33819
- },
33820
- ...search ? [
33821
- {
33822
- $match: {
33823
- $or: [
33824
- { prefixAndName: { $regex: search, $options: "i" } },
33825
- { location: { $regex: search, $options: "i" } },
33826
- { description: { $regex: search, $options: "i" } }
33827
- ]
33850
+ }
33851
+ await _updateUserUnitById(
33852
+ person.user.toString(),
33853
+ userUnitPayload,
33854
+ session
33855
+ );
33856
+ }
33857
+ if (value.unit && (isNameUpdated || isOwnerChanged || value.isOwner)) {
33858
+ const unit = await _getUnitById(value.unit.toString());
33859
+ if (unit) {
33860
+ let updatePayload = {};
33861
+ if (isOwnerChanged) {
33862
+ if (value.isOwner) {
33863
+ updatePayload.owner = _id;
33864
+ updatePayload.ownerName = value.name || "";
33865
+ } else {
33866
+ updatePayload.owner = "";
33867
+ updatePayload.ownerName = "";
33828
33868
  }
33829
33869
  }
33830
- ] : [],
33831
- ...prefixAndName ? [
33832
- {
33833
- $match: {
33834
- prefixAndName: {
33835
- $regex: `^${prefixAndName}$`,
33836
- $options: "i"
33837
- }
33838
- }
33870
+ if (isNameUpdated && value.isOwner && unit.owner?.toString() === _id.toString()) {
33871
+ updatePayload.ownerName = value.name;
33839
33872
  }
33840
- ] : [],
33841
- {
33842
- $project: {
33843
- QRTemplateInfo: 0
33873
+ if (value.isOwner && (!unit.owner || !unit.ownerName)) {
33874
+ updatePayload.owner = _id;
33875
+ updatePayload.ownerName = value.name || "";
33844
33876
  }
33845
- },
33846
- {
33847
- $addFields: {
33848
- prefixOnly: {
33849
- $toUpper: {
33850
- $ifNull: [
33851
- {
33852
- $getField: {
33853
- field: "match",
33854
- input: {
33855
- $regexFind: {
33856
- input: "$prefixAndName",
33857
- regex: "^[A-Za-z]+"
33858
- }
33859
- }
33860
- }
33861
- },
33862
- ""
33863
- ]
33864
- }
33865
- },
33866
- numericSortValue: {
33867
- $toInt: {
33868
- $ifNull: [
33869
- {
33870
- $getField: {
33871
- field: "match",
33872
- input: {
33873
- $regexFind: {
33874
- input: "$prefixAndName",
33875
- regex: "\\d+$"
33876
- }
33877
- }
33878
- }
33879
- },
33880
- "0"
33881
- ]
33882
- }
33883
- }
33877
+ if (Object.keys(updatePayload).length > 0 && unit && unit._id) {
33878
+ await updateUnitById(
33879
+ unit._id.toString(),
33880
+ updatePayload,
33881
+ session
33882
+ );
33883
+ }
33884
+ }
33885
+ }
33886
+ if (isUnitChanged && person.isOwner && person.unit) {
33887
+ const previousUnit = await _getUnitById(person.unit.toString());
33888
+ if (previousUnit && previousUnit._id && previousUnit.owner?.toString() === person?._id?.toString()) {
33889
+ await updateUnitById(
33890
+ previousUnit._id.toString(),
33891
+ { owner: "", ownerName: "" },
33892
+ session
33893
+ );
33894
+ }
33895
+ }
33896
+ await session.commitTransaction();
33897
+ return "Person updated successfully.";
33898
+ } catch (error) {
33899
+ logger80.log({
33900
+ level: "error",
33901
+ message: `Failed to add site billing item: ${error.message}`
33902
+ });
33903
+ await session.abortTransaction();
33904
+ throw error;
33905
+ } finally {
33906
+ session.endSession();
33907
+ }
33908
+ }
33909
+ async function plateValidity(value) {
33910
+ const now = /* @__PURE__ */ new Date();
33911
+ if (["resident" /* RESIDENT */, "tenant" /* TENANT */].includes(
33912
+ value?.type
33913
+ )) {
33914
+ const end = new Date(now);
33915
+ end.setFullYear(end.getFullYear() + 10);
33916
+ return {
33917
+ start: now.toISOString(),
33918
+ end: end.toISOString()
33919
+ };
33920
+ }
33921
+ return { start: "", end: "" };
33922
+ }
33923
+ async function reviewResidentPerson(id, value) {
33924
+ const session = useAtlas50.getClient()?.startSession();
33925
+ session?.startTransaction();
33926
+ try {
33927
+ const person = await _getById(id.toString());
33928
+ if (!person)
33929
+ throw new BadRequestError100("Person not found.");
33930
+ if (value.approvedBy) {
33931
+ const approvedBy = await getUserById(value.approvedBy.id);
33932
+ if (!approvedBy || !approvedBy.name)
33933
+ throw new BadRequestError100("Created by not found.");
33934
+ value.approvedBy.name = approvedBy.name;
33935
+ value.approvedBy.id = approvedBy._id;
33936
+ }
33937
+ const vehicle = {
33938
+ plateNumber: person.plateNumber ?? "",
33939
+ type: "whitelist",
33940
+ category: person.type || "",
33941
+ name: person.name,
33942
+ phoneNumber: person.contact,
33943
+ org: person?.org?.toString(),
33944
+ site: person.site.toString(),
33945
+ block: person.block?.toString() ?? void 0,
33946
+ level: person.level?.toString() ?? void 0,
33947
+ unit: person.unit?.toString() ?? void 0,
33948
+ nric: person.nric,
33949
+ status: "active",
33950
+ peopleId: person._id?.toString()
33951
+ };
33952
+ const reviewStatus = value.status;
33953
+ if (reviewStatus === "approved") {
33954
+ value.status = "active";
33955
+ }
33956
+ await _reviewResidentPerson(id, value, session);
33957
+ const logoPath = `${STORAGE_API}/seven-365.svg`;
33958
+ const statusIconPath = STORAGE_API;
33959
+ let title = "";
33960
+ let message2 = "";
33961
+ let hideLogin = true;
33962
+ let logo = logoPath;
33963
+ let statusLogo = statusIconPath;
33964
+ if (person.user) {
33965
+ let userStatus = "";
33966
+ switch (reviewStatus) {
33967
+ case "approved":
33968
+ userStatus = "active";
33969
+ if (person.plateNumber && person.plateNumber.trim() !== "") {
33970
+ const anprCameraList = await getCamerasBySiteId(
33971
+ person.site.toString()
33972
+ );
33973
+ const existingPerson = await getApprovedPersonByPlateNumber(
33974
+ person.plateNumber,
33975
+ id
33976
+ );
33977
+ if (anprCameraList.length > 0 && !existingPerson) {
33978
+ await addVehicle(vehicle, session);
33979
+ }
33980
+ }
33981
+ title = "Your account has been approved";
33982
+ hideLogin = false;
33983
+ statusLogo = `${statusIconPath}/approved-icon.png`;
33984
+ message2 = `Welcome, ${person.name}! Thank you for signing up; we're excited to have you join us!`;
33985
+ break;
33986
+ case "resubmit":
33987
+ userStatus = "resubmit";
33988
+ title = "We request resubmission for your application";
33989
+ hideLogin = false;
33990
+ statusLogo = `${statusIconPath}/resubmission-icon.png`;
33991
+ message2 = `Hi ${person.name}, please resubmit your application with the required updates.${value.remarks ? ` Remarks: ${value.remarks}` : ""}`;
33992
+ break;
33993
+ case "rejected":
33994
+ userStatus = "rejected";
33995
+ title = "Your account request for application has been rejected.";
33996
+ statusLogo = `${statusIconPath}/deleted-icon.png`;
33997
+ message2 = `Hi ${person.name}, unfortunately your application has been rejected.${value.remarks ? ` Remarks: ${value.remarks}` : " Please contact support for more information."}`;
33998
+ break;
33999
+ default:
34000
+ throw new Error("Invalid review status");
34001
+ }
34002
+ const dir = __dirname;
34003
+ const filePath = getDirectory2(
34004
+ dir,
34005
+ "./public/handlebars/resident-remarks-email"
34006
+ );
34007
+ const emailContent = compileHandlebar2({
34008
+ context: {
34009
+ title,
34010
+ message2,
34011
+ hideLogin,
34012
+ logo,
34013
+ statusLogo
34014
+ },
34015
+ filePath
34016
+ });
34017
+ await mailer.sendMail({
34018
+ to: person.email,
34019
+ subject: title,
34020
+ html: emailContent,
34021
+ sender: "iService365"
34022
+ }).catch((error) => {
34023
+ logger80.log({
34024
+ level: "error",
34025
+ message: `Error sending user ${reviewStatus} email: ${error}`
34026
+ });
34027
+ });
34028
+ await _updateUserFieldById(
34029
+ { _id: person.user.toString(), field: "status", value: userStatus },
34030
+ session
34031
+ );
34032
+ }
34033
+ await session?.commitTransaction();
34034
+ return `Successfully ${reviewStatus} person`;
34035
+ } catch (error) {
34036
+ await session?.abortTransaction();
34037
+ throw error;
34038
+ } finally {
34039
+ session?.endSession();
34040
+ }
34041
+ }
34042
+ async function suspendResidentById(id) {
34043
+ const session = useAtlas50.getClient()?.startSession();
34044
+ if (!session) {
34045
+ throw new BadRequestError100("Database session not available.");
34046
+ }
34047
+ try {
34048
+ session.startTransaction();
34049
+ const person = await _getByUserId(id.toString());
34050
+ if (!person)
34051
+ throw new NotFoundError23("Person not found.");
34052
+ await _updateById(person._id, { status: "deleted" }, session);
34053
+ if (person.user) {
34054
+ await _updateUserFieldById(
34055
+ { _id: person.user.toString(), field: "status", value: "suspended" },
34056
+ session
34057
+ );
34058
+ }
34059
+ await session.commitTransaction();
34060
+ return "Resident suspended successfully.";
34061
+ } catch (error) {
34062
+ await session.abortTransaction();
34063
+ throw error;
34064
+ } finally {
34065
+ session.endSession();
34066
+ }
34067
+ }
34068
+ async function addUpdateFromVisitor(value, session) {
34069
+ value.type = value.type == "walk-in" ? "guest" : value.type;
34070
+ let updateId = null;
34071
+ let add2 = true;
34072
+ let set = {};
34073
+ const cleanInput = (str) => str.replace(/\s+/g, "");
34074
+ const cleanPhone = (str) => str.replace(/\D/g, "");
34075
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
34076
+ const nric = value.nric ? cleanInput(value.nric) : "";
34077
+ const name = value.name?.trim();
34078
+ const contact = value.contact?.trim();
34079
+ const plateNumber = value.plateNumber ? cleanInput(value.plateNumber) : "";
34080
+ const email = value.email?.trim();
34081
+ const type = value.type?.trim();
34082
+ const company = value.company?.trim();
34083
+ const nricQuery = nric ? { nric: { $regex: `^${escapeRegex(nric)}$`, $options: "i" } } : null;
34084
+ const nameQuery = name ? { name: { $regex: `^${escapeRegex(name)}$`, $options: "i" } } : null;
34085
+ let nricCondition = null;
34086
+ if (nric) {
34087
+ if (nric.length < 9) {
34088
+ if (nameQuery) {
34089
+ nricCondition = { $and: [nricQuery, nameQuery] };
34090
+ }
34091
+ } else {
34092
+ nricCondition = nricQuery;
34093
+ }
34094
+ }
34095
+ const orConditions = [
34096
+ ...nricCondition ? [nricCondition] : [],
34097
+ ...contact ? [{ contacts: { $regex: `^\\+?${cleanPhone(escapeRegex(contact))}$`, $options: "i" } }] : [],
34098
+ ...plateNumber ? [{ plateNumbers: { $regex: `^${escapeRegex(plateNumber)}$`, $options: "i" } }] : []
34099
+ ];
34100
+ const existingPerson2 = await findFirstTen(
34101
+ {
34102
+ type,
34103
+ site: value.site,
34104
+ ...orConditions.length > 0 && { $or: orConditions }
34105
+ },
34106
+ session
34107
+ );
34108
+ if (Array.isArray(existingPerson2) && existingPerson2.length > 0) {
34109
+ let matchedPerson = nric ? existingPerson2.find((person) => person.nric === nric) : null;
34110
+ if (matchedPerson) {
34111
+ add2 = false;
34112
+ updateId = matchedPerson._id;
34113
+ if (contact && Array.isArray(matchedPerson.contacts) && !matchedPerson.contacts.includes(contact)) {
34114
+ set = { contacts: [...matchedPerson.contacts, contact] };
34115
+ }
34116
+ if (plateNumber && Array.isArray(matchedPerson.plateNumbers) && !matchedPerson.plateNumbers.includes(plateNumber)) {
34117
+ set = { ...set, plateNumbers: [...matchedPerson.plateNumbers, plateNumber] };
34118
+ }
34119
+ if (company && Array.isArray(matchedPerson.companyName) && !matchedPerson.companyName.includes(company)) {
34120
+ set = { ...set, companyName: [...matchedPerson.companyName, company] };
34121
+ }
34122
+ if (name && name != matchedPerson.name) {
34123
+ set = { ...set, name };
34124
+ }
34125
+ if (email && email != matchedPerson.email) {
34126
+ set = { ...set, email };
34127
+ }
34128
+ } else {
34129
+ matchedPerson = contact ? existingPerson2.find((person) => Array.isArray(person.contacts) && person.contacts.includes(contact)) : null;
34130
+ }
34131
+ if (matchedPerson) {
34132
+ add2 = false;
34133
+ updateId = matchedPerson._id;
34134
+ if (nric && nric !== matchedPerson.nric) {
34135
+ set = { ...set, nric };
34136
+ }
34137
+ if (plateNumber && Array.isArray(matchedPerson.plateNumbers) && !matchedPerson.plateNumbers.includes(plateNumber)) {
34138
+ set = { ...set, plateNumbers: [...matchedPerson.plateNumbers, plateNumber] };
34139
+ }
34140
+ if (company && Array.isArray(matchedPerson.companyName) && !matchedPerson.companyName.includes(company)) {
34141
+ set = { ...set, companyName: [...matchedPerson.companyName, company] };
34142
+ }
34143
+ if (name && name !== matchedPerson.name) {
34144
+ set = { ...set, name };
34145
+ }
34146
+ if (email && email !== matchedPerson.email) {
34147
+ set = { ...set, email };
34148
+ }
34149
+ } else {
34150
+ matchedPerson = plateNumber ? existingPerson2.find((person) => Array.isArray(person.plateNumbers) && person.plateNumbers.includes(plateNumber)) : null;
34151
+ }
34152
+ if (matchedPerson && (contact && Array.isArray(matchedPerson.contacts) && !matchedPerson.contacts.includes(contact) && (nric && nric != matchedPerson.nric) || !nric && !contact)) {
34153
+ add2 = false;
34154
+ updateId = matchedPerson._id;
34155
+ if (nric && nric !== matchedPerson.nric) {
34156
+ set = { ...set, nric };
34157
+ }
34158
+ if (contact && Array.isArray(matchedPerson.contacts) && !matchedPerson.contacts.includes(contact)) {
34159
+ set = { contacts: [...matchedPerson.contacts, contact] };
34160
+ }
34161
+ if (company && Array.isArray(matchedPerson.companyName) && !matchedPerson.companyName.includes(company)) {
34162
+ set = { ...set, companyName: [...matchedPerson.companyName, company] };
34163
+ }
34164
+ if (name && name !== matchedPerson.name) {
34165
+ set = { ...set, name };
34166
+ }
34167
+ if (email && email !== matchedPerson.email) {
34168
+ set = { ...set, email };
34169
+ }
34170
+ }
34171
+ }
34172
+ if (add2) {
34173
+ const data = {
34174
+ nric: value.nric,
34175
+ name: value.name,
34176
+ contacts: value.contact ? [value.contact] : [],
34177
+ plateNumbers: value.plateNumber ? [value.plateNumber] : [],
34178
+ type,
34179
+ email: value.email,
34180
+ site: value.site,
34181
+ status: "active",
34182
+ companyName: value.company ? [value.company] : [],
34183
+ createdBy: value.updatedBy,
34184
+ updatedBy: value.updatedBy
34185
+ };
34186
+ const res_addFromVisitor = await _addFromVisitor(data, session);
34187
+ }
34188
+ if (updateId && Object.keys(set).length > 0) {
34189
+ set = { ...set, updatedBy: value.updatedBy, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
34190
+ const res_updateById = await _updateById(updateId, set, session);
34191
+ }
34192
+ }
34193
+ return {
34194
+ add,
34195
+ updateById,
34196
+ reviewResidentPerson,
34197
+ suspendResidentById,
34198
+ addUpdateFromVisitor
34199
+ };
34200
+ }
34201
+
34202
+ // src/repositories/key.repo.ts
34203
+ import { ObjectId as ObjectId63 } from "mongodb";
34204
+
34205
+ // src/utils/mongo.ts
34206
+ import { useAtlas as useAtlas51 } from "@7365admin1/node-server-utils";
34207
+ var getDB = () => {
34208
+ const db = useAtlas51.getDb();
34209
+ if (!db) {
34210
+ throw new Error("Database not initialized! Check if useAtlas.initialize finished.");
34211
+ }
34212
+ return db;
34213
+ };
34214
+
34215
+ // src/models/key.model.ts
34216
+ import { ObjectId as ObjectId62 } from "mongodb";
34217
+ var MKeyLog = class {
34218
+ constructor({
34219
+ _id = new ObjectId62(),
34220
+ name = "",
34221
+ codes = [],
34222
+ passType = "",
34223
+ passId = "",
34224
+ code = "",
34225
+ // isTaken = false,
34226
+ site = "",
34227
+ organization = "",
34228
+ serviceProvider = "",
34229
+ createdAt = /* @__PURE__ */ new Date(),
34230
+ updatedAt = /* @__PURE__ */ new Date(),
34231
+ template,
34232
+ status = "Available",
34233
+ updatedBy = "",
34234
+ qty,
34235
+ description,
34236
+ location,
34237
+ prefix,
34238
+ keyNo,
34239
+ parentId
34240
+ } = {}) {
34241
+ this._id = _id;
34242
+ this.name = name;
34243
+ this.codes = codes;
34244
+ this.passType = passType;
34245
+ this.passId = passId;
34246
+ this.code = code;
34247
+ this.site = site;
34248
+ this.organization = organization;
34249
+ this.serviceProvider = serviceProvider;
34250
+ this.createdAt = createdAt;
34251
+ this.updatedAt = updatedAt;
34252
+ this.template = template;
34253
+ this.status = status;
34254
+ this.updatedBy = updatedBy;
34255
+ this.qty = qty;
34256
+ this.description = description;
34257
+ this.location = location;
34258
+ this.prefix = prefix;
34259
+ this.parentId = parentId;
34260
+ this.keyNo = keyNo;
34261
+ }
34262
+ };
34263
+
34264
+ // src/utils/pagination.util.ts
34265
+ function paginationUtil(items, page, limit, length) {
34266
+ const startIndex = length ? page * limit + 1 : 0;
34267
+ const endIndex = Math.min(startIndex + limit - 1, length);
34268
+ return {
34269
+ items,
34270
+ pages: Math.ceil(length / limit),
34271
+ pageRange: `${startIndex}-${endIndex} of ${length}`
34272
+ };
34273
+ }
34274
+
34275
+ // src/repositories/key.repo.ts
34276
+ var KeyRepo = class {
34277
+ static collection() {
34278
+ return getDB().collection("keys");
34279
+ }
34280
+ static collectionDynamic(collection) {
34281
+ return getDB().collection(collection);
34282
+ }
34283
+ static async getNextKeyNo(site) {
34284
+ try {
34285
+ site = await convertObjectIdUtil(site);
34286
+ return await this.collection().countDocuments({
34287
+ site,
34288
+ passType: "pass-key",
34289
+ parentId: { $not: { $type: "objectId" } }
34290
+ });
34291
+ } catch (error) {
34292
+ return Promise.reject("Server internal error. " + error);
34293
+ }
34294
+ }
34295
+ static async getKeyWithSequenceNumber(site, _id) {
34296
+ try {
34297
+ site = await convertObjectIdUtil(site);
34298
+ const results = await this.collection().find({
34299
+ site,
34300
+ passType: "pass-key",
34301
+ parentId: { $not: { $type: "objectId" } }
34302
+ }).sort({ createdAt: 1 }).toArray();
34303
+ for (let i = 0; i < results.length; i++) {
34304
+ if (results[i]._id.toString() == _id) {
34305
+ results[i].keyNo = (i + 1).toString().padStart(2, "0");
34306
+ return results[i];
34307
+ }
34308
+ }
34309
+ return Promise.reject("Key Not Found");
34310
+ } catch (error) {
34311
+ return Promise.reject("Server internal error. " + error);
34312
+ }
34313
+ }
34314
+ static async countByTemplateId(template, passType, site) {
34315
+ try {
34316
+ template = await convertObjectIdUtil(template);
34317
+ site = await convertObjectIdUtil(site);
34318
+ let passTypeQuery = { passType };
34319
+ if (passType != "pass-key")
34320
+ passTypeQuery = {
34321
+ passType: { $in: ["visitor-pass", "contractor-pass", "agent-pass"] }
34322
+ };
34323
+ const query = [
34324
+ { $match: { template, ...passTypeQuery, site } },
34325
+ { $addFields: { nameAsNumber: { $toDouble: "$name" } } },
34326
+ { $sort: { nameAsNumber: -1 } },
34327
+ { $limit: 1 },
34328
+ { $project: { nameAsNumber: 1 } }
34329
+ ];
34330
+ const result = await this.collection().aggregate(query).toArray();
34331
+ const highestName = result.length > 0 && result[0].nameAsNumber > 0 ? result[0].nameAsNumber : 0;
34332
+ return highestName;
34333
+ } catch (error) {
34334
+ return Promise.reject("Server internal error. " + error);
34335
+ }
34336
+ }
34337
+ static async createIndexes() {
34338
+ try {
34339
+ await this.collection().createIndex(
34340
+ { passType: "text", name: "text" },
34341
+ { name: "key_search" }
34342
+ );
34343
+ return Promise.resolve("User indexes created.");
34344
+ } catch (error) {
34345
+ return Promise.reject("Failed to create user indexes.");
34346
+ }
34347
+ }
34348
+ static async createKeyLog(keyLog, session) {
34349
+ try {
34350
+ if (keyLog.site)
34351
+ keyLog.site = await convertObjectIdUtil(keyLog.site);
34352
+ if (keyLog.parentId)
34353
+ keyLog.parentId = await convertObjectIdUtil(keyLog.parentId);
34354
+ if (keyLog.organization)
34355
+ keyLog.organization = await convertObjectIdUtil(keyLog.organization);
34356
+ if (keyLog.serviceProvider)
34357
+ keyLog.serviceProvider = await convertObjectIdUtil(
34358
+ keyLog.serviceProvider
34359
+ );
34360
+ if (keyLog.template)
34361
+ keyLog.template = await convertObjectIdUtil(keyLog.template);
34362
+ if (keyLog.updatedBy)
34363
+ keyLog.updatedBy = await convertObjectIdUtil(keyLog.updatedBy);
34364
+ return await this.collection().insertOne(new MKeyLog(keyLog), {
34365
+ session
34366
+ });
34367
+ } catch (error) {
34368
+ return Promise.reject(error || error.message || "Server internal error.");
34369
+ }
34370
+ }
34371
+ static async getByPageSearch2(query, pagination, search, prefixAndName, sortBy) {
34372
+ try {
34373
+ let { page, limit } = pagination;
34374
+ page = page ? page - 1 : 0;
34375
+ limit = limit ? limit : 10;
34376
+ const pipeline = [
34377
+ {
34378
+ $match: query
34379
+ },
34380
+ {
34381
+ $lookup: {
34382
+ from: "qr-code-templates",
34383
+ localField: "template",
34384
+ foreignField: "_id",
34385
+ as: "QRTemplateInfo"
34386
+ }
34387
+ },
34388
+ {
34389
+ $unwind: {
34390
+ path: "$QRTemplateInfo",
34391
+ preserveNullAndEmptyArrays: true
34392
+ }
34393
+ },
34394
+ {
34395
+ $addFields: {
34396
+ prefixPass: { $toUpper: "$QRTemplateInfo.prefixPass" },
34397
+ prefixKey: { $toUpper: "$QRTemplateInfo.prefixKey" },
34398
+ templateName: "$QRTemplateInfo.name"
34399
+ }
34400
+ },
34401
+ {
34402
+ $addFields: {
34403
+ prefixAndName: {
34404
+ $cond: {
34405
+ if: { $or: [{ $not: "$prefixPass" }, { $not: "$prefixKey" }] },
34406
+ then: "$name",
34407
+ else: {
34408
+ $cond: {
34409
+ if: { $eq: ["$passType", "pass-key"] },
34410
+ then: { $concat: ["$prefixKey", "$name"] },
34411
+ else: { $concat: ["$prefixPass", "$name"] }
34412
+ }
34413
+ }
34414
+ }
34415
+ }
34416
+ }
34417
+ },
34418
+ ...search ? [
34419
+ {
34420
+ $match: {
34421
+ $or: [
34422
+ { prefixAndName: { $regex: search, $options: "i" } },
34423
+ { location: { $regex: search, $options: "i" } },
34424
+ { description: { $regex: search, $options: "i" } }
34425
+ ]
34426
+ }
34427
+ }
34428
+ ] : [],
34429
+ ...prefixAndName ? [
34430
+ {
34431
+ $match: {
34432
+ prefixAndName: {
34433
+ $regex: `^${prefixAndName}$`,
34434
+ $options: "i"
34435
+ }
34436
+ }
34437
+ }
34438
+ ] : [],
34439
+ {
34440
+ $project: {
34441
+ QRTemplateInfo: 0
34442
+ }
34443
+ },
34444
+ {
34445
+ $addFields: {
34446
+ prefixOnly: {
34447
+ $toUpper: {
34448
+ $ifNull: [
34449
+ {
34450
+ $getField: {
34451
+ field: "match",
34452
+ input: {
34453
+ $regexFind: {
34454
+ input: "$prefixAndName",
34455
+ regex: "^[A-Za-z]+"
34456
+ }
34457
+ }
34458
+ }
34459
+ },
34460
+ ""
34461
+ ]
34462
+ }
34463
+ },
34464
+ numericSortValue: {
34465
+ $toInt: {
34466
+ $ifNull: [
34467
+ {
34468
+ $getField: {
34469
+ field: "match",
34470
+ input: {
34471
+ $regexFind: {
34472
+ input: "$prefixAndName",
34473
+ regex: "\\d+$"
34474
+ }
34475
+ }
34476
+ }
34477
+ },
34478
+ "0"
34479
+ ]
34480
+ }
34481
+ }
33884
34482
  }
33885
34483
  },
33886
34484
  {
@@ -34215,7 +34813,7 @@ var KeyRepo = class {
34215
34813
  }
34216
34814
  static async getById(_id) {
34217
34815
  try {
34218
- _id = new ObjectId62(_id);
34816
+ _id = new ObjectId63(_id);
34219
34817
  } catch (error) {
34220
34818
  return Promise.reject("Invalid key id");
34221
34819
  }
@@ -34456,7 +35054,7 @@ var KeyRepo = class {
34456
35054
  updatedDocs.forEach((item) => {
34457
35055
  let keyItem = key;
34458
35056
  keyItem["passOrKeyId"] = item?._id;
34459
- keyItem["_id"] = new ObjectId62();
35057
+ keyItem["_id"] = new ObjectId63();
34460
35058
  keyItem["previousStatus"] = item?.status;
34461
35059
  if (visitorId) {
34462
35060
  console.log("visitorId true key.repo");
@@ -34481,7 +35079,7 @@ var KeyRepo = class {
34481
35079
  }
34482
35080
  }
34483
35081
  static async deleteKeyById(_id, session) {
34484
- _id = new ObjectId62(_id);
35082
+ _id = new ObjectId63(_id);
34485
35083
  try {
34486
35084
  await this.collection().findOne({ _id }, { session });
34487
35085
  } catch (error) {
@@ -34496,7 +35094,7 @@ var KeyRepo = class {
34496
35094
  static async getById2(id, session) {
34497
35095
  return this.collection().aggregate(
34498
35096
  [
34499
- { $match: { _id: new ObjectId62(id) } },
35097
+ { $match: { _id: new ObjectId63(id) } },
34500
35098
  {
34501
35099
  $lookup: {
34502
35100
  from: "qr-code-templates",
@@ -34790,7 +35388,7 @@ function useVisitorTransactionService() {
34790
35388
  from: MAILER_FROM_EMAIL,
34791
35389
  password: MAILER_PASSWORD
34792
35390
  };
34793
- const mailer = new useMailer2(MailerConfig);
35391
+ const mailer = new useMailer3(MailerConfig);
34794
35392
  const {
34795
35393
  add: _add,
34796
35394
  updateById: _updateVisitorTansactionById,
@@ -34804,6 +35402,7 @@ function useVisitorTransactionService() {
34804
35402
  getByNRIC,
34805
35403
  updateById: _updateById
34806
35404
  } = usePersonRepo();
35405
+ const { addUpdateFromVisitor } = usePersonService();
34807
35406
  const { getBySite: _getBySite, getVisitorsInBySite: _getVisitorsInBySite } = useSiteCameraRepo();
34808
35407
  const { getSiteById: _getSiteById } = useSiteRepo();
34809
35408
  const {
@@ -34824,15 +35423,15 @@ function useVisitorTransactionService() {
34824
35423
  if (!item)
34825
35424
  return null;
34826
35425
  if (typeof item === "string") {
34827
- return ObjectId63.isValid(item) ? item : null;
35426
+ return ObjectId64.isValid(item) ? item : null;
34828
35427
  }
34829
35428
  if (typeof item === "object" && item !== null) {
34830
35429
  if ("keyId" in item) {
34831
35430
  const keyId = String(item.keyId);
34832
- return ObjectId63.isValid(keyId) ? keyId : null;
35431
+ return ObjectId64.isValid(keyId) ? keyId : null;
34833
35432
  }
34834
35433
  const value = String(item);
34835
- return ObjectId63.isValid(value) ? value : null;
35434
+ return ObjectId64.isValid(value) ? value : null;
34836
35435
  }
34837
35436
  return null;
34838
35437
  }
@@ -34846,12 +35445,12 @@ function useVisitorTransactionService() {
34846
35445
  if (!keyId || seen.has(keyId))
34847
35446
  continue;
34848
35447
  seen.add(keyId);
34849
- normalized.push({ keyId: new ObjectId63(keyId) });
35448
+ normalized.push({ keyId: new ObjectId64(keyId) });
34850
35449
  }
34851
35450
  return normalized;
34852
35451
  }
34853
35452
  async function add(value) {
34854
- const session = useAtlas51.getClient()?.startSession();
35453
+ const session = useAtlas52.getClient()?.startSession();
34855
35454
  const allowedPersonTypes = [
34856
35455
  "contractor" /* CONTRACTOR */,
34857
35456
  "walk-in" /* WALK_IN */,
@@ -34867,272 +35466,50 @@ function useVisitorTransactionService() {
34867
35466
  value.plateNumber
34868
35467
  );
34869
35468
  if (result2)
34870
- throw new BadRequestError100("Plate number is blocklisted");
35469
+ throw new BadRequestError101("Plate number is blocklisted");
34871
35470
  }
34872
35471
  let camera;
34873
35472
  if (value.site && value.plateNumber) {
34874
35473
  try {
34875
35474
  camera = await _getVisitorsInBySite(value.site);
34876
35475
  if (!camera) {
34877
- logger80.warn(`No camera document found for site: ${value.site}`);
35476
+ logger81.warn(`No camera document found for site: ${value.site}`);
34878
35477
  }
34879
35478
  } catch (error) {
34880
- logger80.error(
35479
+ logger81.error(
34881
35480
  `Error _getVisitorsInBySite camera for site ${value.site}:`,
34882
35481
  error
34883
- );
34884
- }
34885
- }
34886
- if (!value.unitName && value.unit) {
34887
- const unit = await _getUnitById(value.unit);
34888
- value.unitName = unit?.name;
34889
- }
34890
- if (allowedPersonTypes.includes(value?.type) && value?.nric) {
34891
- const nric = value?.nric || "";
34892
- const person = await getByNRIC(nric);
34893
- const existingCompanyName = person?.companyName?.includes(
34894
- value.company || ""
34895
- );
34896
- if (person && !existingCompanyName) {
34897
- const updatedCompanyNames = value.company && !existingCompanyName ? [...person.companyName || [], value.company] : person.companyName;
34898
- const updatePayload = {
34899
- companyName: updatedCompanyNames
34900
- };
34901
- await _updateById(person._id, updatePayload, session);
34902
- } else {
34903
- const payload = {
34904
- name: value.name || "",
34905
- contact: value.contact ?? "",
34906
- nric,
34907
- type: value.type,
34908
- companyName: value.company ? [value.company] : [],
34909
- block: value.block ?? "",
34910
- level: value?.level ?? "",
34911
- unit: value?.unit?.toString() || "",
34912
- org: value?.org ?? "",
34913
- site: value?.site ?? "",
34914
- plates: value.plateNumber ? [{ plateNumber: value.plateNumber, recNo: "" }] : [],
34915
- isOwner: false
34916
- };
34917
- await _addPerson(payload, session);
34918
- }
34919
- }
34920
- if (Array.isArray(value.members) && value.members.length > 0) {
34921
- const chunkSize = 10;
34922
- const {
34923
- block,
34924
- level,
34925
- unit,
34926
- site,
34927
- org,
34928
- type,
34929
- company,
34930
- remarks,
34931
- contractorType,
34932
- unitName
34933
- } = value;
34934
- for (let i = 0; i < value.members.length; i += chunkSize) {
34935
- const chunk = value.members.slice(i, i + chunkSize);
34936
- await Promise.all(
34937
- chunk.map(async (member) => {
34938
- const clonedMember = structuredClone(member);
34939
- const { visitorPass, passKeys } = clonedMember;
34940
- const preparedVisitorPass = Array.isArray(visitorPass) ? visitorPass.map((item) => ({
34941
- ...item,
34942
- receivedDate: /* @__PURE__ */ new Date(),
34943
- status: "Not Returned" /* NOT_RETURNED */,
34944
- lastUpdate: null,
34945
- remarks: ""
34946
- })) : [];
34947
- const preparedPassKeys = Array.isArray(passKeys) ? passKeys.map((item) => ({
34948
- ...item,
34949
- receivedDate: /* @__PURE__ */ new Date(),
34950
- status: "Not Returned" /* NOT_RETURNED */,
34951
- lastUpdate: null,
34952
- remarks: ""
34953
- })) : [];
34954
- const visitorId = await _add(
34955
- {
34956
- ...clonedMember,
34957
- block,
34958
- level,
34959
- unit,
34960
- unitName,
34961
- site,
34962
- org,
34963
- type,
34964
- company,
34965
- remarks,
34966
- contractorType,
34967
- checkIn: start,
34968
- visitorPass: preparedVisitorPass,
34969
- passKeys: preparedPassKeys,
34970
- status: "registered" /* REGISTERED */
34971
- },
34972
- session
34973
- );
34974
- console.log("visitorId service", visitorId);
34975
- for (const item of preparedVisitorPass) {
34976
- await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Pass", session);
34977
- await KeyRepo.updateKeyById(
34978
- item.keyId,
34979
- {
34980
- status: "In Use" /* IN_USE */,
34981
- updatedBy: value.createdBy
34982
- },
34983
- value.site,
34984
- session,
34985
- void 0,
34986
- visitorId,
34987
- void 0,
34988
- true
34989
- );
34990
- }
34991
- for (const item of preparedPassKeys) {
34992
- await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Key", session);
34993
- await KeyRepo.updateKeyById(
34994
- item.keyId,
34995
- {
34996
- status: "In Use" /* IN_USE */,
34997
- updatedBy: value.createdBy,
34998
- visitorId
34999
- },
35000
- value.site,
35001
- session,
35002
- void 0,
35003
- visitorId,
35004
- void 0,
35005
- true
35006
- );
35007
- }
35008
- })
35009
- );
35010
- }
35011
- }
35012
- let host;
35013
- let username;
35014
- let password;
35015
- if (camera && value.plateNumber && camera.host && camera.username && camera.password) {
35016
- host = camera.host;
35017
- username = camera.username;
35018
- password = camera.password;
35019
- const blacklist = "TrafficBlackList" /* TRAFFIC_BLACKLIST */;
35020
- const _plateNumber = value.plateNumber;
35021
- const dahuaBlocklistQuery = {
35022
- host,
35023
- username,
35024
- password,
35025
- plateNumber: _plateNumber,
35026
- mode: blacklist
35027
- };
35028
- const isBlocklistedRaw = await _getPlateNumber(dahuaBlocklistQuery);
35029
- const rawString = isBlocklistedRaw?.toString?.("utf-8") ?? String(isBlocklistedRaw);
35030
- const foundMatch = rawString.match(/found=(\d+)/);
35031
- const found = foundMatch ? Number(foundMatch[1]) : 0;
35032
- if (found === 1)
35033
- throw new BadRequestError100("This plate number is blocklisted");
35034
- }
35035
- if (Array.isArray(value.visitorPass)) {
35036
- for (const item of value.visitorPass) {
35037
- item.receivedDate = /* @__PURE__ */ new Date();
35038
- item.status = "Not Returned" /* NOT_RETURNED */;
35039
- item.lastUpdate = null;
35040
- item.remarks = "";
35041
- }
35042
- }
35043
- if (Array.isArray(value.passKeys)) {
35044
- for (const item of value.passKeys) {
35045
- item.receivedDate = /* @__PURE__ */ new Date();
35046
- item.status = "Not Returned" /* NOT_RETURNED */;
35047
- item.lastUpdate = null;
35048
- item.remarks = "";
35049
- }
35050
- }
35051
- const result = await _add(value, session);
35052
- if (Array.isArray(value.visitorPass)) {
35053
- for (const item of value.visitorPass) {
35054
- await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Pass", session);
35055
- await KeyRepo.updateKeyById(
35056
- item.keyId,
35057
- { status: "In Use" /* IN_USE */, updatedBy: value.createdBy },
35058
- value.site,
35059
- session,
35060
- void 0,
35061
- result,
35062
- void 0,
35063
- true
35064
- );
35065
- }
35066
- }
35067
- if (Array.isArray(value.passKeys)) {
35068
- for (const item of value.passKeys) {
35069
- await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Key", session);
35070
- await KeyRepo.updateKeyById(
35071
- item.keyId,
35072
- { status: "In Use" /* IN_USE */, updatedBy: value.createdBy },
35073
- value.site,
35074
- session,
35075
- void 0,
35076
- result,
35077
- void 0,
35078
- true
35079
- );
35080
- }
35081
- }
35082
- await session?.commitTransaction();
35083
- let openBarrier = null;
35084
- const isOpenBarrier = allowedPersonTypes.includes(value?.type) || camera?.ANPRSwitches?.openBarrierPickUpDropOff == true;
35085
- if (isOpenBarrier && value.plateNumber && host && username && password) {
35086
- try {
35087
- logger80.info(`Attempting to open barrier at host: ${host}`);
35088
- openBarrier = await _openBarrier({
35089
- host,
35090
- username,
35091
- password,
35092
- plateNumber: value?.plateNumber
35093
- });
35094
- logger80.info(`Barrier command sent successfully to ${host}`, {
35095
- response: openBarrier
35096
- });
35097
- } catch (error) {
35098
- logger80.error(
35099
- `Failed to open barrier at host ${host}. Critical system error:`,
35100
- error
35101
- );
35102
- openBarrier = { success: false, error: error?.message };
35103
- }
35104
- }
35105
- console.log("Open barrier response:", openBarrier);
35106
- return result;
35107
- } catch (error) {
35108
- await session?.abortTransaction();
35109
- throw error;
35110
- } finally {
35111
- session?.endSession();
35112
- }
35113
- }
35114
- async function updateVisitorTransactionById(id, value) {
35115
- const session = useAtlas51.getClient()?.startSession();
35116
- session?.startTransaction();
35117
- try {
35118
- let camera;
35119
- let host;
35120
- let username;
35121
- let password;
35122
- if (value.checkIn) {
35123
- const parsed = new Date(value.checkIn);
35124
- value.checkIn = isNaN(parsed.getTime()) ? null : parsed;
35482
+ );
35483
+ }
35125
35484
  }
35126
- if (value.isMembersAdded != true && Array.isArray(value.members) && value.members.length > 0) {
35485
+ if (!value.unitName && value.unit) {
35486
+ const unit = await _getUnitById(value.unit);
35487
+ value.unitName = unit?.name;
35488
+ }
35489
+ const { nric = "", name = "", contact = "", plateNumber = "", email = "", site = "", type = "guest", company, createdBy } = value;
35490
+ if (/^(guest|walk-in|contractor|delivery)$/.test(type)) {
35491
+ await addUpdateFromVisitor({
35492
+ nric,
35493
+ name,
35494
+ contact,
35495
+ plateNumber,
35496
+ email,
35497
+ type,
35498
+ site,
35499
+ company,
35500
+ updatedBy: createdBy
35501
+ }, session);
35502
+ }
35503
+ if (Array.isArray(value.members) && value.members.length > 0) {
35127
35504
  const chunkSize = 10;
35128
35505
  const {
35129
35506
  block,
35130
35507
  level,
35131
35508
  unit,
35132
- site,
35509
+ site: site2,
35133
35510
  org,
35134
- type,
35135
- company,
35511
+ type: type2,
35512
+ company: company2,
35136
35513
  remarks,
35137
35514
  contractorType,
35138
35515
  unitName
@@ -35142,7 +35519,18 @@ function useVisitorTransactionService() {
35142
35519
  await Promise.all(
35143
35520
  chunk.map(async (member) => {
35144
35521
  const clonedMember = structuredClone(member);
35145
- const { visitorPass, passKeys } = clonedMember;
35522
+ const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
35523
+ if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
35524
+ await addUpdateFromVisitor({
35525
+ nric: nric2,
35526
+ name: name2,
35527
+ contact: contact2,
35528
+ type: type2,
35529
+ site: site2,
35530
+ company: company2,
35531
+ updatedBy: createdBy
35532
+ }, session);
35533
+ }
35146
35534
  const preparedVisitorPass = Array.isArray(visitorPass) ? visitorPass.map((item) => ({
35147
35535
  ...item,
35148
35536
  receivedDate: /* @__PURE__ */ new Date(),
@@ -35164,13 +35552,13 @@ function useVisitorTransactionService() {
35164
35552
  level,
35165
35553
  unit,
35166
35554
  unitName,
35167
- site,
35555
+ site: site2,
35168
35556
  org,
35169
- type,
35170
- company,
35557
+ type: type2,
35558
+ company: company2,
35171
35559
  remarks,
35172
35560
  contractorType,
35173
- checkIn: value.checkIn,
35561
+ checkIn: start,
35174
35562
  visitorPass: preparedVisitorPass,
35175
35563
  passKeys: preparedPassKeys,
35176
35564
  status: "registered" /* REGISTERED */
@@ -35214,21 +35602,10 @@ function useVisitorTransactionService() {
35214
35602
  })
35215
35603
  );
35216
35604
  }
35217
- value.isMembersAdded = true;
35218
- }
35219
- if (value.site && value.plateNumber) {
35220
- try {
35221
- camera = await _getVisitorsInBySite(value.site);
35222
- if (!camera) {
35223
- logger80.warn(`No camera document found for site: ${value.site}`);
35224
- }
35225
- } catch (error) {
35226
- logger80.error(
35227
- `Error _getVisitorsInBySite camera for site ${value.site}:`,
35228
- error
35229
- );
35230
- }
35231
35605
  }
35606
+ let host;
35607
+ let username;
35608
+ let password;
35232
35609
  if (camera && value.plateNumber && camera.host && camera.username && camera.password) {
35233
35610
  host = camera.host;
35234
35611
  username = camera.username;
@@ -35247,1427 +35624,1109 @@ function useVisitorTransactionService() {
35247
35624
  const foundMatch = rawString.match(/found=(\d+)/);
35248
35625
  const found = foundMatch ? Number(foundMatch[1]) : 0;
35249
35626
  if (found === 1)
35250
- throw new BadRequestError100("This plate number is blocklisted");
35251
- }
35252
- if (value.checkOut) {
35253
- const parsed = new Date(value.checkOut);
35254
- value.checkOut = isNaN(parsed.getTime()) ? null : parsed;
35255
- }
35256
- if (value.site) {
35257
- value.site = typeof value.site === "string" ? new ObjectId63(value.site) : value.site;
35258
- }
35259
- if (value?.block) {
35260
- value.block = await convertObjectIdUtil2(value.block, "block Id");
35261
- }
35262
- if (value?.level) {
35263
- value.level = await convertObjectIdUtil2(value.level, "level Id");
35264
- }
35265
- if (value.unit) {
35266
- value.unit = typeof value.unit === "string" ? new ObjectId63(value.unit) : value.unit;
35267
- const unit = await _getUnitById(value.unit);
35268
- value.unitName = unit?.name;
35269
- }
35270
- if (value.updatedBy) {
35271
- value.updatedBy = await convertObjectIdUtil2(value.updatedBy, "updatedBy Id");
35627
+ throw new BadRequestError101("This plate number is blocklisted");
35272
35628
  }
35273
35629
  if (Array.isArray(value.visitorPass)) {
35274
35630
  for (const item of value.visitorPass) {
35275
- if (item.add) {
35276
- await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Pass", session);
35277
- await KeyRepo.updateKeyById(
35278
- item.keyId,
35279
- {
35280
- status: "In Use" /* IN_USE */,
35281
- updatedBy: value.updatedBy
35282
- },
35283
- value.site,
35284
- session,
35285
- void 0,
35286
- id
35287
- );
35288
- item.receivedDate = /* @__PURE__ */ new Date();
35289
- item.status = "Not Returned" /* NOT_RETURNED */;
35290
- item.lastUpdate = null;
35291
- item.remarks = "";
35292
- } else {
35293
- let status = "Invalid";
35294
- if (item?.status == "Returned" /* RETURNED */) {
35295
- status = "Available" /* AVAILABLE */;
35296
- } else if (item?.status == "Removed" /* REMOVED */) {
35297
- status = "Available" /* AVAILABLE */;
35298
- } else if (item?.status == "Not Returned" /* NOT_RETURNED */) {
35299
- status = "In Use" /* IN_USE */;
35300
- } else if (item?.status && VALID_STATUSES.has(item.status)) {
35301
- status = item.status;
35302
- } else {
35303
- throw new Error("Invalid Visitor Pass Status");
35304
- }
35305
- await KeyRepo.updateKeyById(
35306
- item.keyId,
35307
- {
35308
- status,
35309
- updatedBy: value.updatedBy
35310
- },
35311
- value.site,
35312
- session,
35313
- void 0,
35314
- id,
35315
- true
35316
- );
35317
- delete item.receivedDate;
35318
- item.lastUpdate = /* @__PURE__ */ new Date();
35319
- }
35631
+ item.receivedDate = /* @__PURE__ */ new Date();
35632
+ item.status = "Not Returned" /* NOT_RETURNED */;
35633
+ item.lastUpdate = null;
35634
+ item.remarks = "";
35320
35635
  }
35321
35636
  }
35322
35637
  if (Array.isArray(value.passKeys)) {
35323
35638
  for (const item of value.passKeys) {
35324
- if (item.add) {
35325
- await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Key", session);
35326
- await KeyRepo.updateKeyById(
35327
- item.keyId,
35328
- {
35329
- status: "In Use" /* IN_USE */,
35330
- updatedBy: value.updatedBy
35331
- },
35332
- value.site,
35333
- session,
35334
- void 0,
35335
- id
35336
- );
35337
- item.receivedDate = /* @__PURE__ */ new Date();
35338
- item.status = "Not Returned" /* NOT_RETURNED */;
35339
- item.lastUpdate = null;
35340
- item.remarks = "";
35341
- } else {
35342
- let status = "Invalid";
35343
- if (item?.status == "Returned" /* RETURNED */) {
35344
- status = "Available" /* AVAILABLE */;
35345
- } else if (item?.status == "Removed" /* REMOVED */) {
35346
- status = "Available" /* AVAILABLE */;
35347
- } else if (item?.status == "Not Returned" /* NOT_RETURNED */) {
35348
- status = "In Use" /* IN_USE */;
35349
- } else if (item?.status && VALID_STATUSES.has(item.status)) {
35350
- status = item.status;
35351
- } else {
35352
- throw new Error("Invalid Key Status");
35353
- }
35354
- await KeyRepo.updateKeyById(
35355
- item.keyId,
35356
- {
35357
- status,
35358
- updatedBy: value.updatedBy
35359
- },
35360
- value.site,
35361
- session,
35362
- void 0,
35363
- id,
35364
- true
35365
- );
35366
- delete item.receivedDate;
35367
- item.lastUpdate = /* @__PURE__ */ new Date();
35368
- }
35369
- }
35370
- }
35371
- await _updateVisitorTansactionById(id, value, session);
35372
- const allowedPersonTypes = [
35373
- "contractor" /* CONTRACTOR */,
35374
- "walk-in" /* WALK_IN */,
35375
- "guest" /* GUEST */,
35376
- "delivery" /* DELIVERY */
35377
- // PersonTypes.PICK_UP,
35378
- ];
35379
- let openBarrier = null;
35380
- const isOpenBarrier = allowedPersonTypes.includes(value?.type) || camera?.ANPRSwitches?.openBarrierPickUpDropOff == true;
35381
- if (isOpenBarrier && value.plateNumber && host && username && password) {
35382
- try {
35383
- logger80.info(`Attempting to open barrier at host: ${host}`);
35384
- openBarrier = await _openBarrier({
35385
- host,
35386
- username,
35387
- password,
35388
- plateNumber: value?.plateNumber
35389
- });
35390
- logger80.info(`Barrier command sent successfully to ${host}`, {
35391
- response: openBarrier
35392
- });
35393
- } catch (error) {
35394
- logger80.error(
35395
- `Failed to open barrier at host ${host}. Critical system error:`,
35396
- error
35397
- );
35398
- openBarrier = { success: false, error: error?.message };
35639
+ item.receivedDate = /* @__PURE__ */ new Date();
35640
+ item.status = "Not Returned" /* NOT_RETURNED */;
35641
+ item.lastUpdate = null;
35642
+ item.remarks = "";
35399
35643
  }
35400
35644
  }
35401
- console.log("Open barrier response:", openBarrier);
35402
- const visitorTransaction = await _getVisitorTransactionById(
35403
- id.toString()
35404
- );
35405
- if (!visitorTransaction) {
35406
- throw new Error("Visitor transaction not found.");
35407
- }
35408
- if (value.checkOut && visitorTransaction.type === "guest" /* GUEST */) {
35409
- const users = await getUsersBySiteId({
35410
- status: "active",
35411
- siteId: visitorTransaction.site.toString()
35412
- });
35413
- if (users?.length > 0) {
35414
- const userIds = Array.from(
35415
- new Set(users.map((item) => item.user.toString()))
35645
+ const result = await _add(value, session);
35646
+ if (Array.isArray(value.visitorPass)) {
35647
+ for (const item of value.visitorPass) {
35648
+ await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Pass", session);
35649
+ await KeyRepo.updateKeyById(
35650
+ item.keyId,
35651
+ { status: "In Use" /* IN_USE */, updatedBy: value.createdBy },
35652
+ value.site,
35653
+ session,
35654
+ void 0,
35655
+ result,
35656
+ void 0,
35657
+ true
35416
35658
  );
35417
- NotificationService.invitedVisitorCheckOutForMa({
35418
- to: userIds,
35419
- name: visitorTransaction.name,
35420
- _id: id.toString()
35421
- });
35422
35659
  }
35423
35660
  }
35424
- await session?.commitTransaction();
35425
- return "Successfully updated visitor transaction.";
35426
- } catch (error) {
35427
- await session?.abortTransaction();
35428
- throw error;
35429
- } finally {
35430
- session?.endSession();
35431
- }
35432
- }
35433
- async function processTransactionDahuaStatus() {
35434
- try {
35435
- const limit = 100;
35436
- let page = 1;
35437
- let hasMore = true;
35438
- while (hasMore) {
35439
- const { items: sites } = await _getAllSites({
35440
- page,
35441
- limit
35442
- });
35443
- if (!sites.length) {
35444
- hasMore = false;
35445
- break;
35446
- }
35447
- for (const site of sites) {
35448
- const siteId = site._id;
35449
- const camera = await _getBySite(siteId, {
35450
- type: "anpr" /* ANPR */
35451
- });
35452
- if (!camera)
35453
- continue;
35454
- const transactions = await _getExpiredCheckedOutTransactionsBySite(
35455
- siteId
35661
+ if (Array.isArray(value.passKeys)) {
35662
+ for (const item of value.passKeys) {
35663
+ await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Key", session);
35664
+ await KeyRepo.updateKeyById(
35665
+ item.keyId,
35666
+ { status: "In Use" /* IN_USE */, updatedBy: value.createdBy },
35667
+ value.site,
35668
+ session,
35669
+ void 0,
35670
+ result,
35671
+ void 0,
35672
+ true
35456
35673
  );
35457
- if (!transactions.length)
35458
- continue;
35459
- const successfulIds = [];
35460
- const batchSize = 5;
35461
- for (let i = 0; i < transactions.length; i += batchSize) {
35462
- const batch = transactions.slice(i, i + batchSize);
35463
- await Promise.all(
35464
- batch.map(async (transaction) => {
35465
- try {
35466
- const dahuaPayload = {
35467
- host: camera.host,
35468
- username: camera.username,
35469
- password: camera.password,
35470
- mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
35471
- recno: transaction.recNo
35472
- };
35473
- await _removePlateNumber(dahuaPayload);
35474
- successfulIds.push(transaction._id);
35475
- } catch (error) {
35476
- const message = String(error?.message || "").toLowerCase();
35477
- const isNotFound = message.includes("not found") || message.includes("does not exist") || message.includes("no record");
35478
- if (isNotFound) {
35479
- logger80.warn(
35480
- `Dahua record already missing for transaction ${transaction._id}, marking as removed.`
35481
- );
35482
- successfulIds.push(transaction._id);
35483
- return;
35484
- }
35485
- logger80.error(
35486
- `Failed to remove plate for transaction ${transaction._id}`,
35487
- error
35488
- );
35489
- }
35490
- })
35491
- );
35492
- }
35493
- if (successfulIds.length > 0) {
35494
- await _updateManyDahuaSyncStatus(successfulIds, "removed");
35495
- }
35496
35674
  }
35497
- page++;
35498
35675
  }
35499
- } catch (error) {
35500
- throw error;
35501
- }
35502
- }
35503
- async function inviteVisitor(value, userId) {
35504
- const session = useAtlas51.getClient()?.startSession();
35505
- if (!session) {
35506
- throw new Error("Unable to start session for people service.");
35507
- }
35508
- try {
35509
- session.startTransaction();
35510
- const inviter = await _getByUserId(userId);
35511
- const unit = await getById(inviter?.unit);
35512
- value.checkIn = null;
35513
- const payload = {
35514
- ...value,
35515
- block: inviter?.block?.toString(),
35516
- level: inviter?.level?.toString(),
35517
- unit: inviter?.unit?.toString(),
35518
- unitName: unit?.name,
35519
- org: inviter?.org.toString(),
35520
- site: inviter?.site.toString(),
35521
- status: "approved" /* APPROVED */,
35522
- inviterId: userId
35523
- };
35524
- const result = await _add(payload);
35525
35676
  await session?.commitTransaction();
35677
+ let openBarrier = null;
35678
+ const isOpenBarrier = allowedPersonTypes.includes(value?.type) || camera?.ANPRSwitches?.openBarrierPickUpDropOff == true;
35679
+ if (isOpenBarrier && value.plateNumber && host && username && password) {
35680
+ try {
35681
+ logger81.info(`Attempting to open barrier at host: ${host}`);
35682
+ openBarrier = await _openBarrier({
35683
+ host,
35684
+ username,
35685
+ password,
35686
+ plateNumber: value?.plateNumber
35687
+ });
35688
+ logger81.info(`Barrier command sent successfully to ${host}`, {
35689
+ response: openBarrier
35690
+ });
35691
+ } catch (error) {
35692
+ logger81.error(
35693
+ `Failed to open barrier at host ${host}. Critical system error:`,
35694
+ error
35695
+ );
35696
+ openBarrier = { success: false, error: error?.message };
35697
+ }
35698
+ }
35699
+ console.log("Open barrier response:", openBarrier);
35526
35700
  return result;
35527
35701
  } catch (error) {
35528
- logger80.error("Error in visitor transaction invite visitor:", error);
35529
- await session.abortTransaction();
35702
+ await session?.abortTransaction();
35530
35703
  throw error;
35531
35704
  } finally {
35532
35705
  session?.endSession();
35533
35706
  }
35534
35707
  }
35535
- return {
35536
- add,
35537
- updateVisitorTransactionById,
35538
- processTransactionDahuaStatus,
35539
- inviteVisitor
35540
- // changeVisitorTransactionKeysById,
35541
- };
35542
- }
35543
-
35544
- // src/controllers/visitor-transaction.controller.ts
35545
- import Joi57 from "joi";
35546
- import { BadRequestError as BadRequestError101, logger as logger81 } from "@7365admin1/node-server-utils";
35547
- function useVisitorTransactionController() {
35548
- const {
35549
- add: _add,
35550
- updateVisitorTransactionById: _updateVisitorTransactionById,
35551
- inviteVisitor: _inviteVisitor
35552
- // changeVisitorTransactionKeysById: _changeVisitorTransactionKeysById,
35553
- } = useVisitorTransactionService();
35554
- const {
35555
- getAll: _getAll,
35556
- deleteVisitorTransaction: _deleteVisitorTransaction,
35557
- getVisitorTransactionById: _getVisitorTransactionById,
35558
- resendEmailInvitedResident
35559
- } = useVisitorTransactionRepo();
35560
- async function add(req, res, next) {
35708
+ async function updateVisitorTransactionById(id, value) {
35709
+ const session = useAtlas52.getClient()?.startSession();
35710
+ session?.startTransaction();
35561
35711
  try {
35562
- const { companyName, firstName, lastName, ...restData } = req.body;
35563
- const { error, value } = schemaVisitorTransaction.validate(restData, {
35564
- abortEarly: false
35565
- });
35566
- if (error) {
35567
- const messages = error.details.map((d) => d.message).join(", ");
35568
- logger81.log({ level: "error", message: messages });
35569
- next(new BadRequestError101(messages));
35570
- return;
35712
+ let camera;
35713
+ let host;
35714
+ let username;
35715
+ let password;
35716
+ if (value.checkIn) {
35717
+ const parsed = new Date(value.checkIn);
35718
+ value.checkIn = isNaN(parsed.getTime()) ? null : parsed;
35719
+ }
35720
+ const { nric = "", name = "", contact = "", plateNumber = "", email = "", site = "", type = "guest", company, createdBy } = value;
35721
+ if (/^(guest|walk-in|contractor|delivery)$/.test(type) && !value.checkOut) {
35722
+ await addUpdateFromVisitor({
35723
+ nric,
35724
+ name,
35725
+ contact,
35726
+ plateNumber,
35727
+ email,
35728
+ type,
35729
+ site,
35730
+ company,
35731
+ updatedBy: createdBy
35732
+ }, session);
35733
+ }
35734
+ if (value.isMembersAdded != true && Array.isArray(value.members) && value.members.length > 0) {
35735
+ const chunkSize = 10;
35736
+ const {
35737
+ block,
35738
+ level,
35739
+ unit,
35740
+ site: site2,
35741
+ org,
35742
+ type: type2,
35743
+ company: company2,
35744
+ remarks,
35745
+ contractorType,
35746
+ unitName
35747
+ } = value;
35748
+ for (let i = 0; i < value.members.length; i += chunkSize) {
35749
+ const chunk = value.members.slice(i, i + chunkSize);
35750
+ await Promise.all(
35751
+ chunk.map(async (member) => {
35752
+ const clonedMember = structuredClone(member);
35753
+ const { visitorPass, passKeys, nric: nric2, name: name2, contact: contact2 } = clonedMember;
35754
+ if (/^(guest|walk-in|contractor|delivery)$/.test(type2 ?? "invalid") && site2) {
35755
+ await addUpdateFromVisitor({
35756
+ nric: nric2,
35757
+ name: name2,
35758
+ contact: contact2,
35759
+ type: type2,
35760
+ site: site2,
35761
+ company: company2,
35762
+ updatedBy: createdBy
35763
+ }, session);
35764
+ }
35765
+ const preparedVisitorPass = Array.isArray(visitorPass) ? visitorPass.map((item) => ({
35766
+ ...item,
35767
+ receivedDate: /* @__PURE__ */ new Date(),
35768
+ status: "Not Returned" /* NOT_RETURNED */,
35769
+ lastUpdate: null,
35770
+ remarks: ""
35771
+ })) : [];
35772
+ const preparedPassKeys = Array.isArray(passKeys) ? passKeys.map((item) => ({
35773
+ ...item,
35774
+ receivedDate: /* @__PURE__ */ new Date(),
35775
+ status: "Not Returned" /* NOT_RETURNED */,
35776
+ lastUpdate: null,
35777
+ remarks: ""
35778
+ })) : [];
35779
+ const visitorId = await _add(
35780
+ {
35781
+ ...clonedMember,
35782
+ block,
35783
+ level,
35784
+ unit,
35785
+ unitName,
35786
+ site: site2,
35787
+ org,
35788
+ type: type2,
35789
+ company: company2,
35790
+ remarks,
35791
+ contractorType,
35792
+ checkIn: value.checkIn,
35793
+ visitorPass: preparedVisitorPass,
35794
+ passKeys: preparedPassKeys,
35795
+ status: "registered" /* REGISTERED */
35796
+ },
35797
+ session
35798
+ );
35799
+ console.log("visitorId service", visitorId);
35800
+ for (const item of preparedVisitorPass) {
35801
+ await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Pass", session);
35802
+ await KeyRepo.updateKeyById(
35803
+ item.keyId,
35804
+ {
35805
+ status: "In Use" /* IN_USE */,
35806
+ updatedBy: value.createdBy
35807
+ },
35808
+ value.site,
35809
+ session,
35810
+ void 0,
35811
+ visitorId,
35812
+ void 0,
35813
+ true
35814
+ );
35815
+ }
35816
+ for (const item of preparedPassKeys) {
35817
+ await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Key", session);
35818
+ await KeyRepo.updateKeyById(
35819
+ item.keyId,
35820
+ {
35821
+ status: "In Use" /* IN_USE */,
35822
+ updatedBy: value.createdBy,
35823
+ visitorId
35824
+ },
35825
+ value.site,
35826
+ session,
35827
+ void 0,
35828
+ visitorId,
35829
+ void 0,
35830
+ true
35831
+ );
35832
+ }
35833
+ })
35834
+ );
35835
+ }
35836
+ value.isMembersAdded = true;
35837
+ }
35838
+ if (value.site && value.plateNumber) {
35839
+ try {
35840
+ camera = await _getVisitorsInBySite(value.site);
35841
+ if (!camera) {
35842
+ logger81.warn(`No camera document found for site: ${value.site}`);
35843
+ }
35844
+ } catch (error) {
35845
+ logger81.error(
35846
+ `Error _getVisitorsInBySite camera for site ${value.site}:`,
35847
+ error
35848
+ );
35849
+ }
35850
+ }
35851
+ if (camera && value.plateNumber && camera.host && camera.username && camera.password) {
35852
+ host = camera.host;
35853
+ username = camera.username;
35854
+ password = camera.password;
35855
+ const blacklist = "TrafficBlackList" /* TRAFFIC_BLACKLIST */;
35856
+ const _plateNumber = value.plateNumber;
35857
+ const dahuaBlocklistQuery = {
35858
+ host,
35859
+ username,
35860
+ password,
35861
+ plateNumber: _plateNumber,
35862
+ mode: blacklist
35863
+ };
35864
+ const isBlocklistedRaw = await _getPlateNumber(dahuaBlocklistQuery);
35865
+ const rawString = isBlocklistedRaw?.toString?.("utf-8") ?? String(isBlocklistedRaw);
35866
+ const foundMatch = rawString.match(/found=(\d+)/);
35867
+ const found = foundMatch ? Number(foundMatch[1]) : 0;
35868
+ if (found === 1)
35869
+ throw new BadRequestError101("This plate number is blocklisted");
35870
+ }
35871
+ if (value.checkOut) {
35872
+ const parsed = new Date(value.checkOut);
35873
+ value.checkOut = isNaN(parsed.getTime()) ? null : parsed;
35874
+ }
35875
+ if (value.site) {
35876
+ value.site = typeof value.site === "string" ? new ObjectId64(value.site) : value.site;
35571
35877
  }
35572
- const data = await _add(value);
35573
- res.status(201).json(data);
35574
- return;
35575
- } catch (error) {
35576
- logger81.log({ level: "error", message: error.message });
35577
- console.log("error", error);
35578
- if (error?.message) {
35579
- next(new BadRequestError101(error?.message));
35580
- } else {
35581
- next(error);
35878
+ if (value?.block) {
35879
+ value.block = await convertObjectIdUtil2(value.block, "block Id");
35582
35880
  }
35583
- return;
35584
- }
35585
- }
35586
- async function getAll(req, res, next) {
35587
- try {
35588
- const validation = Joi57.object({
35589
- search: Joi57.string().optional().allow(null, ""),
35590
- page: Joi57.number().integer().min(1).allow("", null).default(1),
35591
- limit: Joi57.number().integer().min(1).max(100).allow("", null).default(10),
35592
- sort: Joi57.string().valid(...Object.values(VisitorSort)).optional().allow(null, ""),
35593
- status: Joi57.string().valid(...Object.values(VisitorStatus)).optional().allow(null, ""),
35594
- org: Joi57.string().hex().length(24).optional().allow(null, ""),
35595
- site: Joi57.string().hex().length(24).optional().allow(null, ""),
35596
- dateFrom: Joi57.date().iso().optional().allow(null, ""),
35597
- dateTo: Joi57.date().iso().optional().allow(null, ""),
35598
- type: Joi57.alternatives().try(
35599
- Joi57.array().items(
35600
- Joi57.string().valid(...Object.values(PersonTypes))
35601
- ),
35602
- Joi57.string().custom((value2) => {
35603
- return value2.split(",").map((t) => t.trim());
35604
- })
35605
- ).custom((value2) => {
35606
- return Array.isArray(value2) ? value2 : [value2];
35607
- }).optional().allow(null, ""),
35608
- checkedOut: Joi57.boolean().allow(null, ""),
35609
- plateNumber: Joi57.string().optional().allow(null, ""),
35610
- order: Joi57.string().valid(...Object.values(SortOrder)).default("asc" /* ASC */),
35611
- tab: Joi57.string().optional().allow(null, "")
35612
- });
35613
- const { error, value } = validation.validate(req.query, {
35614
- abortEarly: false
35615
- });
35616
- if (error) {
35617
- const messages = error.details.map((d) => d.message).join(", ");
35618
- logger81.log({ level: "error", message: messages });
35619
- next(new BadRequestError101(messages));
35620
- return;
35881
+ if (value?.level) {
35882
+ value.level = await convertObjectIdUtil2(value.level, "level Id");
35621
35883
  }
35622
- const {
35623
- org,
35624
- site,
35625
- search,
35626
- page,
35627
- limit,
35628
- status,
35629
- dateTo,
35630
- dateFrom,
35631
- type,
35632
- checkedOut,
35633
- sort,
35634
- order,
35635
- plateNumber,
35636
- tab
35637
- } = value;
35638
- const sortObj = {
35639
- [sort ? sort : "_id" /* ID */]: order === "asc" /* ASC */ ? 1 : -1
35640
- };
35641
- const data = await _getAll({
35642
- org,
35643
- site,
35644
- search,
35645
- page,
35646
- limit,
35647
- sort: sortObj,
35648
- status,
35649
- dateTo,
35650
- dateFrom,
35651
- type,
35652
- checkedOut,
35653
- plateNumber,
35654
- tab
35655
- });
35656
- res.status(200).json(data);
35657
- return;
35658
- } catch (error) {
35659
- logger81.log({ level: "error", message: error.message });
35660
- next(error);
35661
- return;
35662
- }
35663
- }
35664
- async function getVisitorTransactionById(req, res, next) {
35665
- try {
35666
- const schema2 = Joi57.object({
35667
- _id: Joi57.string().hex().length(24).required()
35668
- });
35669
- const { error, value } = schema2.validate(
35670
- { _id: req.params.id },
35671
- {
35672
- abortEarly: false
35884
+ if (value.unit) {
35885
+ value.unit = typeof value.unit === "string" ? new ObjectId64(value.unit) : value.unit;
35886
+ const unit = await _getUnitById(value.unit);
35887
+ value.unitName = unit?.name;
35888
+ }
35889
+ if (value.updatedBy) {
35890
+ value.updatedBy = await convertObjectIdUtil2(value.updatedBy, "updatedBy Id");
35891
+ }
35892
+ if (Array.isArray(value.visitorPass)) {
35893
+ for (const item of value.visitorPass) {
35894
+ if (item.add) {
35895
+ await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Pass", session);
35896
+ await KeyRepo.updateKeyById(
35897
+ item.keyId,
35898
+ {
35899
+ status: "In Use" /* IN_USE */,
35900
+ updatedBy: value.updatedBy
35901
+ },
35902
+ value.site,
35903
+ session,
35904
+ void 0,
35905
+ id
35906
+ );
35907
+ item.receivedDate = /* @__PURE__ */ new Date();
35908
+ item.status = "Not Returned" /* NOT_RETURNED */;
35909
+ item.lastUpdate = null;
35910
+ item.remarks = "";
35911
+ } else {
35912
+ let status = "Invalid";
35913
+ if (item?.status == "Returned" /* RETURNED */) {
35914
+ status = "Available" /* AVAILABLE */;
35915
+ } else if (item?.status == "Removed" /* REMOVED */) {
35916
+ status = "Available" /* AVAILABLE */;
35917
+ } else if (item?.status == "Not Returned" /* NOT_RETURNED */) {
35918
+ status = "In Use" /* IN_USE */;
35919
+ } else if (item?.status && VALID_STATUSES.has(item.status)) {
35920
+ status = item.status;
35921
+ } else {
35922
+ throw new Error("Invalid Visitor Pass Status");
35923
+ }
35924
+ await KeyRepo.updateKeyById(
35925
+ item.keyId,
35926
+ {
35927
+ status,
35928
+ updatedBy: value.updatedBy
35929
+ },
35930
+ value.site,
35931
+ session,
35932
+ void 0,
35933
+ id,
35934
+ true
35935
+ );
35936
+ delete item.receivedDate;
35937
+ item.lastUpdate = /* @__PURE__ */ new Date();
35938
+ }
35673
35939
  }
35674
- );
35675
- if (error) {
35676
- const messages = error.details.map((d) => d.message).join(", ");
35677
- logger81.log({ level: "error", message: messages });
35678
- next(new BadRequestError101(messages));
35679
- return;
35680
35940
  }
35681
- const { _id } = value;
35682
- const data = await _getVisitorTransactionById(_id);
35683
- res.status(200).json(data);
35684
- return;
35685
- } catch (error) {
35686
- logger81.log({ level: "error", message: error.message });
35687
- next(error);
35688
- return;
35689
- }
35690
- }
35691
- async function updateVisitorTansactionById(req, res, next) {
35692
- const _id = req.params.id;
35693
- const payload = { _id, ...req.body };
35694
- const { error } = schemaUpdateVisTrans.validate(payload, {
35695
- abortEarly: false
35696
- });
35697
- if (error) {
35698
- const messages = error.details.map((d) => d.message).join(", ");
35699
- logger81.log({ level: "error", message: messages });
35700
- next(new BadRequestError101(messages));
35701
- return;
35702
- }
35703
- try {
35704
- await _updateVisitorTransactionById(_id, req.body);
35705
- res.status(200).json({ message: "Successfully updated visitor transaction." });
35706
- return;
35707
- } catch (error2) {
35708
- logger81.log({ level: "error", message: error2.message });
35709
- console.log("error", error2);
35710
- if (error2?.message) {
35711
- next(new BadRequestError101(error2?.message));
35712
- } else {
35713
- next(error2);
35941
+ if (Array.isArray(value.passKeys)) {
35942
+ for (const item of value.passKeys) {
35943
+ if (item.add) {
35944
+ await KeyRepo.checkPassOrKeyAvailability(item.keyId, "Key", session);
35945
+ await KeyRepo.updateKeyById(
35946
+ item.keyId,
35947
+ {
35948
+ status: "In Use" /* IN_USE */,
35949
+ updatedBy: value.updatedBy
35950
+ },
35951
+ value.site,
35952
+ session,
35953
+ void 0,
35954
+ id
35955
+ );
35956
+ item.receivedDate = /* @__PURE__ */ new Date();
35957
+ item.status = "Not Returned" /* NOT_RETURNED */;
35958
+ item.lastUpdate = null;
35959
+ item.remarks = "";
35960
+ } else {
35961
+ let status = "Invalid";
35962
+ if (item?.status == "Returned" /* RETURNED */) {
35963
+ status = "Available" /* AVAILABLE */;
35964
+ } else if (item?.status == "Removed" /* REMOVED */) {
35965
+ status = "Available" /* AVAILABLE */;
35966
+ } else if (item?.status == "Not Returned" /* NOT_RETURNED */) {
35967
+ status = "In Use" /* IN_USE */;
35968
+ } else if (item?.status && VALID_STATUSES.has(item.status)) {
35969
+ status = item.status;
35970
+ } else {
35971
+ throw new Error("Invalid Key Status");
35972
+ }
35973
+ await KeyRepo.updateKeyById(
35974
+ item.keyId,
35975
+ {
35976
+ status,
35977
+ updatedBy: value.updatedBy
35978
+ },
35979
+ value.site,
35980
+ session,
35981
+ void 0,
35982
+ id,
35983
+ true
35984
+ );
35985
+ delete item.receivedDate;
35986
+ item.lastUpdate = /* @__PURE__ */ new Date();
35987
+ }
35988
+ }
35989
+ }
35990
+ await _updateVisitorTansactionById(id, value, session);
35991
+ const allowedPersonTypes = [
35992
+ "contractor" /* CONTRACTOR */,
35993
+ "walk-in" /* WALK_IN */,
35994
+ "guest" /* GUEST */,
35995
+ "delivery" /* DELIVERY */
35996
+ // PersonTypes.PICK_UP,
35997
+ ];
35998
+ let openBarrier = null;
35999
+ const isOpenBarrier = allowedPersonTypes.includes(value?.type) || camera?.ANPRSwitches?.openBarrierPickUpDropOff == true;
36000
+ if (isOpenBarrier && value.plateNumber && host && username && password) {
36001
+ try {
36002
+ logger81.info(`Attempting to open barrier at host: ${host}`);
36003
+ openBarrier = await _openBarrier({
36004
+ host,
36005
+ username,
36006
+ password,
36007
+ plateNumber: value?.plateNumber
36008
+ });
36009
+ logger81.info(`Barrier command sent successfully to ${host}`, {
36010
+ response: openBarrier
36011
+ });
36012
+ } catch (error) {
36013
+ logger81.error(
36014
+ `Failed to open barrier at host ${host}. Critical system error:`,
36015
+ error
36016
+ );
36017
+ openBarrier = { success: false, error: error?.message };
36018
+ }
35714
36019
  }
35715
- return;
35716
- }
35717
- }
35718
- async function deleteVisitorTransaction(req, res, next) {
35719
- const validation = Joi57.string().hex().required();
35720
- const _id = req.params.id;
35721
- const { error } = validation.validate(_id);
35722
- if (error) {
35723
- logger81.log({ level: "error", message: error.message });
35724
- next(new BadRequestError101(error.message));
35725
- return;
35726
- }
35727
- try {
35728
- await _deleteVisitorTransaction(_id);
35729
- res.status(200).json({ message: "Successfully deleted visitor transaction." });
35730
- return;
35731
- } catch (error2) {
35732
- logger81.log({ level: "error", message: error2.message });
35733
- next(error2);
35734
- return;
35735
- }
35736
- }
35737
- async function inviteVisitor(req, res, next) {
35738
- const schema2 = Joi57.object({
35739
- expectedCheckIn: Joi57.string().isoDate().required(),
35740
- arrivalTime: Joi57.string().pattern(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().allow(null, "").messages({
35741
- "string.pattern.base": "arrivalTime must be in HH:mm format (e.g. 09:30, 18:45)"
35742
- }),
35743
- duration: Joi57.number().integer().optional().allow(null, ""),
35744
- name: Joi57.string().required(),
35745
- contact: Joi57.string().optional().allow(null, ""),
35746
- email: Joi57.string().email().optional().allow(null, ""),
35747
- plateNumber: Joi57.string().optional().allow(null, ""),
35748
- isOvernightParking: Joi57.boolean().optional().default(false),
35749
- numberOfPassengers: Joi57.number().integer().optional().allow(null, ""),
35750
- purpose: Joi57.string().optional().allow(null, ""),
35751
- inviterUserId: Joi57.string().hex().length(24).required()
35752
- });
35753
- const { error, value } = schema2.validate(
35754
- { inviterUserId: req.params.inviterId, ...req.body },
35755
- { abortEarly: false }
35756
- );
35757
- if (error) {
35758
- const messages = error.details.map((d) => d.message).join(", ");
35759
- logger81.log({ level: "error", message: messages });
35760
- next(new BadRequestError101(messages));
35761
- return;
35762
- }
35763
- const {
35764
- expectedCheckIn,
35765
- arrivalTime,
35766
- duration,
35767
- name,
35768
- contact,
35769
- email,
35770
- plateNumber,
35771
- isOvernightParking,
35772
- numberOfPassengers,
35773
- purpose,
35774
- inviterUserId
35775
- } = value;
35776
- const rest = {
35777
- expectedCheckIn,
35778
- arrivalTime,
35779
- duration,
35780
- name,
35781
- contact,
35782
- email,
35783
- plateNumber,
35784
- isOvernightParking,
35785
- numberOfPassengers,
35786
- purpose
35787
- };
35788
- try {
35789
- const result = await _inviteVisitor(rest, inviterUserId);
35790
- if (result) {
35791
- await resendEmailInvitedResident(result, inviterUserId).catch((error2) => {
35792
- console.error(`Failed to send invitation email: ${error2.message}`);
35793
- logger81.log({ level: "error", message: `Failed to send invitation email: ${error2.message}` });
35794
- });
36020
+ console.log("Open barrier response:", openBarrier);
36021
+ const visitorTransaction = await _getVisitorTransactionById(
36022
+ id.toString()
36023
+ );
36024
+ if (!visitorTransaction) {
36025
+ throw new Error("Visitor transaction not found.");
35795
36026
  }
35796
- res.status(200).json({ insertedId: result });
35797
- return;
35798
- } catch (error2) {
35799
- logger81.log({ level: "error", message: error2.message });
35800
- if (error2?.message) {
35801
- next(new BadRequestError101(error2?.message));
35802
- } else {
35803
- next(error2);
36027
+ if (value.checkOut && visitorTransaction.type === "guest" /* GUEST */) {
36028
+ const users = await getUsersBySiteId({
36029
+ status: "active",
36030
+ siteId: visitorTransaction.site.toString()
36031
+ });
36032
+ if (users?.length > 0) {
36033
+ const userIds = Array.from(
36034
+ new Set(users.map((item) => item.user.toString()))
36035
+ );
36036
+ NotificationService.invitedVisitorCheckOutForMa({
36037
+ to: userIds,
36038
+ name: visitorTransaction.name,
36039
+ _id: id.toString()
36040
+ });
36041
+ }
35804
36042
  }
35805
- }
35806
- }
35807
- return {
35808
- add,
35809
- getAll,
35810
- updateVisitorTansactionById,
35811
- deleteVisitorTransaction,
35812
- inviteVisitor,
35813
- getVisitorTransactionById
35814
- // changeVisitorTransactionKeysById,
35815
- };
35816
- }
35817
-
35818
- // src/models/guest-management.model.ts
35819
- import Joi58 from "joi";
35820
- import { ObjectId as ObjectId64 } from "mongodb";
35821
- var GuestStatus = /* @__PURE__ */ ((GuestStatus2) => {
35822
- GuestStatus2["PENDING"] = "pending";
35823
- GuestStatus2["ACTIVE"] = "active";
35824
- return GuestStatus2;
35825
- })(GuestStatus || {});
35826
- var GuestSort = /* @__PURE__ */ ((GuestSort2) => {
35827
- GuestSort2["CREATED_AT"] = "createdAt";
35828
- GuestSort2["NAME"] = "name";
35829
- GuestSort2["ID"] = "_id";
35830
- return GuestSort2;
35831
- })(GuestSort || {});
35832
- var schemaGuestManagement = Joi58.object({
35833
- name: Joi58.string().required(),
35834
- contact: Joi58.string().optional().allow(null, ""),
35835
- block: Joi58.number().required(),
35836
- level: Joi58.string().required(),
35837
- unit: Joi58.string().required(),
35838
- start: Joi58.date().required(),
35839
- end: Joi58.date().required(),
35840
- status: Joi58.string().optional().allow(null, ""),
35841
- nric: Joi58.string().optional().allow(null, ""),
35842
- remarks: Joi58.string().optional().allow(null, ""),
35843
- org: Joi58.string().hex().length(24).required(),
35844
- site: Joi58.string().hex().length(24).required(),
35845
- plateNumber: Joi58.string().optional().allow(null, ""),
35846
- unitName: Joi58.string().optional().allow(null, "")
35847
- });
35848
- var schemaUpdateGuestManagement = Joi58.object({
35849
- _id: Joi58.string().hex().length(24).required(),
35850
- name: Joi58.string().optional().allow(null, ""),
35851
- contact: Joi58.string().optional().allow(null, ""),
35852
- block: Joi58.number().optional().allow(null, ""),
35853
- level: Joi58.string().optional().allow(null, ""),
35854
- unit: Joi58.string().optional().allow(null, ""),
35855
- start: Joi58.date().optional().allow(null, ""),
35856
- end: Joi58.date().optional().allow(null, ""),
35857
- status: Joi58.string().optional().allow(null, ""),
35858
- unitName: Joi58.string().optional().allow(null, "")
35859
- });
35860
- function MGuestManagement(value) {
35861
- const { error } = schemaGuestManagement.validate(value);
35862
- if (error) {
35863
- throw new Error(error.details[0].message);
35864
- }
35865
- if (value._id && typeof value._id === "string") {
35866
- try {
35867
- value._id = new ObjectId64(value._id);
35868
- } catch (error2) {
35869
- throw new Error("Invalid ID.");
35870
- }
35871
- }
35872
- if (value.org && typeof value.org === "string") {
35873
- try {
35874
- value.org = new ObjectId64(value.org);
35875
- } catch (error2) {
35876
- throw new Error("Invalid org ID.");
35877
- }
35878
- }
35879
- if (value.site && typeof value.site === "string") {
35880
- try {
35881
- value.site = new ObjectId64(value.site);
35882
- } catch (error2) {
35883
- throw new Error("Invalid site ID.");
35884
- }
35885
- }
35886
- return {
35887
- _id: value._id,
35888
- name: value.name,
35889
- contact: value.contact,
35890
- block: value.block,
35891
- level: value.level,
35892
- unit: value.unit,
35893
- start: value.start,
35894
- end: value.end,
35895
- status: value.status ?? "active",
35896
- nric: value.nric,
35897
- remarks: value.remarks,
35898
- org: value.org,
35899
- site: value.site,
35900
- plateNumber: value.plateNumber,
35901
- unitName: value.unitName,
35902
- createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
35903
- updatedAt: value.updatedAt,
35904
- deletedAt: value.deletedAt
35905
- };
35906
- }
35907
-
35908
- // src/repositories/guest-management.repo.ts
35909
- import {
35910
- BadRequestError as BadRequestError102,
35911
- InternalServerError as InternalServerError34,
35912
- logger as logger82,
35913
- makeCacheKey as makeCacheKey31,
35914
- paginate as paginate26,
35915
- useAtlas as useAtlas52,
35916
- useCache as useCache33
35917
- } from "@7365admin1/node-server-utils";
35918
- import { ObjectId as ObjectId65 } from "mongodb";
35919
- var guests_namespace_collection = "visitor.guests";
35920
- function useGuestManagementRepo() {
35921
- const db = useAtlas52.getDb();
35922
- if (!db) {
35923
- throw new InternalServerError34("Unable to connect to server.");
35924
- }
35925
- const collection = db.collection(guests_namespace_collection);
35926
- const { delNamespace, getCache, setCache } = useCache33(
35927
- guests_namespace_collection
35928
- );
35929
- async function createTextIndex() {
35930
- try {
35931
- await collection.createIndex({
35932
- name: "text",
35933
- plateNumber: "text",
35934
- company: "text",
35935
- level: "text",
35936
- unitName: "text",
35937
- contact: "text"
35938
- });
36043
+ await session?.commitTransaction();
36044
+ return "Successfully updated visitor transaction.";
35939
36045
  } catch (error) {
35940
- throw new InternalServerError34("Failed to create text index on guest.");
36046
+ await session?.abortTransaction();
36047
+ throw error;
36048
+ } finally {
36049
+ session?.endSession();
35941
36050
  }
35942
36051
  }
35943
- async function add(value, session) {
36052
+ async function processTransactionDahuaStatus() {
35944
36053
  try {
35945
- value = MGuestManagement(value);
35946
- const res = await collection.insertOne(value, { session });
35947
- delNamespace().then(() => {
35948
- logger82.info(
35949
- `Cache cleared for namespace: ${guests_namespace_collection}`
35950
- );
35951
- }).catch((err) => {
35952
- logger82.error(
35953
- `Failed to clear cache for namespace: ${guests_namespace_collection}`,
35954
- err
35955
- );
35956
- });
35957
- return res.insertedId;
35958
- } catch (error) {
35959
- const isDuplicated = error.message.includes("duplicate");
35960
- if (isDuplicated) {
35961
- throw new BadRequestError102("Visitor already exists.");
36054
+ const limit = 100;
36055
+ let page = 1;
36056
+ let hasMore = true;
36057
+ while (hasMore) {
36058
+ const { items: sites } = await _getAllSites({
36059
+ page,
36060
+ limit
36061
+ });
36062
+ if (!sites.length) {
36063
+ hasMore = false;
36064
+ break;
36065
+ }
36066
+ for (const site of sites) {
36067
+ const siteId = site._id;
36068
+ const camera = await _getBySite(siteId, {
36069
+ type: "anpr" /* ANPR */
36070
+ });
36071
+ if (!camera)
36072
+ continue;
36073
+ const transactions = await _getExpiredCheckedOutTransactionsBySite(
36074
+ siteId
36075
+ );
36076
+ if (!transactions.length)
36077
+ continue;
36078
+ const successfulIds = [];
36079
+ const batchSize = 5;
36080
+ for (let i = 0; i < transactions.length; i += batchSize) {
36081
+ const batch = transactions.slice(i, i + batchSize);
36082
+ await Promise.all(
36083
+ batch.map(async (transaction) => {
36084
+ try {
36085
+ const dahuaPayload = {
36086
+ host: camera.host,
36087
+ username: camera.username,
36088
+ password: camera.password,
36089
+ mode: "TrafficRedList" /* TRAFFIC_REDLIST */,
36090
+ recno: transaction.recNo
36091
+ };
36092
+ await _removePlateNumber(dahuaPayload);
36093
+ successfulIds.push(transaction._id);
36094
+ } catch (error) {
36095
+ const message = String(error?.message || "").toLowerCase();
36096
+ const isNotFound = message.includes("not found") || message.includes("does not exist") || message.includes("no record");
36097
+ if (isNotFound) {
36098
+ logger81.warn(
36099
+ `Dahua record already missing for transaction ${transaction._id}, marking as removed.`
36100
+ );
36101
+ successfulIds.push(transaction._id);
36102
+ return;
36103
+ }
36104
+ logger81.error(
36105
+ `Failed to remove plate for transaction ${transaction._id}`,
36106
+ error
36107
+ );
36108
+ }
36109
+ })
36110
+ );
36111
+ }
36112
+ if (successfulIds.length > 0) {
36113
+ await _updateManyDahuaSyncStatus(successfulIds, "removed");
36114
+ }
36115
+ }
36116
+ page++;
35962
36117
  }
36118
+ } catch (error) {
35963
36119
  throw error;
35964
36120
  }
35965
36121
  }
35966
- async function getAll({
35967
- search = "",
35968
- page = 1,
35969
- limit = 10,
35970
- sort = {},
35971
- status = "active",
35972
- dateFrom,
35973
- dateTo,
35974
- org = "",
35975
- site = ""
35976
- }, session) {
35977
- page = page > 0 ? page - 1 : 0;
35978
- const start = dateFrom ? { $gte: new Date(dateFrom).toISOString() } : void 0;
35979
- const end = dateTo ? {
35980
- $or: [
35981
- { end: { $lte: new Date(dateTo).toISOString() } },
35982
- { end: null }
35983
- ]
35984
- } : void 0;
35985
- const query = {
35986
- status,
35987
- ...start && { start },
35988
- ...end,
35989
- ...search && { $text: { $search: search } },
35990
- ...ObjectId65.isValid(org) && { org: new ObjectId65(org) },
35991
- ...ObjectId65.isValid(site) && { site: new ObjectId65(site) }
35992
- };
35993
- sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
35994
- const cacheOptions = {
35995
- sort: JSON.stringify(sort),
35996
- page,
35997
- limit,
35998
- status,
35999
- ...start && { start: JSON.stringify(start) },
36000
- ...dateTo && { end: JSON.stringify(end) },
36001
- ...search && { search },
36002
- ...query.org && { org: query.org.toString() },
36003
- ...query.site && { site: query.site.toString() }
36004
- };
36005
- const cacheKey = makeCacheKey31(guests_namespace_collection, cacheOptions);
36006
- const cachedData = await getCache(cacheKey);
36007
- if (cachedData) {
36008
- logger82.info(`Cache hit for key: ${cacheKey}`);
36009
- return cachedData;
36122
+ async function inviteVisitor(value, userId) {
36123
+ const session = useAtlas52.getClient()?.startSession();
36124
+ if (!session) {
36125
+ throw new Error("Unable to start session for people service.");
36010
36126
  }
36011
36127
  try {
36012
- const basePipeline = [
36013
- { $match: query },
36014
- { $sort: sort },
36015
- { $skip: page * limit },
36016
- { $limit: limit }
36017
- ];
36018
- const [items, countResult] = await Promise.all([
36019
- collection.aggregate(basePipeline, { session }).toArray(),
36020
- collection.aggregate([{ $match: query }, { $count: "total" }], { session }).toArray()
36021
- ]);
36022
- const totalCount = countResult[0]?.total || 0;
36023
- const data = paginate26(items, page, limit, totalCount);
36024
- setCache(cacheKey, data, 15 * 60).then(() => logger82.info(`Cache set for key: ${cacheKey}`)).catch(
36025
- (err) => logger82.error(`Failed to set cache for key: ${cacheKey}`, err)
36026
- );
36027
- return data;
36128
+ session.startTransaction();
36129
+ const inviter = await _getByUserId(userId);
36130
+ const unit = await getById(inviter?.unit);
36131
+ value.checkIn = null;
36132
+ const payload = {
36133
+ ...value,
36134
+ block: inviter?.block?.toString(),
36135
+ level: inviter?.level?.toString(),
36136
+ unit: inviter?.unit?.toString(),
36137
+ unitName: unit?.name,
36138
+ org: inviter?.org?.toString(),
36139
+ site: inviter?.site.toString(),
36140
+ status: "approved" /* APPROVED */,
36141
+ inviterId: userId
36142
+ };
36143
+ const result = await _add(payload);
36144
+ await session?.commitTransaction();
36145
+ return result;
36028
36146
  } catch (error) {
36147
+ logger81.error("Error in visitor transaction invite visitor:", error);
36148
+ await session.abortTransaction();
36029
36149
  throw error;
36150
+ } finally {
36151
+ session?.endSession();
36030
36152
  }
36031
36153
  }
36032
- async function updateVisitorGuestById(_id, value, session) {
36154
+ return {
36155
+ add,
36156
+ updateVisitorTransactionById,
36157
+ processTransactionDahuaStatus,
36158
+ inviteVisitor
36159
+ // changeVisitorTransactionKeysById,
36160
+ };
36161
+ }
36162
+
36163
+ // src/controllers/visitor-transaction.controller.ts
36164
+ import Joi57 from "joi";
36165
+ import { BadRequestError as BadRequestError102, logger as logger82 } from "@7365admin1/node-server-utils";
36166
+ function useVisitorTransactionController() {
36167
+ const {
36168
+ add: _add,
36169
+ updateVisitorTransactionById: _updateVisitorTransactionById,
36170
+ inviteVisitor: _inviteVisitor
36171
+ // changeVisitorTransactionKeysById: _changeVisitorTransactionKeysById,
36172
+ } = useVisitorTransactionService();
36173
+ const {
36174
+ getAll: _getAll,
36175
+ deleteVisitorTransaction: _deleteVisitorTransaction,
36176
+ getVisitorTransactionById: _getVisitorTransactionById,
36177
+ resendEmailInvitedResident
36178
+ } = useVisitorTransactionRepo();
36179
+ async function add(req, res, next) {
36033
36180
  try {
36034
- _id = new ObjectId65(_id);
36181
+ const { companyName, firstName, lastName, ...restData } = req.body;
36182
+ const { error, value } = schemaVisitorTransaction.validate(restData, {
36183
+ abortEarly: false
36184
+ });
36185
+ if (error) {
36186
+ const messages = error.details.map((d) => d.message).join(", ");
36187
+ logger82.log({ level: "error", message: messages });
36188
+ next(new BadRequestError102(messages));
36189
+ return;
36190
+ }
36191
+ const data = await _add(value);
36192
+ res.status(201).json(data);
36193
+ return;
36035
36194
  } catch (error) {
36036
- throw new BadRequestError102("Invalid visitor transaction ID format.");
36195
+ logger82.log({ level: "error", message: error.message });
36196
+ console.log("error", error);
36197
+ if (error?.message) {
36198
+ next(new BadRequestError102(error?.message));
36199
+ } else {
36200
+ next(error);
36201
+ }
36202
+ return;
36037
36203
  }
36038
- value.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
36204
+ }
36205
+ async function getAll(req, res, next) {
36039
36206
  try {
36040
- const result = await collection.updateOne(
36041
- { _id },
36042
- { $set: value },
36043
- { session }
36044
- );
36045
- delNamespace().then(() => {
36046
- logger82.info(
36047
- `Cache cleared for namespace: ${guests_namespace_collection}`
36048
- );
36049
- }).catch((err) => {
36050
- logger82.error(
36051
- `Failed to clear cache for namespace: ${guests_namespace_collection}`,
36052
- err
36053
- );
36207
+ const validation = Joi57.object({
36208
+ search: Joi57.string().optional().allow(null, ""),
36209
+ page: Joi57.number().integer().min(1).allow("", null).default(1),
36210
+ limit: Joi57.number().integer().min(1).max(100).allow("", null).default(10),
36211
+ sort: Joi57.string().valid(...Object.values(VisitorSort)).optional().allow(null, ""),
36212
+ status: Joi57.string().valid(...Object.values(VisitorStatus)).optional().allow(null, ""),
36213
+ org: Joi57.string().hex().length(24).optional().allow(null, ""),
36214
+ site: Joi57.string().hex().length(24).optional().allow(null, ""),
36215
+ dateFrom: Joi57.date().iso().optional().allow(null, ""),
36216
+ dateTo: Joi57.date().iso().optional().allow(null, ""),
36217
+ type: Joi57.alternatives().try(
36218
+ Joi57.array().items(
36219
+ Joi57.string().valid(...Object.values(PersonTypes))
36220
+ ),
36221
+ Joi57.string().custom((value2) => {
36222
+ return value2.split(",").map((t) => t.trim());
36223
+ })
36224
+ ).custom((value2) => {
36225
+ return Array.isArray(value2) ? value2 : [value2];
36226
+ }).optional().allow(null, ""),
36227
+ checkedOut: Joi57.boolean().allow(null, ""),
36228
+ plateNumber: Joi57.string().optional().allow(null, ""),
36229
+ order: Joi57.string().valid(...Object.values(SortOrder)).default("asc" /* ASC */),
36230
+ tab: Joi57.string().optional().allow(null, "")
36231
+ });
36232
+ const { error, value } = validation.validate(req.query, {
36233
+ abortEarly: false
36234
+ });
36235
+ if (error) {
36236
+ const messages = error.details.map((d) => d.message).join(", ");
36237
+ logger82.log({ level: "error", message: messages });
36238
+ next(new BadRequestError102(messages));
36239
+ return;
36240
+ }
36241
+ const {
36242
+ org,
36243
+ site,
36244
+ search,
36245
+ page,
36246
+ limit,
36247
+ status,
36248
+ dateTo,
36249
+ dateFrom,
36250
+ type,
36251
+ checkedOut,
36252
+ sort,
36253
+ order,
36254
+ plateNumber,
36255
+ tab
36256
+ } = value;
36257
+ const sortObj = {
36258
+ [sort ? sort : "_id" /* ID */]: order === "asc" /* ASC */ ? 1 : -1
36259
+ };
36260
+ const data = await _getAll({
36261
+ org,
36262
+ site,
36263
+ search,
36264
+ page,
36265
+ limit,
36266
+ sort: sortObj,
36267
+ status,
36268
+ dateTo,
36269
+ dateFrom,
36270
+ type,
36271
+ checkedOut,
36272
+ plateNumber,
36273
+ tab
36054
36274
  });
36055
- return result;
36275
+ res.status(200).json(data);
36276
+ return;
36056
36277
  } catch (error) {
36057
- throw error;
36278
+ logger82.log({ level: "error", message: error.message });
36279
+ next(error);
36280
+ return;
36058
36281
  }
36059
36282
  }
36060
- async function deleteVisitorGuest(_id) {
36061
- try {
36062
- _id = new ObjectId65(_id);
36063
- } catch (error) {
36064
- throw new BadRequestError102("Invalid customer ID format.");
36065
- }
36283
+ async function getVisitorTransactionById(req, res, next) {
36066
36284
  try {
36067
- const updateValue = {
36068
- status: "deleted",
36069
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
36070
- deletedAt: (/* @__PURE__ */ new Date()).toISOString()
36071
- };
36072
- const res = await collection.updateOne({ _id }, { $set: updateValue });
36073
- if (res.modifiedCount === 0) {
36074
- throw new InternalServerError34("Unable to delete visitor transaction.");
36075
- }
36076
- delNamespace().then(() => {
36077
- logger82.info(
36078
- `Cache cleared for namespace: ${guests_namespace_collection}`
36079
- );
36080
- }).catch((err) => {
36081
- logger82.error(
36082
- `Failed to clear cache for namespace: ${guests_namespace_collection}`,
36083
- err
36084
- );
36285
+ const schema2 = Joi57.object({
36286
+ _id: Joi57.string().hex().length(24).required()
36085
36287
  });
36086
- return res.modifiedCount;
36288
+ const { error, value } = schema2.validate(
36289
+ { _id: req.params.id },
36290
+ {
36291
+ abortEarly: false
36292
+ }
36293
+ );
36294
+ if (error) {
36295
+ const messages = error.details.map((d) => d.message).join(", ");
36296
+ logger82.log({ level: "error", message: messages });
36297
+ next(new BadRequestError102(messages));
36298
+ return;
36299
+ }
36300
+ const { _id } = value;
36301
+ const data = await _getVisitorTransactionById(_id);
36302
+ res.status(200).json(data);
36303
+ return;
36087
36304
  } catch (error) {
36088
- throw error;
36305
+ logger82.log({ level: "error", message: error.message });
36306
+ next(error);
36307
+ return;
36089
36308
  }
36090
36309
  }
36091
- return {
36092
- add,
36093
- getAll,
36094
- updateVisitorGuestById,
36095
- deleteVisitorGuest,
36096
- createTextIndex
36097
- };
36098
- }
36099
-
36100
- // src/services/guest-management.service.ts
36101
- import { useAtlas as useAtlas53 } from "@7365admin1/node-server-utils";
36102
- function useGuestManagementService() {
36103
- const { add: _add, updateVisitorGuestById: _updateVisitorGuestById } = useGuestManagementRepo();
36104
- async function add(value) {
36105
- const session = useAtlas53.getClient()?.startSession();
36106
- session?.startTransaction();
36107
- try {
36108
- await _add(value, session);
36109
- await session?.commitTransaction();
36110
- return "Successfully added guest.";
36111
- } catch (error) {
36112
- await session?.abortTransaction();
36113
- throw error;
36114
- } finally {
36115
- session?.endSession();
36310
+ async function updateVisitorTansactionById(req, res, next) {
36311
+ const _id = req.params.id;
36312
+ const payload = { _id, ...req.body };
36313
+ const { error } = schemaUpdateVisTrans.validate(payload, {
36314
+ abortEarly: false
36315
+ });
36316
+ if (error) {
36317
+ const messages = error.details.map((d) => d.message).join(", ");
36318
+ logger82.log({ level: "error", message: messages });
36319
+ next(new BadRequestError102(messages));
36320
+ return;
36116
36321
  }
36117
- }
36118
- async function updateVisitorGuestById(id, value) {
36119
- const session = useAtlas53.getClient()?.startSession();
36120
- session?.startTransaction();
36121
36322
  try {
36122
- await _updateVisitorGuestById(id, value, session);
36123
- await session?.commitTransaction();
36124
- return "Successfully updated guest.";
36125
- } catch (error) {
36126
- await session?.abortTransaction();
36127
- throw error;
36128
- } finally {
36129
- session?.endSession();
36323
+ await _updateVisitorTransactionById(_id, req.body);
36324
+ res.status(200).json({ message: "Successfully updated visitor transaction." });
36325
+ return;
36326
+ } catch (error2) {
36327
+ logger82.log({ level: "error", message: error2.message });
36328
+ console.log("error", error2);
36329
+ if (error2?.message) {
36330
+ next(new BadRequestError102(error2?.message));
36331
+ } else {
36332
+ next(error2);
36333
+ }
36334
+ return;
36130
36335
  }
36131
36336
  }
36132
- return {
36133
- add,
36134
- updateVisitorGuestById
36135
- };
36136
- }
36137
-
36138
- // src/controllers/guest-management.controller.ts
36139
- import Joi59 from "joi";
36140
- import { BadRequestError as BadRequestError103, logger as logger83 } from "@7365admin1/node-server-utils";
36141
- function useGuestManagementController() {
36142
- const { add: _add, updateVisitorGuestById: _updateVisitorGuestById } = useGuestManagementService();
36143
- const { getAll: _getAll, deleteVisitorGuest: _deleteVisitorGuest } = useGuestManagementRepo();
36144
- async function add(req, res, next) {
36145
- const payload = { ...req.body };
36146
- const { error } = schemaGuestManagement.validate(payload, {
36147
- abortEarly: false
36148
- });
36337
+ async function deleteVisitorTransaction(req, res, next) {
36338
+ const validation = Joi57.string().hex().required();
36339
+ const _id = req.params.id;
36340
+ const { error } = validation.validate(_id);
36149
36341
  if (error) {
36150
- const messages = error.details.map((d) => d.message).join(", ");
36151
- logger83.log({ level: "error", message: messages });
36152
- next(new BadRequestError103(messages));
36342
+ logger82.log({ level: "error", message: error.message });
36343
+ next(new BadRequestError102(error.message));
36153
36344
  return;
36154
36345
  }
36155
36346
  try {
36156
- const data = await _add(payload);
36157
- res.status(201).json({ message: data });
36347
+ await _deleteVisitorTransaction(_id);
36348
+ res.status(200).json({ message: "Successfully deleted visitor transaction." });
36158
36349
  return;
36159
36350
  } catch (error2) {
36160
- logger83.log({ level: "error", message: error2.message });
36351
+ logger82.log({ level: "error", message: error2.message });
36161
36352
  next(error2);
36162
36353
  return;
36163
36354
  }
36164
36355
  }
36165
- async function getAll(req, res, next) {
36166
- const allowedFields = ["createdAt", "name"];
36167
- const validation = Joi59.object({
36168
- page: Joi59.number().integer().min(1).allow("", null).default(1),
36169
- limit: Joi59.number().integer().min(1).max(100).allow("", null).default(10),
36170
- sort: Joi59.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
36171
- status: Joi59.string().optional().allow(null, ""),
36172
- search: Joi59.string().optional().allow(null, ""),
36173
- dateFrom: Joi59.date().iso().optional().allow(null, ""),
36174
- dateTo: Joi59.date().iso().optional().allow(null, ""),
36175
- org: Joi59.string().optional().allow(null, ""),
36176
- site: Joi59.string().optional().allow(null, "")
36177
- });
36178
- const query = { ...req.query };
36179
- const { error } = validation.validate(query, {
36180
- abortEarly: false
36356
+ async function inviteVisitor(req, res, next) {
36357
+ const schema2 = Joi57.object({
36358
+ expectedCheckIn: Joi57.string().isoDate().required(),
36359
+ arrivalTime: Joi57.string().pattern(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().allow(null, "").messages({
36360
+ "string.pattern.base": "arrivalTime must be in HH:mm format (e.g. 09:30, 18:45)"
36361
+ }),
36362
+ duration: Joi57.number().integer().optional().allow(null, ""),
36363
+ name: Joi57.string().required(),
36364
+ contact: Joi57.string().optional().allow(null, ""),
36365
+ email: Joi57.string().email().optional().allow(null, ""),
36366
+ plateNumber: Joi57.string().optional().allow(null, ""),
36367
+ isOvernightParking: Joi57.boolean().optional().default(false),
36368
+ numberOfPassengers: Joi57.number().integer().optional().allow(null, ""),
36369
+ purpose: Joi57.string().optional().allow(null, ""),
36370
+ inviterUserId: Joi57.string().hex().length(24).required()
36181
36371
  });
36372
+ const { error, value } = schema2.validate(
36373
+ { inviterUserId: req.params.inviterId, ...req.body },
36374
+ { abortEarly: false }
36375
+ );
36182
36376
  if (error) {
36183
36377
  const messages = error.details.map((d) => d.message).join(", ");
36184
- logger83.log({ level: "error", message: messages });
36185
- next(new BadRequestError103(messages));
36378
+ logger82.log({ level: "error", message: messages });
36379
+ next(new BadRequestError102(messages));
36186
36380
  return;
36187
36381
  }
36188
- const search = req.query.search ?? "";
36189
- const page = parseInt(req.query.page ?? "1");
36190
- const limit = parseInt(req.query.limit ?? "10");
36191
- const status = req.query.status ?? "active";
36192
- const dateTo = req.query.dateTo;
36193
- const dateFrom = req.query.dateFrom;
36194
- const org = req.query.org;
36195
- const site = req.query.site;
36196
- const sortObj = {};
36197
- const sortFields = String(req.query.sort).split(",");
36198
- const sortOrders = String(req.query.order).split(",");
36199
- sortFields.forEach((field, index) => {
36200
- if (allowedFields.includes(field)) {
36201
- const order = sortOrders[index] === "asc" ? 1 : -1;
36202
- sortObj[field] = order;
36382
+ const {
36383
+ expectedCheckIn,
36384
+ arrivalTime,
36385
+ duration,
36386
+ name,
36387
+ contact,
36388
+ email,
36389
+ plateNumber,
36390
+ isOvernightParking,
36391
+ numberOfPassengers,
36392
+ purpose,
36393
+ inviterUserId
36394
+ } = value;
36395
+ const rest = {
36396
+ expectedCheckIn,
36397
+ arrivalTime,
36398
+ duration,
36399
+ name,
36400
+ contact,
36401
+ email,
36402
+ plateNumber,
36403
+ isOvernightParking,
36404
+ numberOfPassengers,
36405
+ purpose
36406
+ };
36407
+ try {
36408
+ const result = await _inviteVisitor(rest, inviterUserId);
36409
+ if (result) {
36410
+ await resendEmailInvitedResident(result, inviterUserId).catch((error2) => {
36411
+ console.error(`Failed to send invitation email: ${error2.message}`);
36412
+ logger82.log({ level: "error", message: `Failed to send invitation email: ${error2.message}` });
36413
+ });
36203
36414
  }
36204
- });
36415
+ res.status(200).json({ insertedId: result });
36416
+ return;
36417
+ } catch (error2) {
36418
+ logger82.log({ level: "error", message: error2.message });
36419
+ if (error2?.message) {
36420
+ next(new BadRequestError102(error2?.message));
36421
+ } else {
36422
+ next(error2);
36423
+ }
36424
+ }
36425
+ }
36426
+ return {
36427
+ add,
36428
+ getAll,
36429
+ updateVisitorTansactionById,
36430
+ deleteVisitorTransaction,
36431
+ inviteVisitor,
36432
+ getVisitorTransactionById
36433
+ // changeVisitorTransactionKeysById,
36434
+ };
36435
+ }
36436
+
36437
+ // src/models/guest-management.model.ts
36438
+ import Joi58 from "joi";
36439
+ import { ObjectId as ObjectId65 } from "mongodb";
36440
+ var GuestStatus = /* @__PURE__ */ ((GuestStatus2) => {
36441
+ GuestStatus2["PENDING"] = "pending";
36442
+ GuestStatus2["ACTIVE"] = "active";
36443
+ return GuestStatus2;
36444
+ })(GuestStatus || {});
36445
+ var GuestSort = /* @__PURE__ */ ((GuestSort2) => {
36446
+ GuestSort2["CREATED_AT"] = "createdAt";
36447
+ GuestSort2["NAME"] = "name";
36448
+ GuestSort2["ID"] = "_id";
36449
+ return GuestSort2;
36450
+ })(GuestSort || {});
36451
+ var schemaGuestManagement = Joi58.object({
36452
+ name: Joi58.string().required(),
36453
+ contact: Joi58.string().optional().allow(null, ""),
36454
+ block: Joi58.number().required(),
36455
+ level: Joi58.string().required(),
36456
+ unit: Joi58.string().required(),
36457
+ start: Joi58.date().required(),
36458
+ end: Joi58.date().required(),
36459
+ status: Joi58.string().optional().allow(null, ""),
36460
+ nric: Joi58.string().optional().allow(null, ""),
36461
+ remarks: Joi58.string().optional().allow(null, ""),
36462
+ org: Joi58.string().hex().length(24).required(),
36463
+ site: Joi58.string().hex().length(24).required(),
36464
+ plateNumber: Joi58.string().optional().allow(null, ""),
36465
+ unitName: Joi58.string().optional().allow(null, "")
36466
+ });
36467
+ var schemaUpdateGuestManagement = Joi58.object({
36468
+ _id: Joi58.string().hex().length(24).required(),
36469
+ name: Joi58.string().optional().allow(null, ""),
36470
+ contact: Joi58.string().optional().allow(null, ""),
36471
+ block: Joi58.number().optional().allow(null, ""),
36472
+ level: Joi58.string().optional().allow(null, ""),
36473
+ unit: Joi58.string().optional().allow(null, ""),
36474
+ start: Joi58.date().optional().allow(null, ""),
36475
+ end: Joi58.date().optional().allow(null, ""),
36476
+ status: Joi58.string().optional().allow(null, ""),
36477
+ unitName: Joi58.string().optional().allow(null, "")
36478
+ });
36479
+ function MGuestManagement(value) {
36480
+ const { error } = schemaGuestManagement.validate(value);
36481
+ if (error) {
36482
+ throw new Error(error.details[0].message);
36483
+ }
36484
+ if (value._id && typeof value._id === "string") {
36205
36485
  try {
36206
- const data = await _getAll({
36207
- search,
36208
- page,
36209
- limit,
36210
- sort: sortObj,
36211
- status,
36212
- dateFrom,
36213
- dateTo,
36214
- org,
36215
- site
36216
- });
36217
- res.status(200).json(data);
36218
- return;
36486
+ value._id = new ObjectId65(value._id);
36219
36487
  } catch (error2) {
36220
- logger83.log({ level: "error", message: error2.message });
36221
- next(error2);
36222
- return;
36488
+ throw new Error("Invalid ID.");
36223
36489
  }
36224
36490
  }
36225
- async function updateVisitorGuestById(req, res, next) {
36226
- const _id = req.params.id;
36227
- const payload = { _id, ...req.body };
36228
- const { error } = schemaUpdateGuestManagement.validate(payload, {
36229
- abortEarly: false
36230
- });
36231
- if (error) {
36232
- const messages = error.details.map((d) => d.message).join(", ");
36233
- logger83.log({ level: "error", message: messages });
36234
- next(new BadRequestError103(messages));
36235
- return;
36236
- }
36491
+ if (value.org && typeof value.org === "string") {
36237
36492
  try {
36238
- const result = await _updateVisitorGuestById(_id, req.body);
36239
- res.status(200).json({ message: result });
36240
- return;
36493
+ value.org = new ObjectId65(value.org);
36241
36494
  } catch (error2) {
36242
- logger83.log({ level: "error", message: error2.message });
36243
- next(error2);
36244
- return;
36495
+ throw new Error("Invalid org ID.");
36245
36496
  }
36246
36497
  }
36247
- async function deleteVisitorGuest(req, res, next) {
36248
- const validation = Joi59.string().hex().required();
36249
- const _id = req.params.id;
36250
- const { error } = validation.validate(_id);
36251
- if (error) {
36252
- logger83.log({ level: "error", message: error.message });
36253
- next(new BadRequestError103(error.message));
36254
- return;
36255
- }
36498
+ if (value.site && typeof value.site === "string") {
36256
36499
  try {
36257
- await _deleteVisitorGuest(_id);
36258
- res.status(200).json({ message: "Successfully deleted visitor guest." });
36259
- return;
36500
+ value.site = new ObjectId65(value.site);
36260
36501
  } catch (error2) {
36261
- logger83.log({ level: "error", message: error2.message });
36262
- next(error2);
36263
- return;
36502
+ throw new Error("Invalid site ID.");
36264
36503
  }
36265
36504
  }
36266
36505
  return {
36267
- add,
36268
- getAll,
36269
- updateVisitorGuestById,
36270
- deleteVisitorGuest
36506
+ _id: value._id,
36507
+ name: value.name,
36508
+ contact: value.contact,
36509
+ block: value.block,
36510
+ level: value.level,
36511
+ unit: value.unit,
36512
+ start: value.start,
36513
+ end: value.end,
36514
+ status: value.status ?? "active",
36515
+ nric: value.nric,
36516
+ remarks: value.remarks,
36517
+ org: value.org,
36518
+ site: value.site,
36519
+ plateNumber: value.plateNumber,
36520
+ unitName: value.unitName,
36521
+ createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
36522
+ updatedAt: value.updatedAt,
36523
+ deletedAt: value.deletedAt
36271
36524
  };
36272
36525
  }
36273
36526
 
36274
- // src/controllers/person.controller.ts
36275
- import Joi60 from "joi";
36276
- import { BadRequestError as BadRequestError105, logger as logger85 } from "@7365admin1/node-server-utils";
36277
-
36278
- // src/services/person.service.ts
36527
+ // src/repositories/guest-management.repo.ts
36279
36528
  import {
36280
- BadRequestError as BadRequestError104,
36281
- logger as logger84,
36282
- NotFoundError as NotFoundError23,
36283
- useAtlas as useAtlas54,
36284
- hashPassword as hashPassword3,
36285
- useMailer as useMailer3,
36286
- compileHandlebar as compileHandlebar3,
36287
- getDirectory as getDirectory3
36529
+ BadRequestError as BadRequestError103,
36530
+ InternalServerError as InternalServerError34,
36531
+ logger as logger83,
36532
+ makeCacheKey as makeCacheKey31,
36533
+ paginate as paginate26,
36534
+ useAtlas as useAtlas53,
36535
+ useCache as useCache33
36288
36536
  } from "@7365admin1/node-server-utils";
36289
36537
  import { ObjectId as ObjectId66 } from "mongodb";
36290
- function usePersonService() {
36291
- const MailerConfig = {
36292
- host: MAILER_TRANSPORT_HOST,
36293
- port: MAILER_TRANSPORT_PORT,
36294
- secure: MAILER_TRANSPORT_SECURE,
36295
- email: MAILER_EMAIL,
36296
- from: MAILER_FROM_EMAIL,
36297
- password: MAILER_PASSWORD
36298
- };
36299
- const mailer = new useMailer3(MailerConfig);
36300
- const {
36301
- add: _add,
36302
- getById: _getById,
36303
- updateById: _updateById,
36304
- getByUserId: _getByUserId,
36305
- reviewResidentPerson: _reviewResidentPerson,
36306
- getApprovedPersonByPlateNumber
36307
- } = usePersonRepo();
36308
- const { addPlateNumber: _addPlateNumber } = useDahuaService();
36309
- const {
36310
- createUser: addUser,
36311
- getUserByEmail,
36312
- updateUserFieldById: _updateUserFieldById,
36313
- getUserById,
36314
- updateUserOrgById: _updateUserOrgById,
36315
- updateUserUnitById: _updateUserUnitById
36316
- } = useUserRepo();
36317
- const { add: addMember } = useMemberRepo();
36318
- const { getById: _getUnitById, updateById: updateUnitById } = useBuildingUnitRepo();
36319
- const { getAllCameraWithPassword: _getAllSiteCameras, getCamerasBySiteId } = useSiteCameraRepo();
36320
- const { updateStatusById } = useFileRepo();
36321
- const { getById: getOrgById } = useOrgRepo();
36322
- const { getSiteById } = useSiteRepo();
36323
- const { add: addVehicle } = useVehicleService();
36324
- async function add(value) {
36325
- const session = useAtlas54.getClient()?.startSession();
36326
- if (!session) {
36327
- throw new Error("Unable to start session for people service.");
36328
- }
36538
+ var guests_namespace_collection = "visitor.guests";
36539
+ function useGuestManagementRepo() {
36540
+ const db = useAtlas53.getDb();
36541
+ if (!db) {
36542
+ throw new InternalServerError34("Unable to connect to server.");
36543
+ }
36544
+ const collection = db.collection(guests_namespace_collection);
36545
+ const { delNamespace, getCache, setCache } = useCache33(
36546
+ guests_namespace_collection
36547
+ );
36548
+ async function createTextIndex() {
36329
36549
  try {
36330
- session.startTransaction();
36331
- const personFiles = value?.files ?? [];
36332
- if (personFiles.length > 0) {
36333
- for (const personFile of personFiles) {
36334
- const file = await updateStatusById(
36335
- personFile.id,
36336
- { status: "active" },
36337
- session
36338
- );
36339
- if (!file) {
36340
- throw new NotFoundError23("File not found.");
36341
- }
36342
- }
36343
- }
36344
- const isValidType = ["resident" /* RESIDENT */, "tenant" /* TENANT */].includes(
36345
- value?.type
36346
- );
36347
- if (value.email && value.password && value.name && isValidType) {
36348
- const _user = await getUserByEmail(value.email);
36349
- if (_user) {
36350
- throw new BadRequestError104(`User already exists: ${value.email}.`);
36351
- }
36352
- const hashedPassword = await hashPassword3(value.password);
36353
- const user = {
36354
- email: value.email,
36355
- password: hashedPassword,
36356
- name: value.name,
36357
- status: value.platform == "mobile" ? "pending" : "active",
36358
- defaultOrg: value.org?.toString() || "",
36359
- block: value.block,
36360
- level: value.level,
36361
- type: value.type,
36362
- unitName: value.unitName,
36363
- contact: value.contact,
36364
- site: value.site?.toString() || "",
36365
- unitId: value.unit?.toString() || ""
36366
- };
36367
- const userId = await addUser(user, session);
36368
- value.user = userId.toString();
36369
- let org = null;
36370
- if (userId) {
36371
- if (value?.org) {
36372
- org = await getOrgById(value.org);
36373
- if (!org) {
36374
- value.org = "";
36375
- }
36376
- }
36377
- let site;
36378
- if (value.site) {
36379
- site = await getSiteById(value.site);
36380
- }
36381
- await addMember(
36382
- {
36383
- org: value.org?.toString() || "",
36384
- orgName: org?.name || "",
36385
- user: userId.toString(),
36386
- name: value.name,
36387
- role: "",
36388
- type: "resident",
36389
- siteId: value.site?.toString() || "",
36390
- siteName: site?.name || ""
36391
- },
36392
- session
36393
- );
36394
- }
36395
- }
36396
- if (value.isOwner && value.unit) {
36397
- const unit = await _getUnitById(value.unit.toString());
36398
- await updateUnitById(
36399
- unit?._id?.toString() || "",
36400
- { ownerName: value.name, owner: value._id },
36401
- session
36402
- );
36403
- }
36404
- await _add(value, session);
36405
- await session.commitTransaction();
36406
- if (value.email && isValidType && value.platform === "mobile") {
36407
- const emailContent = compileHandlebar3({
36408
- context: { name: value.name },
36409
- filePath: getDirectory3(
36410
- __dirname,
36411
- "./public/handlebars/registration-pending"
36412
- )
36413
- });
36414
- await mailer.sendMail({
36415
- to: value.email,
36416
- subject: "Registration Received \u2013 Pending Review",
36417
- html: emailContent,
36418
- sender: "iService365"
36419
- }).catch((error) => {
36420
- logger84.error(`Failed to send registration pending email: ${error}`);
36421
- });
36422
- }
36423
- return "People added successfully.";
36424
- } catch (error) {
36425
- logger84.error("Error in people service add:", error);
36426
- await session.abortTransaction();
36427
- throw error;
36428
- } finally {
36429
- session.endSession();
36550
+ await collection.createIndex({
36551
+ name: "text",
36552
+ plateNumber: "text",
36553
+ company: "text",
36554
+ level: "text",
36555
+ unitName: "text",
36556
+ contact: "text"
36557
+ });
36558
+ } catch (error) {
36559
+ throw new InternalServerError34("Failed to create text index on guest.");
36430
36560
  }
36431
36561
  }
36432
- async function updateById(_id, value) {
36433
- const session = useAtlas54.getClient()?.startSession();
36434
- if (!session) {
36435
- throw new BadRequestError104("Database session not available.");
36436
- }
36562
+ async function add(value, session) {
36437
36563
  try {
36438
- await session.startTransaction();
36439
- const person = await _getById(_id.toString());
36440
- if (!person) {
36441
- throw new BadRequestError104("Person not found.");
36442
- }
36443
- const isNameUpdated = typeof value.name === "string" && value.name.trim() !== person.name;
36444
- const isOwnerChanged = value.isOwner !== person.isOwner;
36445
- if (value.org && typeof value.org === "string") {
36446
- value.org = new ObjectId66(value.org);
36447
- }
36448
- if (value.site && typeof value.site === "string") {
36449
- value.site = new ObjectId66(value.site);
36450
- }
36451
- if (value.block && typeof value.block === "string") {
36452
- value.block = new ObjectId66(value.block);
36453
- }
36454
- if (value.level && typeof value.level === "string") {
36455
- value.level = new ObjectId66(value.level);
36456
- }
36457
- if (value.unit && typeof value.unit === "string") {
36458
- value.unit = new ObjectId66(value.unit);
36459
- }
36460
- const isOrgChanged = value.org && person.org?.toString() !== value.org.toString();
36461
- const toKey = (v) => v === null || v === void 0 || v === "" ? "" : v.toString();
36462
- const isBlockChanged = "block" in value && toKey(value.block) !== toKey(person.block);
36463
- const isLevelChanged = "level" in value && toKey(value.level) !== toKey(person.level);
36464
- const isUnitChanged = "unit" in value && toKey(value.unit) !== toKey(person.unit);
36465
- await _updateById(_id, value, session);
36466
- if (isOrgChanged && person.user) {
36467
- await _updateUserOrgById(
36468
- person.user.toString(),
36469
- value.org,
36470
- session
36564
+ value = MGuestManagement(value);
36565
+ const res = await collection.insertOne(value, { session });
36566
+ delNamespace().then(() => {
36567
+ logger83.info(
36568
+ `Cache cleared for namespace: ${guests_namespace_collection}`
36471
36569
  );
36472
- }
36473
- if ((isBlockChanged || isLevelChanged || isUnitChanged) && person.user) {
36474
- const userUnitPayload = {};
36475
- if (isBlockChanged) {
36476
- userUnitPayload.block = value.block ?? null;
36477
- }
36478
- if (isLevelChanged) {
36479
- userUnitPayload.level = value.level ?? null;
36480
- }
36481
- if (isUnitChanged) {
36482
- userUnitPayload.unitId = value.unit ?? null;
36483
- if ("unitName" in value) {
36484
- userUnitPayload.unitName = value.unitName ?? "";
36485
- }
36486
- }
36487
- await _updateUserUnitById(
36488
- person.user.toString(),
36489
- userUnitPayload,
36490
- session
36570
+ }).catch((err) => {
36571
+ logger83.error(
36572
+ `Failed to clear cache for namespace: ${guests_namespace_collection}`,
36573
+ err
36491
36574
  );
36492
- }
36493
- if (value.unit && (isNameUpdated || isOwnerChanged || value.isOwner)) {
36494
- const unit = await _getUnitById(value.unit.toString());
36495
- if (unit) {
36496
- let updatePayload = {};
36497
- if (isOwnerChanged) {
36498
- if (value.isOwner) {
36499
- updatePayload.owner = _id;
36500
- updatePayload.ownerName = value.name || "";
36501
- } else {
36502
- updatePayload.owner = "";
36503
- updatePayload.ownerName = "";
36504
- }
36505
- }
36506
- if (isNameUpdated && value.isOwner && unit.owner?.toString() === _id.toString()) {
36507
- updatePayload.ownerName = value.name;
36508
- }
36509
- if (value.isOwner && (!unit.owner || !unit.ownerName)) {
36510
- updatePayload.owner = _id;
36511
- updatePayload.ownerName = value.name || "";
36512
- }
36513
- if (Object.keys(updatePayload).length > 0 && unit && unit._id) {
36514
- await updateUnitById(
36515
- unit._id.toString(),
36516
- updatePayload,
36517
- session
36518
- );
36519
- }
36520
- }
36521
- }
36522
- if (isUnitChanged && person.isOwner && person.unit) {
36523
- const previousUnit = await _getUnitById(person.unit.toString());
36524
- if (previousUnit && previousUnit._id && previousUnit.owner?.toString() === person?._id?.toString()) {
36525
- await updateUnitById(
36526
- previousUnit._id.toString(),
36527
- { owner: "", ownerName: "" },
36528
- session
36529
- );
36530
- }
36531
- }
36532
- await session.commitTransaction();
36533
- return "Person updated successfully.";
36534
- } catch (error) {
36535
- logger84.log({
36536
- level: "error",
36537
- message: `Failed to add site billing item: ${error.message}`
36538
36575
  });
36539
- await session.abortTransaction();
36576
+ return res.insertedId;
36577
+ } catch (error) {
36578
+ const isDuplicated = error.message.includes("duplicate");
36579
+ if (isDuplicated) {
36580
+ throw new BadRequestError103("Visitor already exists.");
36581
+ }
36540
36582
  throw error;
36541
- } finally {
36542
- session.endSession();
36543
36583
  }
36544
36584
  }
36545
- async function plateValidity(value) {
36546
- const now = /* @__PURE__ */ new Date();
36547
- if (["resident" /* RESIDENT */, "tenant" /* TENANT */].includes(
36548
- value?.type
36549
- )) {
36550
- const end = new Date(now);
36551
- end.setFullYear(end.getFullYear() + 10);
36552
- return {
36553
- start: now.toISOString(),
36554
- end: end.toISOString()
36555
- };
36585
+ async function getAll({
36586
+ search = "",
36587
+ page = 1,
36588
+ limit = 10,
36589
+ sort = {},
36590
+ status = "active",
36591
+ dateFrom,
36592
+ dateTo,
36593
+ org = "",
36594
+ site = ""
36595
+ }, session) {
36596
+ page = page > 0 ? page - 1 : 0;
36597
+ const start = dateFrom ? { $gte: new Date(dateFrom).toISOString() } : void 0;
36598
+ const end = dateTo ? {
36599
+ $or: [
36600
+ { end: { $lte: new Date(dateTo).toISOString() } },
36601
+ { end: null }
36602
+ ]
36603
+ } : void 0;
36604
+ const query = {
36605
+ status,
36606
+ ...start && { start },
36607
+ ...end,
36608
+ ...search && { $text: { $search: search } },
36609
+ ...ObjectId66.isValid(org) && { org: new ObjectId66(org) },
36610
+ ...ObjectId66.isValid(site) && { site: new ObjectId66(site) }
36611
+ };
36612
+ sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
36613
+ const cacheOptions = {
36614
+ sort: JSON.stringify(sort),
36615
+ page,
36616
+ limit,
36617
+ status,
36618
+ ...start && { start: JSON.stringify(start) },
36619
+ ...dateTo && { end: JSON.stringify(end) },
36620
+ ...search && { search },
36621
+ ...query.org && { org: query.org.toString() },
36622
+ ...query.site && { site: query.site.toString() }
36623
+ };
36624
+ const cacheKey = makeCacheKey31(guests_namespace_collection, cacheOptions);
36625
+ const cachedData = await getCache(cacheKey);
36626
+ if (cachedData) {
36627
+ logger83.info(`Cache hit for key: ${cacheKey}`);
36628
+ return cachedData;
36556
36629
  }
36557
- return { start: "", end: "" };
36558
- }
36559
- async function reviewResidentPerson(id, value) {
36560
- const session = useAtlas54.getClient()?.startSession();
36561
- session?.startTransaction();
36562
36630
  try {
36563
- const person = await _getById(id.toString());
36564
- if (!person)
36565
- throw new BadRequestError104("Person not found.");
36566
- if (value.approvedBy) {
36567
- const approvedBy = await getUserById(value.approvedBy.id);
36568
- if (!approvedBy || !approvedBy.name)
36569
- throw new BadRequestError104("Created by not found.");
36570
- value.approvedBy.name = approvedBy.name;
36571
- value.approvedBy.id = approvedBy._id;
36572
- }
36573
- const vehicle = {
36574
- plateNumber: person.plateNumber ?? "",
36575
- type: "whitelist",
36576
- category: person.type || "",
36577
- name: person.name,
36578
- phoneNumber: person.contact,
36579
- org: person.org.toString(),
36580
- site: person.site.toString(),
36581
- block: person.block?.toString() ?? void 0,
36582
- level: person.level?.toString() ?? void 0,
36583
- unit: person.unit?.toString() ?? void 0,
36584
- nric: person.nric,
36585
- status: "active",
36586
- peopleId: person._id?.toString()
36587
- };
36588
- const reviewStatus = value.status;
36589
- if (reviewStatus === "approved") {
36590
- value.status = "active";
36591
- }
36592
- await _reviewResidentPerson(id, value, session);
36593
- const logoPath = `${STORAGE_API}/seven-365.svg`;
36594
- const statusIconPath = STORAGE_API;
36595
- let title = "";
36596
- let message2 = "";
36597
- let hideLogin = true;
36598
- let logo = logoPath;
36599
- let statusLogo = statusIconPath;
36600
- if (person.user) {
36601
- let userStatus = "";
36602
- switch (reviewStatus) {
36603
- case "approved":
36604
- userStatus = "active";
36605
- if (person.plateNumber && person.plateNumber.trim() !== "") {
36606
- const anprCameraList = await getCamerasBySiteId(
36607
- person.site.toString()
36608
- );
36609
- const existingPerson = await getApprovedPersonByPlateNumber(
36610
- person.plateNumber,
36611
- id
36612
- );
36613
- if (anprCameraList.length > 0 && !existingPerson) {
36614
- await addVehicle(vehicle, session);
36615
- }
36616
- }
36617
- title = "Your account has been approved";
36618
- hideLogin = false;
36619
- statusLogo = `${statusIconPath}/approved-icon.png`;
36620
- message2 = `Welcome, ${person.name}! Thank you for signing up; we're excited to have you join us!`;
36621
- break;
36622
- case "resubmit":
36623
- userStatus = "resubmit";
36624
- title = "We request resubmission for your application";
36625
- hideLogin = false;
36626
- statusLogo = `${statusIconPath}/resubmission-icon.png`;
36627
- message2 = `Hi ${person.name}, please resubmit your application with the required updates.${value.remarks ? ` Remarks: ${value.remarks}` : ""}`;
36628
- break;
36629
- case "rejected":
36630
- userStatus = "rejected";
36631
- title = "Your account request for application has been rejected.";
36632
- statusLogo = `${statusIconPath}/deleted-icon.png`;
36633
- message2 = `Hi ${person.name}, unfortunately your application has been rejected.${value.remarks ? ` Remarks: ${value.remarks}` : " Please contact support for more information."}`;
36634
- break;
36635
- default:
36636
- throw new Error("Invalid review status");
36637
- }
36638
- const dir = __dirname;
36639
- const filePath = getDirectory3(
36640
- dir,
36641
- "./public/handlebars/resident-remarks-email"
36631
+ const basePipeline = [
36632
+ { $match: query },
36633
+ { $sort: sort },
36634
+ { $skip: page * limit },
36635
+ { $limit: limit }
36636
+ ];
36637
+ const [items, countResult] = await Promise.all([
36638
+ collection.aggregate(basePipeline, { session }).toArray(),
36639
+ collection.aggregate([{ $match: query }, { $count: "total" }], { session }).toArray()
36640
+ ]);
36641
+ const totalCount = countResult[0]?.total || 0;
36642
+ const data = paginate26(items, page, limit, totalCount);
36643
+ setCache(cacheKey, data, 15 * 60).then(() => logger83.info(`Cache set for key: ${cacheKey}`)).catch(
36644
+ (err) => logger83.error(`Failed to set cache for key: ${cacheKey}`, err)
36645
+ );
36646
+ return data;
36647
+ } catch (error) {
36648
+ throw error;
36649
+ }
36650
+ }
36651
+ async function updateVisitorGuestById(_id, value, session) {
36652
+ try {
36653
+ _id = new ObjectId66(_id);
36654
+ } catch (error) {
36655
+ throw new BadRequestError103("Invalid visitor transaction ID format.");
36656
+ }
36657
+ value.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
36658
+ try {
36659
+ const result = await collection.updateOne(
36660
+ { _id },
36661
+ { $set: value },
36662
+ { session }
36663
+ );
36664
+ delNamespace().then(() => {
36665
+ logger83.info(
36666
+ `Cache cleared for namespace: ${guests_namespace_collection}`
36642
36667
  );
36643
- const emailContent = compileHandlebar3({
36644
- context: {
36645
- title,
36646
- message2,
36647
- hideLogin,
36648
- logo,
36649
- statusLogo
36650
- },
36651
- filePath
36652
- });
36653
- await mailer.sendMail({
36654
- to: person.email,
36655
- subject: title,
36656
- html: emailContent,
36657
- sender: "iService365"
36658
- }).catch((error) => {
36659
- logger84.log({
36660
- level: "error",
36661
- message: `Error sending user ${reviewStatus} email: ${error}`
36662
- });
36663
- });
36664
- await _updateUserFieldById(
36665
- { _id: person.user.toString(), field: "status", value: userStatus },
36666
- session
36668
+ }).catch((err) => {
36669
+ logger83.error(
36670
+ `Failed to clear cache for namespace: ${guests_namespace_collection}`,
36671
+ err
36667
36672
  );
36673
+ });
36674
+ return result;
36675
+ } catch (error) {
36676
+ throw error;
36677
+ }
36678
+ }
36679
+ async function deleteVisitorGuest(_id) {
36680
+ try {
36681
+ _id = new ObjectId66(_id);
36682
+ } catch (error) {
36683
+ throw new BadRequestError103("Invalid customer ID format.");
36684
+ }
36685
+ try {
36686
+ const updateValue = {
36687
+ status: "deleted",
36688
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
36689
+ deletedAt: (/* @__PURE__ */ new Date()).toISOString()
36690
+ };
36691
+ const res = await collection.updateOne({ _id }, { $set: updateValue });
36692
+ if (res.modifiedCount === 0) {
36693
+ throw new InternalServerError34("Unable to delete visitor transaction.");
36668
36694
  }
36695
+ delNamespace().then(() => {
36696
+ logger83.info(
36697
+ `Cache cleared for namespace: ${guests_namespace_collection}`
36698
+ );
36699
+ }).catch((err) => {
36700
+ logger83.error(
36701
+ `Failed to clear cache for namespace: ${guests_namespace_collection}`,
36702
+ err
36703
+ );
36704
+ });
36705
+ return res.modifiedCount;
36706
+ } catch (error) {
36707
+ throw error;
36708
+ }
36709
+ }
36710
+ return {
36711
+ add,
36712
+ getAll,
36713
+ updateVisitorGuestById,
36714
+ deleteVisitorGuest,
36715
+ createTextIndex
36716
+ };
36717
+ }
36718
+
36719
+ // src/services/guest-management.service.ts
36720
+ import { useAtlas as useAtlas54 } from "@7365admin1/node-server-utils";
36721
+ function useGuestManagementService() {
36722
+ const { add: _add, updateVisitorGuestById: _updateVisitorGuestById } = useGuestManagementRepo();
36723
+ async function add(value) {
36724
+ const session = useAtlas54.getClient()?.startSession();
36725
+ session?.startTransaction();
36726
+ try {
36727
+ await _add(value, session);
36669
36728
  await session?.commitTransaction();
36670
- return `Successfully ${reviewStatus} person`;
36729
+ return "Successfully added guest.";
36671
36730
  } catch (error) {
36672
36731
  await session?.abortTransaction();
36673
36732
  throw error;
@@ -36675,41 +36734,165 @@ function usePersonService() {
36675
36734
  session?.endSession();
36676
36735
  }
36677
36736
  }
36678
- async function suspendResidentById(id) {
36737
+ async function updateVisitorGuestById(id, value) {
36679
36738
  const session = useAtlas54.getClient()?.startSession();
36680
- if (!session) {
36681
- throw new BadRequestError104("Database session not available.");
36682
- }
36739
+ session?.startTransaction();
36683
36740
  try {
36684
- session.startTransaction();
36685
- const person = await _getByUserId(id.toString());
36686
- if (!person)
36687
- throw new NotFoundError23("Person not found.");
36688
- await _updateById(person._id, { status: "deleted" }, session);
36689
- if (person.user) {
36690
- await _updateUserFieldById(
36691
- { _id: person.user.toString(), field: "status", value: "suspended" },
36692
- session
36693
- );
36694
- }
36695
- await session.commitTransaction();
36696
- return "Resident suspended successfully.";
36741
+ await _updateVisitorGuestById(id, value, session);
36742
+ await session?.commitTransaction();
36743
+ return "Successfully updated guest.";
36697
36744
  } catch (error) {
36698
- await session.abortTransaction();
36745
+ await session?.abortTransaction();
36699
36746
  throw error;
36700
36747
  } finally {
36701
- session.endSession();
36748
+ session?.endSession();
36702
36749
  }
36703
36750
  }
36704
36751
  return {
36705
36752
  add,
36706
- updateById,
36707
- reviewResidentPerson,
36708
- suspendResidentById
36753
+ updateVisitorGuestById
36754
+ };
36755
+ }
36756
+
36757
+ // src/controllers/guest-management.controller.ts
36758
+ import Joi59 from "joi";
36759
+ import { BadRequestError as BadRequestError104, logger as logger84 } from "@7365admin1/node-server-utils";
36760
+ function useGuestManagementController() {
36761
+ const { add: _add, updateVisitorGuestById: _updateVisitorGuestById } = useGuestManagementService();
36762
+ const { getAll: _getAll, deleteVisitorGuest: _deleteVisitorGuest } = useGuestManagementRepo();
36763
+ async function add(req, res, next) {
36764
+ const payload = { ...req.body };
36765
+ const { error } = schemaGuestManagement.validate(payload, {
36766
+ abortEarly: false
36767
+ });
36768
+ if (error) {
36769
+ const messages = error.details.map((d) => d.message).join(", ");
36770
+ logger84.log({ level: "error", message: messages });
36771
+ next(new BadRequestError104(messages));
36772
+ return;
36773
+ }
36774
+ try {
36775
+ const data = await _add(payload);
36776
+ res.status(201).json({ message: data });
36777
+ return;
36778
+ } catch (error2) {
36779
+ logger84.log({ level: "error", message: error2.message });
36780
+ next(error2);
36781
+ return;
36782
+ }
36783
+ }
36784
+ async function getAll(req, res, next) {
36785
+ const allowedFields = ["createdAt", "name"];
36786
+ const validation = Joi59.object({
36787
+ page: Joi59.number().integer().min(1).allow("", null).default(1),
36788
+ limit: Joi59.number().integer().min(1).max(100).allow("", null).default(10),
36789
+ sort: Joi59.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
36790
+ status: Joi59.string().optional().allow(null, ""),
36791
+ search: Joi59.string().optional().allow(null, ""),
36792
+ dateFrom: Joi59.date().iso().optional().allow(null, ""),
36793
+ dateTo: Joi59.date().iso().optional().allow(null, ""),
36794
+ org: Joi59.string().optional().allow(null, ""),
36795
+ site: Joi59.string().optional().allow(null, "")
36796
+ });
36797
+ const query = { ...req.query };
36798
+ const { error } = validation.validate(query, {
36799
+ abortEarly: false
36800
+ });
36801
+ if (error) {
36802
+ const messages = error.details.map((d) => d.message).join(", ");
36803
+ logger84.log({ level: "error", message: messages });
36804
+ next(new BadRequestError104(messages));
36805
+ return;
36806
+ }
36807
+ const search = req.query.search ?? "";
36808
+ const page = parseInt(req.query.page ?? "1");
36809
+ const limit = parseInt(req.query.limit ?? "10");
36810
+ const status = req.query.status ?? "active";
36811
+ const dateTo = req.query.dateTo;
36812
+ const dateFrom = req.query.dateFrom;
36813
+ const org = req.query.org;
36814
+ const site = req.query.site;
36815
+ const sortObj = {};
36816
+ const sortFields = String(req.query.sort).split(",");
36817
+ const sortOrders = String(req.query.order).split(",");
36818
+ sortFields.forEach((field, index) => {
36819
+ if (allowedFields.includes(field)) {
36820
+ const order = sortOrders[index] === "asc" ? 1 : -1;
36821
+ sortObj[field] = order;
36822
+ }
36823
+ });
36824
+ try {
36825
+ const data = await _getAll({
36826
+ search,
36827
+ page,
36828
+ limit,
36829
+ sort: sortObj,
36830
+ status,
36831
+ dateFrom,
36832
+ dateTo,
36833
+ org,
36834
+ site
36835
+ });
36836
+ res.status(200).json(data);
36837
+ return;
36838
+ } catch (error2) {
36839
+ logger84.log({ level: "error", message: error2.message });
36840
+ next(error2);
36841
+ return;
36842
+ }
36843
+ }
36844
+ async function updateVisitorGuestById(req, res, next) {
36845
+ const _id = req.params.id;
36846
+ const payload = { _id, ...req.body };
36847
+ const { error } = schemaUpdateGuestManagement.validate(payload, {
36848
+ abortEarly: false
36849
+ });
36850
+ if (error) {
36851
+ const messages = error.details.map((d) => d.message).join(", ");
36852
+ logger84.log({ level: "error", message: messages });
36853
+ next(new BadRequestError104(messages));
36854
+ return;
36855
+ }
36856
+ try {
36857
+ const result = await _updateVisitorGuestById(_id, req.body);
36858
+ res.status(200).json({ message: result });
36859
+ return;
36860
+ } catch (error2) {
36861
+ logger84.log({ level: "error", message: error2.message });
36862
+ next(error2);
36863
+ return;
36864
+ }
36865
+ }
36866
+ async function deleteVisitorGuest(req, res, next) {
36867
+ const validation = Joi59.string().hex().required();
36868
+ const _id = req.params.id;
36869
+ const { error } = validation.validate(_id);
36870
+ if (error) {
36871
+ logger84.log({ level: "error", message: error.message });
36872
+ next(new BadRequestError104(error.message));
36873
+ return;
36874
+ }
36875
+ try {
36876
+ await _deleteVisitorGuest(_id);
36877
+ res.status(200).json({ message: "Successfully deleted visitor guest." });
36878
+ return;
36879
+ } catch (error2) {
36880
+ logger84.log({ level: "error", message: error2.message });
36881
+ next(error2);
36882
+ return;
36883
+ }
36884
+ }
36885
+ return {
36886
+ add,
36887
+ getAll,
36888
+ updateVisitorGuestById,
36889
+ deleteVisitorGuest
36709
36890
  };
36710
36891
  }
36711
36892
 
36712
36893
  // src/controllers/person.controller.ts
36894
+ import Joi60 from "joi";
36895
+ import { BadRequestError as BadRequestError105, logger as logger85 } from "@7365admin1/node-server-utils";
36713
36896
  function usePersonController() {
36714
36897
  const {
36715
36898
  getAll: _getAll,
@@ -36754,17 +36937,21 @@ function usePersonController() {
36754
36937
  const validation = Joi60.object({
36755
36938
  page: Joi60.number().integer().min(1).allow("", null).default(1),
36756
36939
  limit: Joi60.number().integer().min(1).max(100).allow("", null).default(10),
36757
- sort: Joi60.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", ...allowedFields),
36940
+ sort: Joi60.string().pattern(/^([a-zA-Z0-9_]+)(,[a-zA-Z0-9_]+)*$/).optional().allow("", null),
36941
+ order: Joi60.string().pattern(/^(asc|desc)(,(asc|desc))*$/i).optional().allow("", null),
36758
36942
  status: Joi60.string().optional().allow(null, ""),
36759
36943
  search: Joi60.string().optional().allow(null, ""),
36760
36944
  dateFrom: Joi60.date().iso().optional().allow(null, ""),
36761
36945
  dateTo: Joi60.date().iso().optional().allow(null, ""),
36762
- org: Joi60.string().optional().allow(null, ""),
36763
- site: Joi60.string().optional().allow(null, ""),
36946
+ org: Joi60.string().hex().length(24).optional().allow(null, ""),
36947
+ site: Joi60.string().hex().length(24).optional().allow(null, ""),
36764
36948
  type: Joi60.alternatives().try(
36765
36949
  Joi60.string().valid(...PERSON_TYPES),
36766
36950
  Joi60.array().items(Joi60.string().valid(...PERSON_TYPES))
36767
- ).optional().allow(null, "")
36951
+ ).optional().allow(null, ""),
36952
+ nric: Joi60.string().optional().allow(null, ""),
36953
+ contact: Joi60.string().optional().allow(null, ""),
36954
+ plateNumber: Joi60.string().optional().allow(null, "")
36768
36955
  });
36769
36956
  const query = { ...req.query };
36770
36957
  const { error } = validation.validate(query, {
@@ -36785,6 +36972,9 @@ function usePersonController() {
36785
36972
  const org = req.query.org;
36786
36973
  const site = req.query.site;
36787
36974
  const type = req.query.type;
36975
+ const nric = req.query.nric;
36976
+ const contact = req.query.contact;
36977
+ const plateNumber = req.query.plateNumber;
36788
36978
  const sortObj = {};
36789
36979
  const sortFields = String(req.query.sort).split(",");
36790
36980
  const sortOrders = String(req.query.order).split(",");
@@ -36805,7 +36995,10 @@ function usePersonController() {
36805
36995
  dateTo,
36806
36996
  org,
36807
36997
  site,
36808
- type
36998
+ type,
36999
+ nric,
37000
+ contact,
37001
+ plateNumber
36809
37002
  });
36810
37003
  res.status(200).json(data);
36811
37004
  return;