@7365admin1/layer-common 4.149.1 → 4.150.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +16 -4
  2. package/components/EntryPassInformation.vue +13 -0
  3. package/components/HidAccessPermissions.vue +202 -40
  4. package/components/HidEnabledGate.vue +29 -10
  5. package/components/HidIntercomManagement.vue +26 -13
  6. package/components/HidProfileQrCode.vue +6 -2
  7. package/components/HidQrCodeConfiguration.vue +2 -2
  8. package/components/HidReaderForm.vue +239 -1
  9. package/components/HidReaderManagement.vue +26 -8
  10. package/components/HidReaderUserRoster.vue +3 -1
  11. package/components/HidServiceSettingsPanel.vue +2 -2
  12. package/components/HidUserEnrollment.vue +1662 -162
  13. package/components/MemberMain.vue +1 -1
  14. package/components/OvernightParkingAvailability.vue +123 -11
  15. package/components/SiteSettings.vue +5 -2
  16. package/components/VisitorForm.vue +39 -11
  17. package/composables/useCleaningPermission.ts +11 -0
  18. package/composables/useHidAccessPermission.ts +176 -81
  19. package/composables/useHidAmico.ts +37 -0
  20. package/composables/useHidNavigation.ts +44 -23
  21. package/composables/useLandscapingPermission.ts +11 -0
  22. package/composables/useMechanicalElectricalPermission.ts +5 -0
  23. package/composables/usePestControlPermission.ts +11 -0
  24. package/composables/usePoolMaintenancePermission.ts +11 -0
  25. package/composables/usePropertyPermission.ts +31 -7
  26. package/composables/useSecurityPermission.ts +33 -13
  27. package/composables/useServiceProvider.ts +9 -4
  28. package/constants/permissions.ts +73 -21
  29. package/package.json +1 -1
  30. package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +3 -0
  31. package/types/overnight-parking.d.ts +14 -0
  32. package/types/site.d.ts +7 -1
  33. package/utils/hid-enrolment-subject.ts +226 -4
  34. package/utils/hid-permission-assignments.ts +75 -6
  35. package/utils/hid-qr-validity.ts +25 -0
  36. package/utils/hid-reader-audiences.ts +557 -0
  37. package/utils/hid-registration.ts +24 -0
  38. package/utils/module-routes.ts +15 -2
  39. package/utils/nav-sections.ts +1 -1
@@ -204,6 +204,22 @@ export default function useHidAmico() {
204
204
  });
205
205
  }
206
206
 
207
+ /**
208
+ * The readers a member may enrol THEMSELVES on: the site's enabled readers
209
+ * with a portal, each with its credential capabilities, and nothing else.
210
+ *
211
+ * The self service screens (own QR, face, PIN, card) load this instead of
212
+ * `getReaders`: that one is an admin list behind the HID view right, and a
213
+ * member holding only `hid-mgmt:use-hid-access` is refused on it. This route
214
+ * asks for site membership and the use right. Response: `{ data: { items } }`.
215
+ */
216
+ function getEnrollmentReaders(siteId: string) {
217
+ return useNuxtApp().$api<{ data: { items: HidApiRecord[] } }>(
218
+ `${basePath}/sites/${siteId}/enrollment-readers`,
219
+ { method: "GET" },
220
+ );
221
+ }
222
+
207
223
  /**
208
224
  * The signed-in member's own QR code on one reader.
209
225
  *
@@ -398,6 +414,25 @@ export default function useHidAmico() {
398
414
  }
399
415
 
400
416
 
417
+ /**
418
+ * Put a person's STORED enrolment photo on a reader.
419
+ *
420
+ * No body: the image is the one already on file from when they were first
421
+ * captured. This is what makes a reader that was offline during an enrolment
422
+ * retryable WITHOUT the person coming back - a face template lives on one
423
+ * device and cannot be copied, so the alternative is a fresh capture at every
424
+ * reader.
425
+ *
426
+ * Keyed by the iService account, not a HID user id: the caller does not know
427
+ * which number the person carries on which reader, and the server looks it up.
428
+ */
429
+ function pushStoredFace(readerId: string, userId: string) {
430
+ return useNuxtApp().$api<{ data: HidFacialEnrollmentResult; message?: string }>(
431
+ `${basePath}/readers/${readerId}/accounts/${userId}/stored-face`,
432
+ { method: "POST" },
433
+ );
434
+ }
435
+
401
436
  function setUserImage(
402
437
  readerId: string,
403
438
  hidUserId: string | number,
@@ -660,6 +695,7 @@ export default function useHidAmico() {
660
695
 
661
696
  return {
662
697
  getReaders,
698
+ getEnrollmentReaders,
663
699
  getProfileQr,
664
700
  issueProfileQr,
665
701
  deleteProfileQr,
@@ -681,6 +717,7 @@ export default function useHidAmico() {
681
717
  reconcileReaderIdentificationMethods,
682
718
  getLogs,
683
719
  setUserImage,
720
+ pushStoredFace,
684
721
  deleteUserImage,
685
722
  getUserCards,
686
723
  assignUserCard,
@@ -3,23 +3,23 @@ import type { ComputedRef, Ref } from "vue";
3
3
  /**
4
4
  * THE HID FACE READER MENU IS A PERMISSION, NOT A SERVICE FLAG.
5
5
  *
6
- * This composable used to draw the five-item "HID Face Reader" group whenever
7
- * `getHidServiceEnabled(site)` was true. That is a SITE SERVICE FLAG — it says
8
- * the estate bought HID, not that this member may use it. Since the six
9
- * `access-mgmt` screens were gated (`HidEnabledGate.vue`, layer-common #1363 /
10
- * 4.2.14) a member without an access-card right still saw all five items and
11
- * only met the refusal after clicking. A module a member cannot use should not
12
- * be in the menu at all.
6
+ * This composable used to draw the six item "HID Reader" group whenever
7
+ * `getHidServiceEnabled(site)` was true. That is a SITE SERVICE FLAG: it says
8
+ * the estate bought HID, not that this member may use it. A module a member
9
+ * cannot use should not be in the menu at all, so the menu asks the same
10
+ * questions the pages ask (`useHidAccessPermission`), ANDed with the service
11
+ * flag: no HID service still means no menu.
13
12
  *
14
- * The menu now asks the SAME question the pages ask —
15
- * `useHidAccessPermission().canAccessHidScreens` — ANDed with the service flag,
16
- * so no HID service still means no menu.
13
+ * Decision of 2026-10-16: the admin entries (Face Reader, HID Reader Users, HID
14
+ * Cards, Intercom, Access Logs) follow `canViewHid`; the member's own "HID QR
15
+ * Code" entry follows `canUseHid`. The group is drawn when at least one entry
16
+ * is, so a member holding only `use-hid-access` still finds their QR page, and
17
+ * a member holding only the admin rights does not see a page the server would
18
+ * refuse them. `site-settings:manage-entry-pass` no longer draws anything here.
17
19
  *
18
- * `Layout/NavigationDrawer.vue` used to keep its own hand-written copy of the
19
- * permission list, and that copy was NARROWER than the page gate: it omitted
20
- * `replace-access-card` and `site-settings:manage-entry-pass`. Folding it in
21
- * here therefore WIDENS — nobody who could see the menu can lose it — and
22
- * leaves one list in one place.
20
+ * `Layout/NavigationDrawer.vue` keeps no permission list of its own: it takes
21
+ * `menuItem` from here and runs it through `withoutDeniedRoutes`, where the
22
+ * client's module list (`hid-mgmt`) can take the whole group away again.
23
23
  */
24
24
 
25
25
  type HidNavigationParams = {
@@ -32,14 +32,21 @@ type HidNavigationSite = Record<string, unknown>;
32
32
  export default function useHidNavigation() {
33
33
  const { getSiteById } = useSiteSettings();
34
34
  const { getHidServiceEnabled } = useOptionalServices();
35
- const { canAccessHidScreens } = useHidAccessPermission();
35
+ const { canViewHid, canUseHid } = useHidAccessPermission();
36
36
 
37
+ /**
38
+ * The whole group, unfiltered. Exported for callers that compose their own
39
+ * menus; `useHidFaceReaderMenu` below is the one that applies the rights.
40
+ * Each entry says which right draws it so the filter cannot drift from the
41
+ * list.
42
+ */
37
43
  function getHidFaceReaderMenu({ org, site }: HidNavigationParams) {
38
44
  return {
39
- title: "HID Face Reader",
45
+ title: "HID Reader",
40
46
  children: [
41
47
  {
42
48
  title: "Face Reader",
49
+ hidRight: "view" as const,
43
50
  route: {
44
51
  name: "org-site-access-mgmt-hid-readers",
45
52
  params: { org, site },
@@ -61,6 +68,7 @@ export default function useHidNavigation() {
61
68
  // `hid-users` is left in place and simply unlisted - the same state
62
69
  // `administrator` was in - so any existing link to it still resolves.
63
70
  title: "HID Reader Users",
71
+ hidRight: "view" as const,
64
72
  route: {
65
73
  name: "org-site-access-mgmt-administrator",
66
74
  params: { org, site },
@@ -71,6 +79,7 @@ export default function useHidNavigation() {
71
79
  // entry in this group acts on a resident or a visitor; this one has
72
80
  // no subject but the caller.
73
81
  title: "HID QR Code",
82
+ hidRight: "use" as const,
74
83
  route: {
75
84
  name: "org-site-access-mgmt-hid-qr-code",
76
85
  params: { org, site },
@@ -78,6 +87,7 @@ export default function useHidNavigation() {
78
87
  },
79
88
  {
80
89
  title: "HID Cards",
90
+ hidRight: "view" as const,
81
91
  route: {
82
92
  name: "org-site-access-mgmt-hid-cards",
83
93
  params: { org, site },
@@ -85,6 +95,7 @@ export default function useHidNavigation() {
85
95
  },
86
96
  {
87
97
  title: "Intercom",
98
+ hidRight: "view" as const,
88
99
  route: {
89
100
  name: "org-site-access-mgmt-intercom",
90
101
  params: { org, site },
@@ -92,6 +103,7 @@ export default function useHidNavigation() {
92
103
  },
93
104
  {
94
105
  title: "Access Logs",
106
+ hidRight: "view" as const,
95
107
  route: {
96
108
  name: "org-site-access-mgmt-access-logs",
97
109
  params: { org, site },
@@ -138,14 +150,23 @@ export default function useHidNavigation() {
138
150
  getHidServiceEnabled(site.value, siteDetails.value || fallbackSite?.value),
139
151
  );
140
152
 
141
- // ...AND this member may open the screens behind it. `enabled` keeps its
142
- // name and its meaning for callers: "should the HID menu be drawn".
143
- const enabled = computed(() => serviceEnabled.value && canAccessHidScreens.value);
144
-
145
- const menuItem = computed(() =>
146
- enabled.value ? getHidFaceReaderMenu({ org: org.value, site: site.value }) : null,
153
+ // ...AND this member may open at least one screen behind it. `enabled`
154
+ // keeps its name and its meaning for callers: "should the HID menu be drawn".
155
+ const enabled = computed(
156
+ () => serviceEnabled.value && (canViewHid.value || canUseHid.value),
147
157
  );
148
158
 
159
+ // Only the entries this member's rights draw. The admin entries need
160
+ // `canViewHid`; the member's own QR page needs `canUseHid`.
161
+ const menuItem = computed(() => {
162
+ if (!enabled.value) return null;
163
+ const group = getHidFaceReaderMenu({ org: org.value, site: site.value });
164
+ const children = group.children.filter((child) =>
165
+ child.hidRight === "use" ? canUseHid.value : canViewHid.value,
166
+ );
167
+ return children.length ? { ...group, children } : null;
168
+ });
169
+
149
170
  return {
150
171
  enabled,
151
172
  serviceEnabled,
@@ -285,6 +285,17 @@ export default function useLandscapingPermission() {
285
285
  description: "Allows the user to manage site settings.",
286
286
  },
287
287
  },
288
+ // HID access, user side only (decision of 2026-10-16). The service apps
289
+ // have no HID admin screens yet, so a role here can be prepared with the
290
+ // one string that lets a member use their own QR code, face, PIN, card and
291
+ // intercom. Mirrored from core `src/utils/hid-access.util.ts`.
292
+ "hid-mgmt": {
293
+ "use-hid-access": {
294
+ check: true,
295
+ description:
296
+ "Allows the user to see HID Access in the app and use their own QR code, face, PIN, card and intercom at this site.",
297
+ },
298
+ },
288
299
  };
289
300
 
290
301
  return {
@@ -52,6 +52,11 @@ export default function useMechanicalElectricalPermission() {
52
52
  "area-mgmt": schedulePermissions["area-mgmt"],
53
53
  "unit-mgmt": schedulePermissions["unit-mgmt"],
54
54
  "site-settings": schedulePermissions["site-settings"],
55
+ // HID access, user side only (2026-10-19): the M&E mobile app gains the
56
+ // HID user side, so an M&E role must be able to hold `use-hid-access`.
57
+ // Taken from the cleaning catalogue like the keys above (one action, same
58
+ // description), so the two cannot drift. Catalogue-only: grants nothing.
59
+ "hid-mgmt": schedulePermissions["hid-mgmt"],
55
60
  "cleaning-schedule-mgmt": schedulePermissions["cleaning-schedule-mgmt"],
56
61
  "schedule-task-mgmt": schedulePermissions["schedule-task-mgmt"],
57
62
  "attendance-mgmt": schedulePermissions["attendance-mgmt"],
@@ -93,6 +93,17 @@ export default function usePestControlPermission() {
93
93
  description: "Allows the user to manage and update site settings.",
94
94
  },
95
95
  },
96
+ // HID access, user side only (decision of 2026-10-16). The service apps
97
+ // have no HID admin screens yet, so a role here can be prepared with the
98
+ // one string that lets a member use their own QR code, face, PIN, card and
99
+ // intercom. Mirrored from core `src/utils/hid-access.util.ts`.
100
+ "hid-mgmt": {
101
+ "use-hid-access": {
102
+ check: true,
103
+ description:
104
+ "Allows the user to see HID Access in the app and use their own QR code, face, PIN, card and intercom at this site.",
105
+ },
106
+ },
96
107
  // area management permissions
97
108
  "area-mgmt": {
98
109
  "see-all-areas": {
@@ -93,6 +93,17 @@ export default function usePoolMaintenancePermission() {
93
93
  description: "Allows the user to manage and update site settings.",
94
94
  },
95
95
  },
96
+ // HID access, user side only (decision of 2026-10-16). The service apps
97
+ // have no HID admin screens yet, so a role here can be prepared with the
98
+ // one string that lets a member use their own QR code, face, PIN, card and
99
+ // intercom. Mirrored from core `src/utils/hid-access.util.ts`.
100
+ "hid-mgmt": {
101
+ "use-hid-access": {
102
+ check: true,
103
+ description:
104
+ "Allows the user to see HID Access in the app and use their own QR code, face, PIN, card and intercom at this site.",
105
+ },
106
+ },
96
107
  // area management permissions
97
108
  "area-mgmt": {
98
109
  "see-all-areas": {
@@ -14,7 +14,8 @@ import { useCommonPermissions } from "./useCommonPermission";
14
14
  * Two copies is how they drift. On 2026-09-05 the `-org` copy was found to be a
15
15
  * strict SUBSET of `-property-management`'s — **32 permissions short**, nine
16
16
  * whole families plus all six `access-card-mgmt` strings that
17
- * `core/utils/hid-access.util.ts` actually enforces — so a role built through
17
+ * `core/utils/hid-access.util.ts` enforced at the time (HID reads its own
18
+ * `hid-mgmt` family since 2026-10-16) — so a role built through
18
19
  * the onboarding wizard could never be granted them, and the modules were
19
20
  * unreachable for anybody who joined that way. web-app-org #189 closed it by
20
21
  * copying the file across, which left two identical copies and the same trap
@@ -256,10 +257,6 @@ export default function usePropertyPermission() {
256
257
  description:
257
258
  "Allows the user to remove an access card from the system permanently.",
258
259
  },
259
- view: {
260
- check: true,
261
- description: "Allows the user to view an access card's details.",
262
- },
263
260
  "assign-access-card": {
264
261
  check: true,
265
262
  description: "Allows the user to assign an access card to a person.",
@@ -269,10 +266,37 @@ export default function usePropertyPermission() {
269
266
  description:
270
267
  "Allows the user to replace a lost or damaged access card with a new one.",
271
268
  },
272
- "manage-hid": {
269
+ },
270
+ // HID access management (decision of 2026-10-16). Five strings under one
271
+ // resource, mirrored from core `src/utils/hid-access.util.ts`; the HID
272
+ // screens, menu and buttons (`useHidAccessPermission`) and every
273
+ // `/api/access-management/hid` route read them. Wildcard passes all five,
274
+ // an empty list passes none, either manage right implies view.
275
+ "hid-mgmt": {
276
+ "use-hid-access": {
277
+ check: true,
278
+ description:
279
+ "Allows the user to see HID Access in the app and use their own QR code, face, PIN, card and intercom at this site.",
280
+ },
281
+ "see-all-hid": {
282
+ check: true,
283
+ description:
284
+ "Allows the user to view the site's HID readers, enrolled people, cards and access logs.",
285
+ },
286
+ "manage-hid-readers": {
287
+ check: true,
288
+ description:
289
+ "Allows the user to add, configure, test, sync and remove HID readers. Includes viewing.",
290
+ },
291
+ "manage-hid-users": {
292
+ check: true,
293
+ description:
294
+ "Allows the user to enrol and remove people, cards and PINs, and assign HID access groups. Includes viewing.",
295
+ },
296
+ "manage-hid-settings": {
273
297
  check: true,
274
298
  description:
275
- "Allows the user to manage the HID door readers an access card opens.",
299
+ "Allows the user to switch HID on or off for the site and configure the HID QR code.",
276
300
  },
277
301
  },
278
302
  "service-provider-mgmt": {
@@ -575,12 +575,10 @@ export default function useSecurityPermission() {
575
575
  description: "Allows the user to delete/remove a unit.",
576
576
  },
577
577
  },
578
- // The strings API-core's HID routes already enforce. `authorizeView` and
579
- // `authorizeManage` on every `/api/access-management/hid` route resolve to
580
- // core's `utils/hid-access.util.ts` lists -- this family -- so a security
581
- // agency that cannot be granted them is refused by the server on every HID
582
- // call. Offering them here does not open any gate; it makes the server's
583
- // existing rule grantable.
578
+ // Access cards. Until 2026-10 this family was also what every
579
+ // `/api/access-management/hid` route read; HID now has `hid-mgmt` below and
580
+ // the card strings here mean cards again (the server still accepts them
581
+ // for HID during the alias period, see `useHidAccessPermission`).
584
582
  "access-card-mgmt": {
585
583
  "see-all-card": {
586
584
  check: true,
@@ -590,11 +588,6 @@ export default function useSecurityPermission() {
590
588
  check: true,
591
589
  description: "Allows the user to view QR tagging.",
592
590
  },
593
- view: {
594
- check: true,
595
- description:
596
- "Allows the user to view access card management, including HID readers and access logs.",
597
- },
598
591
  "add-access-card": {
599
592
  check: true,
600
593
  description: "Allows the user to add a new access card to the system.",
@@ -616,10 +609,37 @@ export default function useSecurityPermission() {
616
609
  check: true,
617
610
  description: "Allows the user to replace a lost or damaged access card.",
618
611
  },
619
- "manage-hid": {
612
+ },
613
+ // HID access management (decision of 2026-10-16). Five strings under one
614
+ // resource, mirrored from core `src/utils/hid-access.util.ts`; the HID
615
+ // screens, menu and buttons (`useHidAccessPermission`) and every
616
+ // `/api/access-management/hid` route read them. Wildcard passes all five,
617
+ // an empty list passes none, either manage right implies view.
618
+ "hid-mgmt": {
619
+ "use-hid-access": {
620
+ check: true,
621
+ description:
622
+ "Allows the user to see HID Access in the app and use their own QR code, face, PIN, card and intercom at this site.",
623
+ },
624
+ "see-all-hid": {
625
+ check: true,
626
+ description:
627
+ "Allows the user to view the site's HID readers, enrolled people, cards and access logs.",
628
+ },
629
+ "manage-hid-readers": {
630
+ check: true,
631
+ description:
632
+ "Allows the user to add, configure, test, sync and remove HID readers. Includes viewing.",
633
+ },
634
+ "manage-hid-users": {
635
+ check: true,
636
+ description:
637
+ "Allows the user to enrol and remove people, cards and PINs, and assign HID access groups. Includes viewing.",
638
+ },
639
+ "manage-hid-settings": {
620
640
  check: true,
621
641
  description:
622
- "Allows the user to manage HID face readers, cards and access records for the site.",
642
+ "Allows the user to switch HID on or off for the site and configure the HID QR code.",
623
643
  },
624
644
  },
625
645
  // Two resources below are checked ONLY by the iSecure365 mobile app, which
@@ -19,10 +19,15 @@ export type TSiteProviderMember = {
19
19
  unit?: string | null;
20
20
  unitName?: string;
21
21
  /**
22
- * The person's user account. NOT yet returned by the endpoint — the
23
- * repository projects it but the service layer drops it. Anything acting on
24
- * the PERSON (assigning an access card writes it onto the card as `userId`)
25
- * needs this rather than `_id`, which identifies the membership.
22
+ * The person's user account. Anything acting on the PERSON needs this rather
23
+ * than `_id`, which identifies the membership: an access card carries it as
24
+ * `userId`, and HID reader access is bound by it -
25
+ * `resolvePermissionUserBindings` drops every subject without one.
26
+ *
27
+ * Returned from core 3.130. Before that the repository projected it and the
28
+ * service layer dropped it, so against an older API it is absent on every
29
+ * row - `providerAccountsKnown` is how the HID enrolment screen tells the two
30
+ * apart rather than reading an absence as "nobody has an account".
26
31
  */
27
32
  user?: string | null;
28
33
  };
@@ -380,16 +380,38 @@ export const PERMISSIONS = {
380
380
  "add-service-provider",
381
381
  "invite-service-provider",
382
382
  ],
383
+ // Access cards and QR tagging: the Property Management screens of those
384
+ // names. Until 2026-10 the HID screens borrowed this family (core
385
+ // `hid-access.util.ts` read nine of its strings); HID now has `hid-mgmt`
386
+ // below. `view` and `manage-hid` were only ever HID rights, so they left with
387
+ // it: see `RETIRED_PERMISSIONS` for how they are still read during the alias
388
+ // period.
383
389
  "access-card-mgmt": [
384
390
  "see-all-card",
385
391
  "see-all-qr-tagging",
386
- "view",
387
392
  "add-access-card",
388
393
  "update-access-card",
389
394
  "replace-access-card",
390
395
  "delete-access-card",
391
396
  "assign-access-card",
392
- "manage-hid",
397
+ ],
398
+ // HID access management (decision of 2026-10-16). The five strings are
399
+ // defined in core `src/utils/hid-access.util.ts` (`HID_RESOURCE` and the
400
+ // `HID_*_PERMISSIONS` lists); this package does not depend on `@7365admin1/core`,
401
+ // so it keeps its own copy and `test/hid-access-gate.test.mjs` pins the five.
402
+ // use-hid-access user side: own QR, face, PIN, card, intercom
403
+ // see-all-hid read any HID admin screen
404
+ // manage-hid-readers add, configure, test, sync, remove readers
405
+ // manage-hid-users enrol and remove people, cards, PINs, groups
406
+ // manage-hid-settings HID on or off and the QR configuration for a site
407
+ // Wildcard passes all five; an empty list passes none; either manage right
408
+ // implies view. The module deny list key is the resource name.
409
+ "hid-mgmt": [
410
+ "use-hid-access",
411
+ "see-all-hid",
412
+ "manage-hid-readers",
413
+ "manage-hid-users",
414
+ "manage-hid-settings",
393
415
  ],
394
416
 
395
417
  // ---- security ----
@@ -659,12 +681,14 @@ export const LEGACY_PERMISSION_ALIASES: Readonly<
659
681
  /**
660
682
  * Strings the SERVER enforces that no UI checks and no role editor can grant.
661
683
  *
662
- * `core/src/utils/hid-access.util.ts:36-38` gates the HID Amico visitor
663
- * endpoints on `visitor:create`, `visitor:update` and `visitor:check-out`. The
664
- * whole estate spells visitor rights `visitor-mgmt:add-visitor`,
665
- * `:update-visitor` and so on; a bare `visitor` resource appears in no catalogue
666
- * in any repo, so no role can hold these and the branch is reachable only
667
- * through `*` or through the `access-card-mgmt` strings the same list unions in.
684
+ * `core/src/utils/hid-access.util.ts` keeps `visitor:create`, `visitor:update`
685
+ * and `visitor:check-out` in `HID_VISITOR_PERMISSIONS` (the visitor pass revoke
686
+ * routes) until the alias drop. The whole estate spells visitor rights
687
+ * `visitor-mgmt:add-visitor`, `:update-visitor` and so on; a bare `visitor`
688
+ * resource appears in no catalogue in any repo, so no role can hold these and
689
+ * the branch is reachable only through `*`, through
690
+ * `visitor-mgmt:update-visitor` (which the same list now also accepts) or
691
+ * through the HID manage users strings it unions in.
668
692
  *
669
693
  * They are NOT aliased to `visitor-mgmt:*` here, because doing so would be a
670
694
  * decision with a real consequence in both directions:
@@ -695,19 +719,47 @@ export const SERVER_ONLY_UNGRANTABLE: readonly string[] = Object.freeze([
695
719
  * Canonical strings that are CHECKED -- by this package and by the server -- and
696
720
  * that no role editor can GRANT.
697
721
  *
698
- * `NavigationDrawer.vue:172-180` accepts eight `access-card-mgmt` strings for
699
- * the HID menu and `core/src/utils/hid-access.util.ts:19-32` enforces all nine;
700
- * only six of them appear in any app's catalogue. The menu still opens, because
701
- * the gate is an OR and the other six are grantable -- but a role built out of
702
- * only these three is refused at both ends while looking correctly configured in
703
- * the role editor. Frozen here so the list cannot grow without somebody noticing.
722
+ * Empty since 2026-10-16. It used to hold `access-card-mgmt:view`,
723
+ * `:assign-access-card` and `:manage-hid`, which the HID menu accepted and
724
+ * `core/src/utils/hid-access.util.ts` enforced while no app's catalogue offered
725
+ * them. HID now has its own `hid-mgmt` family, offered in full by the property
726
+ * and security catalogues and as `use-hid-access` by the five service
727
+ * catalogues (M&E joined on 2026-10-19); `assign-access-card` is offered by
728
+ * both card catalogues; `view` and `manage-hid` are retired (below). Kept,
729
+ * and still pinned by the drift test, so the next string of this shape is
730
+ * recorded here rather than found by somebody staring at a blank screen.
704
731
  */
705
732
  export const CHECKED_BUT_UNGRANTABLE: readonly TPermissionString[] =
706
- Object.freeze([
707
- "access-card-mgmt:view",
708
- "access-card-mgmt:assign-access-card",
709
- "access-card-mgmt:manage-hid",
710
- ]);
733
+ Object.freeze([]);
734
+
735
+ /**
736
+ * Strings that are no longer canonical and have no canonical equivalent, but
737
+ * that this package and the server STILL READ for one release.
738
+ *
739
+ * `access-card-mgmt:view` and `access-card-mgmt:manage-hid` were the HID
740
+ * rights before `hid-mgmt` existed. Core `hid-access.util.ts` keeps them in
741
+ * `HID_LEGACY_VIEW_PERMISSIONS` and `HID_LEGACY_MANAGE_PERMISSIONS` until the
742
+ * live roles are backfilled onto the five `hid-mgmt` strings, and
743
+ * `useHidAccessPermission` reads them at the same levels during that period.
744
+ * No role editor offers them any more, and nothing new may be written against
745
+ * them.
746
+ *
747
+ * They are NOT legacy aliases: an alias joins two spellings of ONE grant into
748
+ * an equivalence class (`utils/permission-spellings.ts`) and joins the two
749
+ * resources into one module spelling group (`utils/org-modules.ts`). Access
750
+ * cards and HID are two modules, and `manage-hid` meant three HID levels at
751
+ * once, so neither of those is true here.
752
+ *
753
+ * `isKnownPermission` answers true for these; `canonicalPermission` does not
754
+ * resolve them. The alias drop deletes this list and the composable's legacy
755
+ * branches together.
756
+ */
757
+ export const RETIRED_PERMISSIONS: Readonly<Record<string, string>> = Object.freeze({
758
+ "access-card-mgmt:view":
759
+ "HID view right before hid-mgmt:see-all-hid; read until the alias drop",
760
+ "access-card-mgmt:manage-hid":
761
+ "HID manage right before hid-mgmt:manage-hid-*; read until the alias drop",
762
+ });
711
763
 
712
764
  const CANONICAL = new Set<string>(PERMISSION_STRINGS);
713
765
 
@@ -724,7 +776,7 @@ export function canonicalPermission(
724
776
  return LEGACY_PERMISSION_ALIASES[value];
725
777
  }
726
778
 
727
- /** Is this string one the catalogue knows, canonical or legacy? */
779
+ /** Is this string one the catalogue knows: canonical, legacy, or retired but still read? */
728
780
  export function isKnownPermission(value: string): boolean {
729
- return canonicalPermission(value) !== undefined;
781
+ return canonicalPermission(value) !== undefined || value in RETIRED_PERMISSIONS;
730
782
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "4.149.1",
5
+ "version": "4.150.0",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
@@ -1,8 +1,11 @@
1
1
  <template>
2
2
  <v-container fluid>
3
+ <!-- The member's own pass, so the gate asks for the use right, not the
4
+ admin view right every other HID page asks for. -->
3
5
  <HidEnabledGate
4
6
  :site="siteId"
5
7
  :org="orgId"
8
+ level="use"
6
9
  message="Enable HID as a service for this site before generating a QR code."
7
10
  >
8
11
  <HidProfileQrCode :site="siteId" />
@@ -14,6 +14,20 @@ declare type TOvernightParkingAvailability = {
14
14
  sunday: TOvernightParkingDay;
15
15
  isAllowPlateNumberOpenBarrier?: boolean;
16
16
  autoApproveOvernightParking?: boolean;
17
+ visitorQrCodes?: TVisitorQrCode[];
18
+ };
19
+
20
+ declare type TVisitorQrCodeType = "hid" | "entryPass" | "security";
21
+
22
+ // How long an HID visitor pass lasts: until the end of the visit day, or
23
+ // until the visitor's first entry.
24
+ declare type TVisitorQrUsage = "wholeDay" | "singleUse";
25
+
26
+ declare type TVisitorQrCode = {
27
+ type: TVisitorQrCodeType;
28
+ isEnabled: boolean;
29
+ // HID only.
30
+ usage?: TVisitorQrUsage;
17
31
  };
18
32
 
19
33
  declare type TOvernightParkingRequestStatus =
package/types/site.d.ts CHANGED
@@ -5,7 +5,10 @@ declare type THidQrCodeFormat = "0" | "1" | "2";
5
5
  declare type THidPermissionCategory =
6
6
  | "resident"
7
7
  | "property_management"
8
- | "service_provider";
8
+ /** A provider COMPANY - granting one admits every active member of that org. */
9
+ | "service_provider"
10
+ /** ONE PERSON from a provider company, by their membership at this site. */
11
+ | "service_provider_member";
9
12
 
10
13
  declare type THidPermissionAssignment = {
11
14
  subjectId: string;
@@ -27,7 +30,10 @@ declare type THidSitePermissions = {
27
30
  counts: {
28
31
  resident: number;
29
32
  propertyManagement: number;
33
+ /** Provider COMPANIES granted. */
30
34
  serviceProvider: number;
35
+ /** Named provider people granted. Counted apart from the companies. */
36
+ serviceProviderMember: number;
31
37
  intercom: number;
32
38
  };
33
39
  };