@7365admin1/core 3.48.1-staging.173 → 3.48.1-staging.175

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.
@@ -0,0 +1,5 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Scope the site billing configuration routes to the estate, and take createdBy from the session
package/dist/index.d.ts CHANGED
@@ -6253,6 +6253,23 @@ declare function useSiteBillingConfigurationRepo(): {
6253
6253
  createIndexes: () => Promise<string>;
6254
6254
  };
6255
6255
 
6256
+ /**
6257
+ * Billing configuration is per-estate — the tax rate, the rounding and the
6258
+ * payment settings every bill at that site is calculated from.
6259
+ *
6260
+ * Every one of these five handlers carried `requireAuth` and nothing else. The
6261
+ * record id came from the URL and the site came from the caller's own query or
6262
+ * body, so any signed-in account on the platform could read another client's
6263
+ * billing settings, change their tax rate, move a configuration to a different
6264
+ * estate, or delete it. `createdBy` was read off the raw `user` COOKIE with a
6265
+ * fall back to the body, so the "who set this up" name was client-supplied.
6266
+ *
6267
+ * The rule is the existing one — `requireSiteReach`, i.e. `entitleSite` — asked
6268
+ * about the site the request names on the way in, and about the STORED site for
6269
+ * the id routes, which name no site at all. `PUT` asks twice: once about where
6270
+ * the record is and once about where the body wants to move it, because a MOVE
6271
+ * needs the destination checked too. `createdBy` now comes from the session.
6272
+ */
6256
6273
  declare function useSiteBillingConfigurationController(): {
6257
6274
  add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
6258
6275
  getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
@@ -10913,6 +10930,7 @@ declare function useHidAmicoService(): {
10913
10930
  includePassword?: boolean;
10914
10931
  }) => Promise<THidAmicoReader>;
10915
10932
  authorizeReaderUser: (readerId: string, hidUserIdValue: string | number, userId: string) => Promise<THidAmicoReader>;
10933
+ authorizeVisitorForMember: (readerId: string, visitorId: string, userId: string) => Promise<THidAmicoReader>;
10916
10934
  authorizeSiteForCaller: (siteId: string, userId: string) => Promise<{
10917
10935
  site: mongodb.WithId<bson.Document> | null;
10918
10936
  siteObjectId: bson.ObjectId;
@@ -10936,6 +10954,23 @@ declare function useHidAmicoService(): {
10936
10954
  pages: number;
10937
10955
  pageRange: string;
10938
10956
  }>;
10957
+ getMemberReaderInfo: (readerId: string, userId: string) => Promise<{
10958
+ _id: string;
10959
+ name: string;
10960
+ location: string | undefined;
10961
+ status: "deleted" | "active" | "inactive" | "offline" | undefined;
10962
+ portalId: number | undefined;
10963
+ portalName: string | undefined;
10964
+ readerModel: "unknown" | "VL35LF" | "VL70LF" | undefined;
10965
+ cardReadingMode: "unconfigured" | "pacs" | "csn" | "csn_for_mifare" | undefined;
10966
+ capabilities: {
10967
+ card: boolean;
10968
+ facial: boolean;
10969
+ qr: boolean;
10970
+ pin: boolean;
10971
+ intercom: boolean;
10972
+ };
10973
+ }>;
10939
10974
  discoverReader: (value: Pick<THidAmicoReader, "baseUrl" | "username" | "password"> & Partial<Pick<THidAmicoReader, "readerModel">>) => Promise<{
10940
10975
  deviceId: string;
10941
10976
  serial: string;
@@ -11084,7 +11119,7 @@ declare function useHidAmicoService(): {
11084
11119
  pages: number;
11085
11120
  pageRange: string;
11086
11121
  }>;
11087
- listReaderUsers: (readerId: string, page?: number, limit?: number, search?: string, status?: "mapped" | "unmapped" | "", includeVisitors?: boolean) => Promise<{
11122
+ listReaderUsers: (readerId: string, page?: number, limit?: number, search?: string, status?: "mapped" | "unmapped" | "", includeVisitors?: boolean, userType?: "all" | "user" | "visitor") => Promise<{
11088
11123
  items: {
11089
11124
  reader: string;
11090
11125
  hidUserId: string;
@@ -11096,7 +11131,6 @@ declare function useHidAmicoService(): {
11096
11131
  facialEnrolled: boolean;
11097
11132
  };
11098
11133
  isAdministrator: boolean;
11099
- _id?: bson.ObjectId | undefined;
11100
11134
  }[];
11101
11135
  page: number;
11102
11136
  limit: number;
@@ -11153,6 +11187,15 @@ declare function useHidAmicoService(): {
11153
11187
  validFrom: string;
11154
11188
  expiresAt: string;
11155
11189
  }>;
11190
+ getVisitorQrForMember: (readerId: string, visitorId: string, userId: string) => Promise<{
11191
+ readerId: string;
11192
+ visitorId: string;
11193
+ qrValue: string;
11194
+ expiresAt: string;
11195
+ portalId: number | undefined;
11196
+ portalName: string | undefined;
11197
+ readerName: string;
11198
+ } | null>;
11156
11199
  revokeVisitorQr: (readerId: string, visitorId: string) => Promise<{
11157
11200
  readerId: string;
11158
11201
  visitorId: string;
@@ -11365,6 +11408,7 @@ declare function useHidAmicoController(): {
11365
11408
  authorizeManage: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
11366
11409
  authorizeVisitor: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
11367
11410
  listReaders: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11411
+ getMemberReaderInfo: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11368
11412
  discoverReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11369
11413
  createReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11370
11414
  updateReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
@@ -11400,6 +11444,7 @@ declare function useHidAmicoController(): {
11400
11444
  setConfiguration: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11401
11445
  runObjectOperation: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11402
11446
  issueVisitorQr: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11447
+ getVisitorQr: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11403
11448
  revokeVisitorQr: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11404
11449
  getProfileFacial: (req: Request, res: Response, next: NextFunction) => Promise<void>;
11405
11450
  setProfileFacial: (req: Request, res: Response, next: NextFunction) => Promise<void>;
package/dist/index.js CHANGED
@@ -43262,7 +43262,7 @@ var schemaHidAmicoIdentity = import_joi64.default.object({
43262
43262
  var schemaCreateHidAmicoIdentity = import_joi64.default.object({
43263
43263
  site: objectIdSchema2.optional(),
43264
43264
  hidUserId: import_joi64.default.alternatives(import_joi64.default.string(), import_joi64.default.number()).optional().allow(null, ""),
43265
- registration: import_joi64.default.string().trim().required(),
43265
+ registration: import_joi64.default.string().trim().optional().allow(""),
43266
43266
  cardNo: import_joi64.default.string().optional().allow(null, ""),
43267
43267
  person: objectIdSchema2.optional().allow(null, ""),
43268
43268
  user: objectIdSchema2.optional().allow(null, ""),
@@ -43409,7 +43409,8 @@ var schemaHidAmicoReaderUserQuery = import_joi64.default.object({
43409
43409
  limit: import_joi64.default.number().integer().min(1).max(100).default(10),
43410
43410
  search: import_joi64.default.string().trim().max(100).allow("").default(""),
43411
43411
  status: import_joi64.default.string().valid("mapped", "unmapped").allow("").default(""),
43412
- includeVisitors: import_joi64.default.boolean().default(false)
43412
+ includeVisitors: import_joi64.default.boolean().default(false),
43413
+ userType: import_joi64.default.string().valid("all", "user", "visitor").default("all")
43413
43414
  });
43414
43415
  var schemaHidAmicoAccessLogQuery = import_joi64.default.object({
43415
43416
  page: import_joi64.default.number().integer().min(1).default(1),
@@ -44928,18 +44929,34 @@ function getSecretKey() {
44928
44929
  return import_crypto2.default.createHash("sha256").update(secret).digest();
44929
44930
  }
44930
44931
  function getReaderCapabilities(readerModel, firmwareVersion) {
44932
+ const isAmicoBiometricReader2 = readerModel === "VL35LF" || readerModel === "VL70LF";
44931
44933
  return {
44932
- card: hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
44934
+ // VL35LF/VL70LF expose the relevant credential functions even when the
44935
+ // installed product firmware reports a 2.x version rather than the legacy
44936
+ // API version scheme.
44937
+ card: isAmicoBiometricReader2 || hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
44933
44938
  // VL35LF and VL70LF are biometric readers. Some deployed readers report
44934
44939
  // their device firmware as 2.x even though their face API is available.
44935
- facial: readerModel === "VL35LF" || readerModel === "VL70LF",
44936
- qr: hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
44937
- pin: hidFirmwareAtLeast(firmwareVersion, "6.9.0"),
44940
+ facial: isAmicoBiometricReader2,
44941
+ qr: isAmicoBiometricReader2 || hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
44942
+ pin: isAmicoBiometricReader2 || hidFirmwareAtLeast(firmwareVersion, "6.9.0"),
44938
44943
  intercom: readerModel === "VL70LF"
44939
44944
  };
44940
44945
  }
44946
+ function isAmicoBiometricReader(reader) {
44947
+ return reader.readerModel === "VL35LF" || reader.readerModel === "VL70LF";
44948
+ }
44941
44949
  function readerSupportsFacial(reader) {
44942
- return reader.capabilities?.facial === true || reader.readerModel === "VL35LF" || reader.readerModel === "VL70LF";
44950
+ return reader.capabilities?.facial === true || isAmicoBiometricReader(reader);
44951
+ }
44952
+ function readerSupportsCredential(reader, capability) {
44953
+ if (reader.capabilities?.[capability] === true)
44954
+ return true;
44955
+ if (capability === "facial")
44956
+ return readerSupportsFacial(reader);
44957
+ if (capability === "intercom")
44958
+ return reader.readerModel === "VL70LF";
44959
+ return isAmicoBiometricReader(reader);
44943
44960
  }
44944
44961
  function getMonitorConfiguration(callbackToken) {
44945
44962
  const configured = String(process.env.HID_AMICO_MONITOR_URL || "").trim();
@@ -45095,14 +45112,25 @@ function createVisitorQrCredential(visitorId, readerId) {
45095
45112
  }
45096
45113
  async function configureVisitorQrReader(client) {
45097
45114
  const requestedConfiguration = {
45115
+ general: ["online", "local_identification"],
45098
45116
  face_id: ["qrcode_legacy_mode_enabled"],
45099
45117
  identifier: ["qrcode_identification_enabled", "multi_factor_authentication"]
45100
45118
  };
45101
45119
  const currentConfiguration = await client.getConfiguration(requestedConfiguration);
45102
45120
  const current = isUnknownRecord2(currentConfiguration) ? currentConfiguration : {};
45121
+ const currentGeneral = isUnknownRecord2(current.general) ? current.general : {};
45103
45122
  const currentFaceId = isUnknownRecord2(current.face_id) ? current.face_id : {};
45104
45123
  const currentIdentifier = isUnknownRecord2(current.identifier) ? current.identifier : {};
45105
45124
  let changed = false;
45125
+ if (String(currentGeneral.online ?? "") !== "0" || String(currentGeneral.local_identification ?? "") !== "1") {
45126
+ await client.setConfiguration({
45127
+ general: {
45128
+ online: "0",
45129
+ local_identification: "1"
45130
+ }
45131
+ });
45132
+ changed = true;
45133
+ }
45106
45134
  if (String(currentFaceId.qrcode_legacy_mode_enabled ?? "") !== VISITOR_QR_FORMAT) {
45107
45135
  await client.setConfiguration({
45108
45136
  face_id: { qrcode_legacy_mode_enabled: VISITOR_QR_FORMAT }
@@ -45122,9 +45150,10 @@ async function configureVisitorQrReader(client) {
45122
45150
  }
45123
45151
  const configuration = await client.getConfiguration(requestedConfiguration);
45124
45152
  const savedConfiguration = isUnknownRecord2(configuration) ? configuration : {};
45153
+ const general = isUnknownRecord2(savedConfiguration.general) ? savedConfiguration.general : {};
45125
45154
  const faceId = isUnknownRecord2(savedConfiguration.face_id) ? savedConfiguration.face_id : {};
45126
45155
  const identifier = isUnknownRecord2(savedConfiguration.identifier) ? savedConfiguration.identifier : {};
45127
- if (String(faceId.qrcode_legacy_mode_enabled ?? "") !== VISITOR_QR_FORMAT || String(identifier.qrcode_identification_enabled ?? "") !== "1" || String(identifier.multi_factor_authentication ?? "") !== "0") {
45156
+ if (String(general.online ?? "") !== "0" || String(general.local_identification ?? "") !== "1" || String(faceId.qrcode_legacy_mode_enabled ?? "") !== VISITOR_QR_FORMAT || String(identifier.qrcode_identification_enabled ?? "") !== "1" || String(identifier.multi_factor_authentication ?? "") !== "0") {
45128
45157
  throw new import_node_server_utils132.BadRequestError(
45129
45158
  "The HID reader did not retain alphanumeric QR configuration."
45130
45159
  );
@@ -46357,6 +46386,17 @@ function useHidAmicoService() {
46357
46386
  return reader;
46358
46387
  return authorizeReader(readerId, userId);
46359
46388
  }
46389
+ async function authorizeVisitorForMember(readerId, visitorId, userId) {
46390
+ const reader = await entitleReaderForSelf(readerId, userId);
46391
+ const visitor = await visitorRepo.getVisitorTransactionById(visitorId);
46392
+ const visitorSiteId = String(visitor?.site?._id || visitor?.site || "");
46393
+ if (!visitorSiteId || visitorSiteId !== String(reader.site || "")) {
46394
+ throw new import_node_server_utils132.BadRequestError("Visitor and HID reader must belong to the same site.");
46395
+ }
46396
+ if (String(visitor?.createdBy || "") === userId)
46397
+ return reader;
46398
+ return authorizeReader(readerId, userId);
46399
+ }
46360
46400
  async function getActiveReader(id) {
46361
46401
  const reader = await repo.getById(id, { includePassword: true });
46362
46402
  if (reader.enabled === false || reader.status === "inactive" || reader.status === "deleted") {
@@ -46365,7 +46405,7 @@ function useHidAmicoService() {
46365
46405
  return reader;
46366
46406
  }
46367
46407
  function assertReaderCapability(reader, capability) {
46368
- if (reader.capabilities?.[capability] || capability === "facial" && readerSupportsFacial(reader)) {
46408
+ if (readerSupportsCredential(reader, capability)) {
46369
46409
  return;
46370
46410
  }
46371
46411
  const requirement = capability === "pin" ? "firmware 6.9.0 or newer" : capability === "intercom" ? "a VL70LF reader" : "firmware 6.5.1 or newer";
@@ -46809,6 +46849,26 @@ function useHidAmicoService() {
46809
46849
  async function listReaders(value) {
46810
46850
  return repo.list(value);
46811
46851
  }
46852
+ async function getMemberReaderInfo(readerId, userId) {
46853
+ const reader = await entitleReaderForSelf(readerId, userId);
46854
+ return {
46855
+ _id: String(reader._id),
46856
+ name: reader.name,
46857
+ location: reader.location,
46858
+ status: reader.status,
46859
+ portalId: reader.portalId,
46860
+ portalName: reader.portalName,
46861
+ readerModel: reader.readerModel,
46862
+ cardReadingMode: reader.cardReadingMode,
46863
+ capabilities: {
46864
+ card: readerSupportsCredential(reader, "card"),
46865
+ facial: readerSupportsFacial(reader),
46866
+ qr: readerSupportsCredential(reader, "qr"),
46867
+ pin: readerSupportsCredential(reader, "pin"),
46868
+ intercom: readerSupportsCredential(reader, "intercom")
46869
+ }
46870
+ };
46871
+ }
46812
46872
  async function deleteReader(id) {
46813
46873
  return repo.markDeleted(id);
46814
46874
  }
@@ -46983,7 +47043,7 @@ function useHidAmicoService() {
46983
47043
  async function listIdentities(value) {
46984
47044
  return repo.listIdentities(value);
46985
47045
  }
46986
- async function listReaderUsers(readerId, page = 1, limit = 10, search = "", status = "", includeVisitors = false) {
47046
+ async function listReaderUsers(readerId, page = 1, limit = 10, search = "", status = "", includeVisitors = false, userType = "all") {
46987
47047
  const reader = await getActiveReader(readerId);
46988
47048
  const offset = (page - 1) * limit;
46989
47049
  const client = new HidAmicoClient(reader);
@@ -47000,54 +47060,22 @@ function useHidAmicoService() {
47000
47060
  let rows = [];
47001
47061
  let reportedTotal = null;
47002
47062
  let hasMore = false;
47003
- if (!status) {
47004
- const response = await client.loadObjects({
47005
- object: "users",
47006
- where: { users: userWhere },
47007
- limit: 500,
47008
- offset: 0,
47009
- order: ["id", "ascending"]
47010
- });
47011
- const deviceRows = getHidObjectRows(response, "users").filter((row) => includeVisitors || Number(row.user_type_id) !== 1);
47012
- reportedTotal = deviceRows.length;
47013
- rows = deviceRows.slice(offset, offset + limit);
47014
- hasMore = offset + rows.length < deviceRows.length;
47015
- } else {
47016
- const linkedIdentities = await repo.listActiveIdentities(readerId);
47017
- const mappedUserIds = new Set(
47018
- linkedIdentities.map((identity) => String(identity.hidUserId || "")).filter(Boolean)
47019
- );
47020
- const matchedRows = [];
47021
- const targetCount = offset + limit + 1;
47022
- let deviceOffset = 0;
47023
- let exhausted = status === "mapped" && mappedUserIds.size === 0;
47024
- const chunkSize = 100;
47025
- while (!exhausted && matchedRows.length < targetCount) {
47026
- const response = await client.loadObjects({
47027
- object: "users",
47028
- where: { users: userWhere },
47029
- limit: chunkSize,
47030
- offset: deviceOffset,
47031
- order: ["id", "ascending"]
47032
- });
47033
- const batch = getHidObjectRows(response, "users");
47034
- batch.forEach((row) => {
47035
- if (!includeVisitors && Number(row.user_type_id) === 1)
47036
- return;
47037
- const mapped = mappedUserIds.has(String(row.id ?? ""));
47038
- if (status === "mapped" && mapped || status === "unmapped" && !mapped) {
47039
- matchedRows.push(row);
47040
- }
47041
- });
47042
- deviceOffset += batch.length;
47043
- exhausted = batch.length < chunkSize;
47044
- }
47045
- rows = matchedRows.slice(offset, offset + limit);
47046
- hasMore = matchedRows.length > offset + limit || !exhausted;
47047
- }
47048
- const userIds = rows.map((row) => String(row.id ?? "")).filter(Boolean);
47049
- const [identities, administratorFlags, facialUserIds] = await Promise.all([
47050
- repo.findIdentitiesByHidUserIds(readerId, userIds),
47063
+ const response = await client.loadObjects({
47064
+ object: "users",
47065
+ where: { users: userWhere },
47066
+ limit: 500,
47067
+ offset: 0,
47068
+ order: ["id", "ascending"]
47069
+ });
47070
+ const deviceRows = getHidObjectRows(response, "users");
47071
+ const filteredRows = deviceRows.filter((row) => {
47072
+ const isVisitor = Number(row.user_type_id) === 1;
47073
+ return userType === "visitor" ? isVisitor : userType === "user" ? !isVisitor : true;
47074
+ });
47075
+ reportedTotal = filteredRows.length;
47076
+ rows = filteredRows.slice(offset, offset + limit);
47077
+ hasMore = offset + rows.length < filteredRows.length;
47078
+ const [administratorFlags, facialUserIds] = await Promise.all([
47051
47079
  Promise.all(rows.map(async (row) => {
47052
47080
  const userId = Number(row.id);
47053
47081
  if (!Number.isSafeInteger(userId) || userId <= 0)
@@ -47062,24 +47090,17 @@ function useHidAmicoService() {
47062
47090
  })),
47063
47091
  readerSupportsFacial(reader) ? client.listUserImageIds().catch(() => []) : Promise.resolve([])
47064
47092
  ]);
47065
- const identityMap = new Map(
47066
- identities.map((identity) => [String(identity.hidUserId), identity])
47067
- );
47068
47093
  const facialUserIdSet = new Set(facialUserIds.map(String));
47069
47094
  const items = rows.map((row, index) => {
47070
- const identity = identityMap.get(String(row.id ?? ""));
47071
- const identityMetadata = identity && isUnknownRecord2(identity.metadata) ? identity.metadata : {};
47072
- const facialEnrolled = facialUserIdSet.has(String(row.id ?? "")) || identityMetadata.facialEnrolled === true;
47095
+ const facialEnrolled = facialUserIdSet.has(String(row.id ?? ""));
47073
47096
  return {
47074
47097
  ...row,
47075
- ...identity || {},
47076
47098
  reader: readerId,
47077
- hidUserId: String(row.id ?? identity?.hidUserId ?? ""),
47078
- registration: String(row.registration ?? identity?.registration ?? ""),
47099
+ hidUserId: String(row.id ?? ""),
47100
+ registration: String(row.registration ?? ""),
47079
47101
  metadata: {
47080
- ...identityMetadata,
47081
- name: String(row.name ?? identityMetadata.name ?? ""),
47082
- hidUserTypeId: row.user_type_id ?? identityMetadata.hidUserTypeId,
47102
+ name: String(row.name ?? ""),
47103
+ hidUserTypeId: row.user_type_id,
47083
47104
  facialEnrolled,
47084
47105
  ...facialEnrolled ? { facialData: String(row.id ?? "") } : {}
47085
47106
  },
@@ -47404,6 +47425,7 @@ function useHidAmicoService() {
47404
47425
  site: reader.site,
47405
47426
  type: "visitor_qr_reader_configured",
47406
47427
  payload: {
47428
+ operatingMode: "standalone",
47407
47429
  qrFormat: VISITOR_QR_FORMAT,
47408
47430
  qrcodeIdentificationEnabled: "1",
47409
47431
  multiFactorAuthentication: "0"
@@ -47550,7 +47572,11 @@ function useHidAmicoService() {
47550
47572
  site: reader.site
47551
47573
  });
47552
47574
  }
47553
- await repo.updateById(readerId, { status: "active", lastSeenAt: /* @__PURE__ */ new Date() });
47575
+ await repo.updateById(readerId, {
47576
+ status: "active",
47577
+ lastSeenAt: /* @__PURE__ */ new Date(),
47578
+ operatingMode: "standalone"
47579
+ });
47554
47580
  await repo.addEvent({
47555
47581
  reader: readerId,
47556
47582
  site: reader.site,
@@ -47593,6 +47619,32 @@ function useHidAmicoService() {
47593
47619
  await client.logout();
47594
47620
  }
47595
47621
  }
47622
+ async function getVisitorQrForMember(readerId, visitorId, userId) {
47623
+ const reader = await authorizeVisitorForMember(readerId, visitorId, userId);
47624
+ const result = await repo.listIdentities({
47625
+ reader: readerId,
47626
+ type: "visitor",
47627
+ status: "active",
47628
+ page: 1,
47629
+ limit: 500
47630
+ });
47631
+ const identities = Array.isArray(result.items) ? result.items : [];
47632
+ const identity = identities.find((item) => String(item.visitor || "") === visitorId);
47633
+ const metadata = identity && isUnknownRecord2(identity.metadata) ? identity.metadata : {};
47634
+ const qrValue = String(metadata.qrValue || identity?.cardNo || "").trim();
47635
+ const expiresAt = String(metadata.expiresAt || "").trim();
47636
+ if (!qrValue || !expiresAt)
47637
+ return null;
47638
+ return {
47639
+ readerId,
47640
+ visitorId,
47641
+ qrValue,
47642
+ expiresAt,
47643
+ portalId: reader.portalId,
47644
+ portalName: reader.portalName,
47645
+ readerName: reader.name
47646
+ };
47647
+ }
47596
47648
  async function getProfileFacial(readerId, userId) {
47597
47649
  const reader = await getActiveReader(readerId);
47598
47650
  const context = await getProfileEnrollmentContext(readerId, reader, userId);
@@ -47759,47 +47811,25 @@ function useHidAmicoService() {
47759
47811
  try {
47760
47812
  await client.login();
47761
47813
  const facialUserIds = await getReaderFacialUserIds(client);
47762
- const activeIdentities = await repo.listActiveIdentities(readerId);
47763
- const identitiesByUserId = new Map(
47764
- activeIdentities.filter((identity) => identity._id && identity.hidUserId !== void 0).map((identity) => [String(identity.hidUserId), identity])
47765
- );
47766
- const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
47767
- let syncedCount = 0;
47768
- let unmappedCount = 0;
47769
- for (const hidUserId of facialUserIds) {
47770
- const identity = identitiesByUserId.get(String(hidUserId));
47771
- if (!identity?._id) {
47772
- unmappedCount += 1;
47773
- continue;
47774
- }
47775
- const metadata = isUnknownRecord2(identity.metadata) ? identity.metadata : {};
47776
- await repo.updateIdentity(identity._id, {
47777
- metadata: {
47778
- ...metadata,
47779
- facialData: String(hidUserId),
47780
- facialEnrolled: true,
47781
- facialSyncedAt: syncedAt
47782
- }
47783
- });
47784
- syncedCount += 1;
47785
- }
47786
- const message = [
47787
- facialUserIds.length ? `Detected ${facialUserIds.length} facial enrollment(s) on the HID reader.` : "No facial enrollments found on the HID reader.",
47788
- syncedCount ? `${syncedCount} mapped HID user(s) updated.` : "",
47789
- unmappedCount ? `${unmappedCount} facial enrollment(s) still need HID User Mapping.` : ""
47790
- ].filter(Boolean).join(" ");
47814
+ const message = facialUserIds.length ? `Found ${facialUserIds.length} Amico user(s) with facial data. Facial status and images are read directly from the reader.` : "No facial enrollments found on the HID reader.";
47791
47815
  await repo.updateById(readerId, { status: "active", lastSeenAt: /* @__PURE__ */ new Date() });
47792
47816
  await repo.addEvent({
47793
47817
  reader: readerId,
47794
47818
  site: reader.site,
47795
47819
  type: "facial_sync",
47796
- payload: { facialDetected: facialUserIds.length, syncedCount, unmappedCount }
47820
+ payload: {
47821
+ facialDetected: facialUserIds.length,
47822
+ source: "amico",
47823
+ hidUserIds: facialUserIds
47824
+ }
47797
47825
  });
47798
47826
  return {
47799
47827
  readerId,
47800
47828
  facialDetected: facialUserIds.length,
47801
- syncedCount,
47802
- unmappedCount,
47829
+ // Retain these response fields for existing clients. A direct refresh
47830
+ // has no unmapped records and does not write a second iService copy.
47831
+ syncedCount: facialUserIds.length,
47832
+ unmappedCount: 0,
47803
47833
  message
47804
47834
  };
47805
47835
  } catch (error) {
@@ -47976,7 +48006,7 @@ function useHidAmicoService() {
47976
48006
  async function listUserCards(readerId, hidUserIdValue) {
47977
48007
  const reader = await getActiveReader(readerId);
47978
48008
  assertReaderCapability(reader, "card");
47979
- const cardType = assertReaderCardProfile(reader);
48009
+ const cardType = reader.cardReadingMode === "pacs" || reader.cardReadingMode === "csn" ? reader.cardReadingMode : "unknown";
47980
48010
  const hidUserId = Number(hidUserIdValue);
47981
48011
  const client = new HidAmicoClient(reader);
47982
48012
  try {
@@ -48936,9 +48966,11 @@ function useHidAmicoService() {
48936
48966
  return {
48937
48967
  authorizeReader,
48938
48968
  authorizeReaderUser,
48969
+ authorizeVisitorForMember,
48939
48970
  authorizeSiteForCaller,
48940
48971
  authorizeHidAccess,
48941
48972
  listReaders,
48973
+ getMemberReaderInfo,
48942
48974
  discoverReader,
48943
48975
  createReader,
48944
48976
  updateReader,
@@ -48958,6 +48990,7 @@ function useHidAmicoService() {
48958
48990
  deleteIdentity,
48959
48991
  runObjectOperation,
48960
48992
  issueVisitorQr,
48993
+ getVisitorQrForMember,
48961
48994
  revokeVisitorQr,
48962
48995
  queueVisitorQrJob,
48963
48996
  processVisitorQrGatewayJobs,
@@ -58864,11 +58897,6 @@ function useSiteBillingConfigurationController() {
58864
58897
  } = useSiteBillingConfigurationRepo();
58865
58898
  const { add: _add } = useSiteBillingConfigurationService();
58866
58899
  async function add(req, res, next) {
58867
- const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
58868
- (acc, [key, value2]) => ({ ...acc, [key]: value2 }),
58869
- {}
58870
- );
58871
- req.body.createdBy = cookies?.["user"] ? cookies["user"].toString() : req.body.createdBy;
58872
58900
  const data = { ...req.body };
58873
58901
  const { error, value } = schemaBillingConfiguration.validate(data);
58874
58902
  if (error) {
@@ -58876,7 +58904,8 @@ function useSiteBillingConfigurationController() {
58876
58904
  return;
58877
58905
  }
58878
58906
  try {
58879
- const question = await _add(value);
58907
+ await requireSiteReach(req, value.site);
58908
+ const question = await _add({ ...value, createdBy: callerId(req) });
58880
58909
  res.status(201).json({
58881
58910
  message: "Site Billing Configuration added successfully.",
58882
58911
  data: question
@@ -58920,6 +58949,7 @@ function useSiteBillingConfigurationController() {
58920
58949
  }
58921
58950
  });
58922
58951
  try {
58952
+ await requireSiteReach(req, site);
58923
58953
  const data = await _getAll({
58924
58954
  search,
58925
58955
  page,
@@ -58948,6 +58978,7 @@ function useSiteBillingConfigurationController() {
58948
58978
  }
58949
58979
  try {
58950
58980
  const building = await _getById(id);
58981
+ await requireSiteReach(req, building?.site);
58951
58982
  res.json({
58952
58983
  message: "Successfully retrieved site billing configuration.",
58953
58984
  data: { building }
@@ -58970,6 +59001,10 @@ function useSiteBillingConfigurationController() {
58970
59001
  return;
58971
59002
  }
58972
59003
  try {
59004
+ const current = await _getById(_id);
59005
+ await requireSiteReach(req, current?.site);
59006
+ if (req.body?.site)
59007
+ await requireSiteReach(req, req.body.site);
58973
59008
  const result = await _updateById(_id, req.body);
58974
59009
  res.status(200).json({ message: result });
58975
59010
  return;
@@ -58989,6 +59024,8 @@ function useSiteBillingConfigurationController() {
58989
59024
  return;
58990
59025
  }
58991
59026
  try {
59027
+ const current = await _getById(_id);
59028
+ await requireSiteReach(req, current?.site);
58992
59029
  await _deleteById(_id);
58993
59030
  res.status(200).json({ message: "Successfully deleted site billing configuration." });
58994
59031
  return;
@@ -85966,6 +86003,20 @@ function useHidAmicoController() {
85966
86003
  next(error2);
85967
86004
  }
85968
86005
  }
86006
+ async function getMemberReaderInfo(req, res, next) {
86007
+ const { error, value } = schemaHidAmicoReaderIdParams.validate(req.params);
86008
+ if (error) {
86009
+ next(new import_node_server_utils294.BadRequestError(error.message));
86010
+ return;
86011
+ }
86012
+ try {
86013
+ res.json({
86014
+ data: await service.getMemberReaderInfo(value.readerId, getAuthenticatedUserId(req))
86015
+ });
86016
+ } catch (requestError) {
86017
+ next(requestError);
86018
+ }
86019
+ }
85969
86020
  async function createReader(req, res, next) {
85970
86021
  const { error, value } = schemaHidAmicoReader.validate(req.body);
85971
86022
  if (error) {
@@ -86153,7 +86204,8 @@ function useHidAmicoController() {
86153
86204
  query2.value.limit,
86154
86205
  query2.value.search,
86155
86206
  query2.value.status,
86156
- query2.value.includeVisitors
86207
+ query2.value.includeVisitors,
86208
+ query2.value.userType
86157
86209
  ));
86158
86210
  } catch (error) {
86159
86211
  next(error);
@@ -86359,6 +86411,11 @@ function useHidAmicoController() {
86359
86411
  return;
86360
86412
  }
86361
86413
  try {
86414
+ await service.authorizeVisitorForMember(
86415
+ params.value.readerId,
86416
+ body.value.visitorId,
86417
+ getAuthenticatedUserId(req)
86418
+ );
86362
86419
  res.status(201).json({
86363
86420
  data: await service.issueVisitorQr(params.value.readerId, body.value)
86364
86421
  });
@@ -86366,6 +86423,24 @@ function useHidAmicoController() {
86366
86423
  next(error);
86367
86424
  }
86368
86425
  }
86426
+ async function getVisitorQr(req, res, next) {
86427
+ const { error, value } = schemaHidAmicoVisitorImageParams.validate(req.params);
86428
+ if (error) {
86429
+ next(new import_node_server_utils294.BadRequestError(error.message));
86430
+ return;
86431
+ }
86432
+ try {
86433
+ res.json({
86434
+ data: await service.getVisitorQrForMember(
86435
+ value.readerId,
86436
+ value.visitorId,
86437
+ getAuthenticatedUserId(req)
86438
+ )
86439
+ });
86440
+ } catch (requestError) {
86441
+ next(requestError);
86442
+ }
86443
+ }
86369
86444
  async function discoverReader(req, res, next) {
86370
86445
  const { error, value } = schemaDiscoverHidAmicoReader.validate(req.body);
86371
86446
  if (error) {
@@ -86399,7 +86474,7 @@ function useHidAmicoController() {
86399
86474
  return;
86400
86475
  }
86401
86476
  try {
86402
- await authorizeReader(req, value.readerId);
86477
+ await authorizeReaderUser(req, value.readerId, value.hidUserId);
86403
86478
  res.json({ data: await service.listUserCards(value.readerId, value.hidUserId) });
86404
86479
  } catch (requestError) {
86405
86480
  next(requestError);
@@ -86433,7 +86508,7 @@ function useHidAmicoController() {
86433
86508
  return;
86434
86509
  }
86435
86510
  try {
86436
- await authorizeReader(req, params.value.readerId);
86511
+ await authorizeReaderUser(req, params.value.readerId, params.value.hidUserId);
86437
86512
  res.status(201).json({
86438
86513
  data: await service.enrollUserCard(
86439
86514
  params.value.readerId,
@@ -86466,7 +86541,7 @@ function useHidAmicoController() {
86466
86541
  return;
86467
86542
  }
86468
86543
  try {
86469
- await authorizeReader(req, value.readerId);
86544
+ await authorizeReaderUser(req, value.readerId, value.hidUserId);
86470
86545
  res.json({
86471
86546
  data: await service.deleteUserCard(
86472
86547
  value.readerId,
@@ -86725,6 +86800,7 @@ function useHidAmicoController() {
86725
86800
  authorizeManage,
86726
86801
  authorizeVisitor,
86727
86802
  listReaders,
86803
+ getMemberReaderInfo,
86728
86804
  discoverReader,
86729
86805
  createReader,
86730
86806
  updateReader,
@@ -86760,6 +86836,7 @@ function useHidAmicoController() {
86760
86836
  setConfiguration,
86761
86837
  runObjectOperation,
86762
86838
  issueVisitorQr,
86839
+ getVisitorQr,
86763
86840
  revokeVisitorQr,
86764
86841
  getProfileFacial,
86765
86842
  setProfileFacial,