@7365admin1/layer-common 4.73.1-staging.461 → 4.73.1-staging.463
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/components/AccessCardDetailsDialog.vue +57 -4
- package/components/AccessCardPreviewDialog.vue +39 -34
- package/components/MemberSiteAccess.vue +27 -9
- package/components/RolePermissionMain.vue +6 -0
- package/composables/useRole.ts +3 -1
- package/composables/useServiceProvider.ts +7 -0
- package/package.json +1 -1
- package/utils/site-access.ts +38 -13
|
@@ -47,14 +47,22 @@
|
|
|
47
47
|
<div>
|
|
48
48
|
<div class="card-details__label">User</div>
|
|
49
49
|
<div class="card-details__value">
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
<template v-if="holderState === 'named'">
|
|
51
|
+
{{ holderName }}
|
|
52
|
+
<template v-if="details.user?.email">({{ details.user.email }})</template>
|
|
53
|
+
</template>
|
|
54
|
+
<!-- Assigned, but the server did not send the name back. Saying
|
|
55
|
+
so beats "N/A", which reads as "nobody holds this card". -->
|
|
56
|
+
<template v-else-if="holderState === 'assigned'">
|
|
57
|
+
<span class="text-medium-emphasis">Assigned — name unavailable</span>
|
|
58
|
+
</template>
|
|
59
|
+
<template v-else>Unassigned</template>
|
|
52
60
|
</div>
|
|
53
61
|
</div>
|
|
54
62
|
|
|
55
|
-
<div v-if="
|
|
63
|
+
<div v-if="holderType">
|
|
56
64
|
<div class="card-details__label">User Type</div>
|
|
57
|
-
<div class="card-details__value">{{
|
|
65
|
+
<div class="card-details__value">{{ holderType }}</div>
|
|
58
66
|
</div>
|
|
59
67
|
|
|
60
68
|
<!-- Visitor / Resident Credentials -->
|
|
@@ -216,6 +224,51 @@ const sortedHistory = computed(() =>
|
|
|
216
224
|
)
|
|
217
225
|
);
|
|
218
226
|
|
|
227
|
+
/**
|
|
228
|
+
* WHO HOLDS THIS CARD.
|
|
229
|
+
*
|
|
230
|
+
* `userCred` is written by the visitor-pass flow and carries the person's
|
|
231
|
+
* details on the card itself. `user` is the resolved account - the card only
|
|
232
|
+
* stores `userId`, so the server has to join it.
|
|
233
|
+
*
|
|
234
|
+
* `card-details` does not do that join today (the lookup is commented out in
|
|
235
|
+
* core's `getCardDetailsRepo`), so this is empty for every card assigned
|
|
236
|
+
* through Assign to Person and the field falls through to `holderState`.
|
|
237
|
+
* Nothing here needs to change when the join is restored.
|
|
238
|
+
*/
|
|
239
|
+
const holderName = computed(
|
|
240
|
+
() => details.value?.userCred?.name ?? details.value?.user?.name ?? "",
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* A card with a `userId` IS assigned, whether or not the name came back.
|
|
245
|
+
*
|
|
246
|
+
* Printing "N/A" for both cases is the actual defect here: it reads as "nobody
|
|
247
|
+
* holds this card", which is the opposite of the truth for an assigned one and
|
|
248
|
+
* could have someone hand out a card that is already in use.
|
|
249
|
+
*/
|
|
250
|
+
const holderState = computed<"named" | "assigned" | "unassigned">(() => {
|
|
251
|
+
if (holderName.value) return "named";
|
|
252
|
+
return details.value?.userId ? "assigned" : "unassigned";
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* WHICH KIND OF PERSON HOLDS THIS CARD — Visitor/Resident, Management or
|
|
257
|
+
* Service Provider, matching the groups Assign to Person offers.
|
|
258
|
+
*
|
|
259
|
+
* `holderType` is what the server should send once it records the category the
|
|
260
|
+
* card was assigned under. `userType` is NOT that: it is the card's own pool,
|
|
261
|
+
* and `getCardDetailsRepo` filters on `EAccessCardUserTypes.DEFAULT`, so every
|
|
262
|
+
* card this dialog can load has the literal value "Visitor/Resident" whoever
|
|
263
|
+
* actually holds it. Preferring `holderType` means a card given to Management
|
|
264
|
+
* stops describing itself as a resident's the day the field arrives.
|
|
265
|
+
*
|
|
266
|
+
* Falls back to `userType` so nothing disappears in the meantime.
|
|
267
|
+
*/
|
|
268
|
+
const holderType = computed(
|
|
269
|
+
() => details.value?.holderType ?? details.value?.userType ?? "",
|
|
270
|
+
);
|
|
271
|
+
|
|
219
272
|
watch(
|
|
220
273
|
() => props.modelValue,
|
|
221
274
|
async (val) => {
|
|
@@ -453,7 +453,7 @@ const emit = defineEmits<{
|
|
|
453
453
|
const { getResidents, assignUser } = useAccessManagement();
|
|
454
454
|
const { getEncryptedAcmUrl } = useSiteEntryPassSettings();
|
|
455
455
|
const { getAll: getMembers } = useMember();
|
|
456
|
-
const {
|
|
456
|
+
const { getSiteMembers } = useServiceProvider();
|
|
457
457
|
|
|
458
458
|
const historyDialog = ref(false);
|
|
459
459
|
const showAssignForm = ref(false);
|
|
@@ -552,14 +552,12 @@ const personTypeLabel = (person: Record<string, any>) => {
|
|
|
552
552
|
const assignNoDataText = computed(() => {
|
|
553
553
|
if (peopleLoading.value) return "Loading...";
|
|
554
554
|
if (assignCategory.value === "management") return "No management members assigned to this unit";
|
|
555
|
-
if (assignCategory.value === "providers") return "No service
|
|
555
|
+
if (assignCategory.value === "providers") return "No service provider members assigned to this unit";
|
|
556
556
|
return "No residents or tenants found for this unit";
|
|
557
557
|
});
|
|
558
558
|
|
|
559
|
-
const assignUnassignableText = computed(
|
|
560
|
-
|
|
561
|
-
? "A card is held by a person, and this is the provider company. Assigning to service providers needs the server to say which account the card belongs to."
|
|
562
|
-
: "This person has no user account, so a card cannot be assigned to them.",
|
|
559
|
+
const assignUnassignableText = computed(
|
|
560
|
+
() => "This person has no account on record, so a card cannot be assigned to them.",
|
|
563
561
|
);
|
|
564
562
|
|
|
565
563
|
/** Residents and tenants of THIS UNIT - the list the dialog has always shown. */
|
|
@@ -619,41 +617,48 @@ async function loadManagementRows(): Promise<AssignRow[]> {
|
|
|
619
617
|
}
|
|
620
618
|
|
|
621
619
|
/**
|
|
622
|
-
* Service Providers
|
|
620
|
+
* Service Providers - the provider PEOPLE attached to this unit.
|
|
623
621
|
*
|
|
624
|
-
* `GET /api/service-providers?siteId&
|
|
625
|
-
*
|
|
626
|
-
* providers
|
|
622
|
+
* `GET /api/service-providers/site-members?siteId&unit=<id>`. A card is held by
|
|
623
|
+
* a person, so this lists provider staff rather than the companies
|
|
624
|
+
* `GET /api/service-providers` returns, and the `unit` filter keeps it to the
|
|
625
|
+
* people that unit actually has - the same scoping Management uses.
|
|
627
626
|
*
|
|
628
|
-
*
|
|
629
|
-
* service `type`, and there is no user account behind a company. See
|
|
630
|
-
* `assigneeId` below.
|
|
627
|
+
* The response also carries a `companies` summary; only `items` is people.
|
|
631
628
|
*/
|
|
632
629
|
async function loadProviderRows(): Promise<AssignRow[]> {
|
|
633
630
|
if (!props.siteId || !props.unit?._id) return [];
|
|
634
|
-
const res
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
631
|
+
const res = await getSiteMembers(props.siteId, props.unit._id);
|
|
632
|
+
const rows = res?.items ?? [];
|
|
633
|
+
return rows.map((member: Record<string, any>) => ({
|
|
634
|
+
/*
|
|
635
|
+
* `user` when the endpoint returns it, otherwise the MEMBERSHIP `_id`.
|
|
636
|
+
*
|
|
637
|
+
* `site-members` does not carry `user` today (the repository projects it,
|
|
638
|
+
* the service layer drops it), and the product decision is to assign on
|
|
639
|
+
* `_id` rather than wait. Worth knowing what that means: the card's
|
|
640
|
+
* `userId` then holds a `service-provider-members` id, so the card list's
|
|
641
|
+
* `userId -> users` lookup finds nothing and the holder column reads blank
|
|
642
|
+
* for these cards. The door access itself is unaffected - the ACM command
|
|
643
|
+
* is built from the CARD's id, not the assignee's.
|
|
644
|
+
*
|
|
645
|
+
* `user` is preferred so that the day it starts arriving, these cards
|
|
646
|
+
* begin resolving to real people with no change here.
|
|
647
|
+
*/
|
|
648
|
+
assigneeId: String(member.user ?? member._id ?? ""),
|
|
649
|
+
name: member.name || member.email || "",
|
|
650
|
+
subtitle: member.email ?? "",
|
|
643
651
|
/*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
* that has not been made. Sending one anyway would write an id the server
|
|
649
|
-
* stores without checking and that matches no user.
|
|
652
|
+
* The service the company provides, plus this person's role - two people
|
|
653
|
+
* from the same company differ only by role ("VMS Access" vs "Security
|
|
654
|
+
* Access"). `typeLabel` is computed by the endpoint; the local converter is
|
|
655
|
+
* the fallback for a row that predates it.
|
|
650
656
|
*/
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
raw: provider,
|
|
657
|
+
meta:
|
|
658
|
+
[member.typeLabel || serviceTypeLabel(member.type), member.role]
|
|
659
|
+
.filter(Boolean)
|
|
660
|
+
.join(" · "),
|
|
661
|
+
raw: member,
|
|
657
662
|
}));
|
|
658
663
|
}
|
|
659
664
|
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
<v-card-text class="screen-modal__body">
|
|
5
5
|
<p class="text-body-2 mb-3">
|
|
6
6
|
Choose the sites this person can open and their role at each. They will see only these sites.
|
|
7
|
+
A role can be one made for that site, or an <strong>organisation role</strong> (made once in Roles &
|
|
8
|
+
Permissions) - either way it gives its permissions at that site only.
|
|
9
|
+
</p>
|
|
10
|
+
<p class="text-body-2 mb-3">
|
|
11
|
+
<a href="#" data-test="site-access-create-org-role" @click.prevent="createOrgRole()">Create an organisation role</a>
|
|
12
|
+
to use at many sites.
|
|
7
13
|
</p>
|
|
8
14
|
|
|
9
15
|
<div v-if="loading" class="py-4 text-center"><v-progress-circular indeterminate /></div>
|
|
@@ -31,7 +37,9 @@
|
|
|
31
37
|
<div class="d-flex align-center ga-2 mb-2">
|
|
32
38
|
<v-select
|
|
33
39
|
v-model="sameRole"
|
|
34
|
-
:items="
|
|
40
|
+
:items="sharedItems"
|
|
41
|
+
item-title="title"
|
|
42
|
+
item-value="value"
|
|
35
43
|
label="Same role for all selected sites"
|
|
36
44
|
density="compact"
|
|
37
45
|
hide-details
|
|
@@ -39,27 +47,28 @@
|
|
|
39
47
|
class="filter-field"
|
|
40
48
|
data-test="site-access-same-role"
|
|
41
49
|
/>
|
|
42
|
-
<v-btn variant="outlined" :disabled="!sameRole" @click="applySame()">Apply</v-btn>
|
|
50
|
+
<v-btn variant="outlined" :disabled="!sameRole || !anyOn" @click="applySame()">Apply</v-btn>
|
|
43
51
|
</div>
|
|
52
|
+
<p v-if="sameRole && !anyOn" class="text-caption mb-2">Tick the sites first, then Apply.</p>
|
|
44
53
|
|
|
45
54
|
<div v-for="(c, i) in choices" :key="c.site" class="d-flex align-center ga-2 py-1" data-test="site-access-row">
|
|
46
55
|
<v-checkbox v-model="choices[i].on" density="compact" hide-details :label="c.name" style="min-width: 220px" />
|
|
47
56
|
<v-select
|
|
48
57
|
v-if="c.roles.length"
|
|
49
58
|
v-model="choices[i].role"
|
|
50
|
-
:items="c
|
|
51
|
-
item-title="
|
|
52
|
-
item-value="
|
|
59
|
+
:items="roleItems(c)"
|
|
60
|
+
item-title="title"
|
|
61
|
+
item-value="value"
|
|
53
62
|
:disabled="!c.on"
|
|
54
63
|
label="Role"
|
|
55
64
|
density="compact"
|
|
56
65
|
hide-details
|
|
57
66
|
class="filter-field"
|
|
58
67
|
/>
|
|
59
|
-
<span v-else class="text-caption">No roles
|
|
68
|
+
<span v-else class="text-caption">No roles for {{ c.name }} yet — create an organisation role (link above) or one in that site's Roles & Permissions.</span>
|
|
60
69
|
</div>
|
|
61
70
|
<p v-for="c in choices.filter((x) => x.on && !x.role && x.replacedRole)" :key="`r-${c.site}`" class="text-caption">
|
|
62
|
-
{{ c.name }}: "{{ c.replacedRole }}" is not
|
|
71
|
+
{{ c.name }}: "{{ c.replacedRole }}" is not a role for this site — choose an organisation role or one of {{ c.name }}'s roles.
|
|
63
72
|
</p>
|
|
64
73
|
</div>
|
|
65
74
|
|
|
@@ -78,7 +87,7 @@
|
|
|
78
87
|
|
|
79
88
|
<script setup lang="ts">
|
|
80
89
|
// Relative import: this layer's `utils/` is not auto-imported into consumers.
|
|
81
|
-
import { applySameRole, initialChoices,
|
|
90
|
+
import { applySameRole, initialChoices, roleItems, sharedRoleItems, toPayload, type SiteChoice } from "../utils/site-access";
|
|
82
91
|
|
|
83
92
|
const props = defineProps({
|
|
84
93
|
org: { type: String, required: true },
|
|
@@ -98,7 +107,16 @@ const orgWide = ref<Array<{ roleName?: string }>>([]);
|
|
|
98
107
|
const removeOrgAccess = ref(false);
|
|
99
108
|
const sameRole = ref<string | null>(null);
|
|
100
109
|
|
|
101
|
-
const
|
|
110
|
+
const sharedItems = computed(() => sharedRoleItems(choices.value));
|
|
111
|
+
const anyOn = computed(() => choices.value.some((c) => c.on));
|
|
112
|
+
|
|
113
|
+
// Organization app -> Roles & Permissions, on the organisation's own service;
|
|
114
|
+
// Create there comes back here (`returnTo`), and the box reloads its roles on open.
|
|
115
|
+
function createOrgRole() {
|
|
116
|
+
emit("cancel");
|
|
117
|
+
const back = useRoute().fullPath;
|
|
118
|
+
navigateTo({ path: `/org/${props.org}/roles-permissions`, query: { service: "own", returnTo: back } });
|
|
119
|
+
}
|
|
102
120
|
const missing = computed(() => toPayload(choices.value).missing);
|
|
103
121
|
|
|
104
122
|
function applySame() {
|
|
@@ -227,6 +227,11 @@ const props = defineProps({
|
|
|
227
227
|
type: String,
|
|
228
228
|
default: "",
|
|
229
229
|
},
|
|
230
|
+
/** "organisation": list only roles with no site (Organization app service roles). */
|
|
231
|
+
scope: {
|
|
232
|
+
type: String,
|
|
233
|
+
default: "",
|
|
234
|
+
},
|
|
230
235
|
/*
|
|
231
236
|
* Whether the Create-role dialog shows its (read-only) Site field.
|
|
232
237
|
* The dialog defaulted this to `true` but this component never declared or
|
|
@@ -429,6 +434,7 @@ const {
|
|
|
429
434
|
type: props.listType ?? props.type,
|
|
430
435
|
org: props.orgId,
|
|
431
436
|
site: props.siteId,
|
|
437
|
+
scope: props.scope,
|
|
432
438
|
}),
|
|
433
439
|
{
|
|
434
440
|
watch: [page, headerSearch],
|
package/composables/useRole.ts
CHANGED
|
@@ -24,10 +24,12 @@ export default function useRole() {
|
|
|
24
24
|
type = "",
|
|
25
25
|
org = "",
|
|
26
26
|
site = "",
|
|
27
|
+
/** "organisation": only roles with no site (needs core 3.134.0+ - an older API refuses the key with 400) */
|
|
28
|
+
scope = "",
|
|
27
29
|
} = {}) {
|
|
28
30
|
return useNuxtApp().$api<Record<string, any>>("/api/roles", {
|
|
29
31
|
method: "GET",
|
|
30
|
-
query: { search, page, limit, type, org, site },
|
|
32
|
+
query: { search, page, limit, type, org, site, ...(scope ? { scope } : {}) },
|
|
31
33
|
});
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -18,6 +18,13 @@ export type TSiteProviderMember = {
|
|
|
18
18
|
/** The building unit this person is attached to (a unit's Management tab). */
|
|
19
19
|
unit?: string | null;
|
|
20
20
|
unitName?: string;
|
|
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.
|
|
26
|
+
*/
|
|
27
|
+
user?: string | null;
|
|
21
28
|
};
|
|
22
29
|
|
|
23
30
|
export type TSiteProviderCompany = {
|
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.73.1-staging.
|
|
5
|
+
"version": "4.73.1-staging.463",
|
|
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.",
|
package/utils/site-access.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Site access editor state (owner request 2026-09-28). One row per site of the
|
|
3
|
-
* organisation; a chosen site needs one
|
|
4
|
-
* organisation
|
|
3
|
+
* organisation; a chosen site needs a role: one created FOR that site, or one of
|
|
4
|
+
* the organisation's service roles (owner request 2026-09-30, `scope:
|
|
5
|
+
* "organisation"`). Either way the server confines the membership to that site.
|
|
5
6
|
*/
|
|
6
|
-
export type SiteAccessRole = { _id: string; name: string; type?: string };
|
|
7
|
+
export type SiteAccessRole = { _id: string; name: string; type?: string; scope?: "site" | "organisation" };
|
|
7
8
|
export type SiteAccessRow = {
|
|
8
9
|
site: string;
|
|
9
10
|
name: string;
|
|
@@ -11,33 +12,57 @@ export type SiteAccessRow = {
|
|
|
11
12
|
roles: SiteAccessRole[];
|
|
12
13
|
};
|
|
13
14
|
export type SiteChoice = { site: string; name: string; on: boolean; role: string; roles: SiteAccessRole[]; replacedRole?: string };
|
|
15
|
+
/** A v-select entry: a role, or a group heading. */
|
|
16
|
+
export type RoleItem = { title: string; value?: string; type?: "subheader" };
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
const isOrgRole = (r: SiteAccessRole) => r.scope === "organisation";
|
|
19
|
+
|
|
20
|
+
/** Start from what the member has now. A current role the site does not offer is dropped. */
|
|
16
21
|
export function initialChoices(rows: SiteAccessRow[]): SiteChoice[] {
|
|
17
22
|
return (rows ?? []).map((r) => {
|
|
18
23
|
const current = r.membership?.role ?? "";
|
|
19
24
|
const valid = r.roles.some((x) => x._id === current);
|
|
20
25
|
return {
|
|
21
26
|
site: r.site, name: r.name, on: !!r.membership, role: valid ? current : "", roles: r.roles ?? [],
|
|
22
|
-
// their current role here is not one
|
|
23
|
-
//
|
|
27
|
+
// their current role here is not one this site offers (e.g. another site's
|
|
28
|
+
// role) - say so rather than blank it silently
|
|
24
29
|
...(r.membership && !valid ? { replacedRole: r.membership.roleName || "their current role" } : {}),
|
|
25
30
|
};
|
|
26
31
|
});
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
/**
|
|
30
|
-
export function
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
/** One site's Role list: "Organisation roles" first, then "<Site> roles". */
|
|
35
|
+
export function roleItems(choice: Pick<SiteChoice, "name" | "roles">): RoleItem[] {
|
|
36
|
+
const org = choice.roles.filter(isOrgRole);
|
|
37
|
+
const own = choice.roles.filter((r) => !isOrgRole(r));
|
|
38
|
+
const items: RoleItem[] = [];
|
|
39
|
+
if (org.length) items.push({ title: "Organisation roles", type: "subheader" }, ...org.map((r) => ({ title: r.name, value: r._id })));
|
|
40
|
+
if (own.length) items.push({ title: `${choice.name} roles`, type: "subheader" }, ...own.map((r) => ({ title: r.name, value: r._id })));
|
|
41
|
+
return items;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* "Same role for all selected sites", grouped: the organisation's roles (they
|
|
46
|
+
* work at every site, so they are listed even before a site is ticked), then
|
|
47
|
+
* the names of the ticked sites' own roles.
|
|
48
|
+
*/
|
|
49
|
+
export function sharedRoleItems(choices: SiteChoice[]): RoleItem[] {
|
|
50
|
+
const org = new Set<string>();
|
|
51
|
+
for (const c of choices) for (const r of c.roles) if (isOrgRole(r)) org.add(r.name);
|
|
52
|
+
const own = new Set<string>();
|
|
53
|
+
for (const c of choices) if (c.on) for (const r of c.roles) if (!isOrgRole(r) && !org.has(r.name)) own.add(r.name);
|
|
54
|
+
const sorted = (s: Set<string>) => [...s].sort((a, b) => a.localeCompare(b)).map((n) => ({ title: n, value: n }));
|
|
55
|
+
const items: RoleItem[] = [];
|
|
56
|
+
if (org.size) items.push({ title: "Organisation roles", type: "subheader" }, ...sorted(org));
|
|
57
|
+
if (own.size) items.push({ title: "Roles of the selected sites", type: "subheader" }, ...sorted(own));
|
|
58
|
+
return items;
|
|
34
59
|
}
|
|
35
60
|
|
|
36
|
-
/** Give every selected site the role with this name, where that site has one. */
|
|
61
|
+
/** Give every selected site the role with this name (an organisation role first), where that site has one. */
|
|
37
62
|
export function applySameRole(choices: SiteChoice[], roleName: string): SiteChoice[] {
|
|
38
63
|
return choices.map((c) => {
|
|
39
64
|
if (!c.on) return c;
|
|
40
|
-
const match = c.roles.find((r) => r.name === roleName);
|
|
65
|
+
const match = c.roles.find((r) => r.name === roleName && isOrgRole(r)) ?? c.roles.find((r) => r.name === roleName);
|
|
41
66
|
return match ? { ...c, role: match._id } : c;
|
|
42
67
|
});
|
|
43
68
|
}
|