gdc-common-utils-ts 2.2.2 → 2.3.1
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/README.md +4 -2
- package/dist/constants/data-collections.js +0 -1
- package/dist/constants/fhir-resource-types.d.ts +14 -0
- package/dist/constants/fhir-resource-types.js +6 -0
- package/dist/constants/healthcare.d.ts +348 -193
- package/dist/constants/healthcare.js +99 -37
- package/dist/constants/hl7-roles.d.ts +38 -4
- package/dist/constants/hl7-roles.js +92 -4
- package/dist/examples/shared.d.ts +1 -1
- package/dist/examples/shared.js +1 -1
- package/dist/i18n/role-codes.i18n.d.ts +9 -1
- package/dist/i18n/role-codes.i18n.js +12 -1
- package/dist/models/device-license.d.ts +20 -0
- package/dist/models/interoperable-claims/invoice-claims.d.ts +1 -1
- package/dist/models/interoperable-claims/invoice-claims.js +2 -2
- package/dist/models/interoperable-claims/observation-claims.d.ts +2 -2
- package/dist/models/interoperable-claims/observation-claims.js +2 -2
- package/dist/models/interoperable-claims/related-person-claims.d.ts +13 -0
- package/dist/models/interoperable-claims/related-person-claims.js +16 -0
- package/dist/utils/allergy-intolerance-entry-editor.js +2 -2
- package/dist/utils/bundle-document-builder.d.ts +35 -0
- package/dist/utils/bundle-document-builder.js +176 -0
- package/dist/utils/clinical-resource-view.d.ts +16 -0
- package/dist/utils/clinical-resource-view.js +267 -33
- package/dist/utils/communication-consent-access-editor.d.ts +8 -4
- package/dist/utils/communication-consent-access-editor.js +8 -4
- package/dist/utils/condition-entry-editor.js +2 -2
- package/dist/utils/consent-duplicate-rules.js +1 -1
- package/dist/utils/did.d.ts +2 -2
- package/dist/utils/did.js +3 -3
- package/dist/utils/license-list-search.d.ts +5 -0
- package/dist/utils/license-list-search.js +9 -0
- package/dist/utils/license.d.ts +45 -1
- package/dist/utils/license.js +86 -2
- package/dist/utils/related-person-list.d.ts +4 -0
- package/dist/utils/related-person-list.js +26 -6
- package/package.json +1 -1
|
@@ -4,11 +4,15 @@ import type { CommunicationAttachedBundleSessionOptions, ConsentEditorClassified
|
|
|
4
4
|
import type { ConsentDuplicateRuleConflict } from './consent-duplicate-rules';
|
|
5
5
|
import { CommunicationAttachedBundleSession } from './communication-attached-bundle-session';
|
|
6
6
|
/**
|
|
7
|
-
* High-level
|
|
7
|
+
* High-level access-policy editor for onboarding and app-facing code.
|
|
8
8
|
*
|
|
9
|
-
* This
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* This edits permit/deny rules governing who may access which data, for which
|
|
10
|
+
* purpose and scope, inside a Communication-carried bundle. It does not model
|
|
11
|
+
* informed consent for a clinical procedure, treatment or intervention.
|
|
12
|
+
*
|
|
13
|
+
* The name deliberately separates authorization consent from clinical
|
|
14
|
+
* intervention consent while retaining the FHIR-like `Consent` resource used
|
|
15
|
+
* by the existing access-rule contract.
|
|
12
16
|
*/
|
|
13
17
|
export declare class ConsentAccessEditor extends CommunicationAttachedBundleSession {
|
|
14
18
|
/** Returns duplicate atomic consent-rule conflicts across the current bundle. */
|
|
@@ -7,11 +7,15 @@ import { detectDuplicateConsentRuleConflicts } from './consent-duplicate-rules.j
|
|
|
7
7
|
import { CommunicationAttachedBundleSession } from './communication-attached-bundle-session.js';
|
|
8
8
|
import { asTrimmedString, buildClassifiedConsentTarget, buildConsentViewModel, buildSectionCatalogOptions, classifyConsentActors, classifyConsentPurposes, classifyConsentRoles, classifyConsentTargetsFromClaims, CSV_SEPARATOR, flattenClassifiedActors, flattenClassifiedRoles, flattenClassifiedTargets, normalizeCsvValues, splitCsv, } from './communication-attached-bundle-session-helpers.js';
|
|
9
9
|
/**
|
|
10
|
-
* High-level
|
|
10
|
+
* High-level access-policy editor for onboarding and app-facing code.
|
|
11
11
|
*
|
|
12
|
-
* This
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* This edits permit/deny rules governing who may access which data, for which
|
|
13
|
+
* purpose and scope, inside a Communication-carried bundle. It does not model
|
|
14
|
+
* informed consent for a clinical procedure, treatment or intervention.
|
|
15
|
+
*
|
|
16
|
+
* The name deliberately separates authorization consent from clinical
|
|
17
|
+
* intervention consent while retaining the FHIR-like `Consent` resource used
|
|
18
|
+
* by the existing access-rule contract.
|
|
15
19
|
*/
|
|
16
20
|
export class ConsentAccessEditor extends CommunicationAttachedBundleSession {
|
|
17
21
|
/** Returns duplicate atomic consent-rule conflicts across the current bundle. */
|
|
@@ -28,7 +28,7 @@ export class ConditionEntryEditor extends ClinicalResourceEntryEditor {
|
|
|
28
28
|
getOnsetDateTime() { return this.getScalarClaim(ConditionClaim.OnsetDateTime); }
|
|
29
29
|
setRecorder(reference) { return this.setScalarClaim(ConditionClaim.Recorder, reference); }
|
|
30
30
|
getRecorder() { return this.getScalarClaim(ConditionClaim.Recorder); }
|
|
31
|
-
setContainedDocumentIdentifierList(identifiers) { return this.setCsvClaimList(ConditionClaim.
|
|
32
|
-
getContainedDocumentIdentifierList() { return this.getCsvClaimList(ConditionClaim.
|
|
31
|
+
setContainedDocumentIdentifierList(identifiers) { return this.setCsvClaimList(ConditionClaim.ContainedReferenceList, identifiers); }
|
|
32
|
+
getContainedDocumentIdentifierList() { return this.getCsvClaimList(ConditionClaim.ContainedReferenceList); }
|
|
33
33
|
}
|
|
34
34
|
registerBundleEntryEditor(BundleEditableResourceTypes.condition, ConditionEntryEditor);
|
|
@@ -205,7 +205,7 @@ function normalizeConsentRoleForRuleKey(value) {
|
|
|
205
205
|
return `org.hl7.terminology.codesystem.v3-rolecode.${code}`;
|
|
206
206
|
}
|
|
207
207
|
if (system === 'v3-personalrelationshiproletype') {
|
|
208
|
-
return `org.hl7.v3
|
|
208
|
+
return `org.hl7.terminology.codesystem.v3-rolecode.${code}`;
|
|
209
209
|
}
|
|
210
210
|
return claim.toLowerCase();
|
|
211
211
|
}
|
package/dist/utils/did.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ export declare function extractTenantIdFromHostedDidWeb(did: string): string | u
|
|
|
71
71
|
* Builds the canonical hosted provider DID root used by hosted tenant services.
|
|
72
72
|
*
|
|
73
73
|
* Canonical form:
|
|
74
|
-
* `did:web:<host.domain>:<sector
|
|
74
|
+
* `did:web:<host.domain>:<sector>:organization:taxid:<provider-tax-id>`
|
|
75
75
|
*
|
|
76
76
|
* This is the provider-scoped DID root under which hosted individual and member
|
|
77
77
|
* identities are published. Downstream individual DIDs extend this root with
|
|
@@ -167,7 +167,7 @@ export declare function buildProfessionalDidWeb(input: {
|
|
|
167
167
|
*
|
|
168
168
|
* Canonical supported forms:
|
|
169
169
|
* - hosted:
|
|
170
|
-
* `did:web:<host.domain>:<sector
|
|
170
|
+
* `did:web:<host.domain>:<sector>:organization:taxid:<provider-tax-id>:individual:multibase:<individualId>`
|
|
171
171
|
* - external/provider-domain:
|
|
172
172
|
* `did:web:<sector.provider.domain>:individual:multibase:<individualId>`
|
|
173
173
|
*
|
package/dist/utils/did.js
CHANGED
|
@@ -137,7 +137,7 @@ export function extractTenantIdFromHostedDidWeb(did) {
|
|
|
137
137
|
* Builds the canonical hosted provider DID root used by hosted tenant services.
|
|
138
138
|
*
|
|
139
139
|
* Canonical form:
|
|
140
|
-
* `did:web:<host.domain>:<sector
|
|
140
|
+
* `did:web:<host.domain>:<sector>:organization:taxid:<provider-tax-id>`
|
|
141
141
|
*
|
|
142
142
|
* This is the provider-scoped DID root under which hosted individual and member
|
|
143
143
|
* identities are published. Downstream individual DIDs extend this root with
|
|
@@ -153,7 +153,7 @@ export function buildHostedProviderDidWeb(input) {
|
|
|
153
153
|
throw new Error('buildHostedProviderDidWeb requires sector.');
|
|
154
154
|
if (!providerTaxId)
|
|
155
155
|
throw new Error('buildHostedProviderDidWeb requires providerTaxId.');
|
|
156
|
-
return `did:web:${hostDomain}:${sector}
|
|
156
|
+
return `did:web:${hostDomain}:${sector}:organization:taxid:${providerTaxId}`;
|
|
157
157
|
}
|
|
158
158
|
/**
|
|
159
159
|
* Builds the canonical public provider DID root used by an external provider domain.
|
|
@@ -269,7 +269,7 @@ export function buildProfessionalDidWeb(input) {
|
|
|
269
269
|
*
|
|
270
270
|
* Canonical supported forms:
|
|
271
271
|
* - hosted:
|
|
272
|
-
* `did:web:<host.domain>:<sector
|
|
272
|
+
* `did:web:<host.domain>:<sector>:organization:taxid:<provider-tax-id>:individual:multibase:<individualId>`
|
|
273
273
|
* - external/provider-domain:
|
|
274
274
|
* `did:web:<sector.provider.domain>:individual:multibase:<individualId>`
|
|
275
275
|
*
|
|
@@ -8,9 +8,11 @@ import { buildLicenseSearchEntry, type LicenseClaims, type LicenseSearchInput, t
|
|
|
8
8
|
export type LicenseListSearchState = Readonly<{
|
|
9
9
|
serialNumbers?: readonly string[];
|
|
10
10
|
email?: string;
|
|
11
|
+
telephone?: string;
|
|
11
12
|
role?: string;
|
|
12
13
|
status?: LicenseStatus;
|
|
13
14
|
subjectId?: string;
|
|
15
|
+
ownerOrganizationId?: string;
|
|
14
16
|
userClass?: string;
|
|
15
17
|
type?: string;
|
|
16
18
|
active?: boolean;
|
|
@@ -28,6 +30,7 @@ export type LicenseListRecord = Readonly<{
|
|
|
28
30
|
id?: string;
|
|
29
31
|
status?: string;
|
|
30
32
|
subjectId?: string;
|
|
33
|
+
ownerOrganizationId?: string;
|
|
31
34
|
email?: string;
|
|
32
35
|
role?: string;
|
|
33
36
|
category?: string;
|
|
@@ -66,6 +69,8 @@ export declare class LicenseListSearchEditor {
|
|
|
66
69
|
setRole(value: string): this;
|
|
67
70
|
setStatus(value: LicenseStatus): this;
|
|
68
71
|
setSubjectId(value: string): this;
|
|
72
|
+
/** Limits results to the organization that owns the seat pool. */
|
|
73
|
+
setOwnerOrganizationId(value: string): this;
|
|
69
74
|
setUserClass(value: string): this;
|
|
70
75
|
setAppType(value: string): this;
|
|
71
76
|
setActive(value: boolean): this;
|
|
@@ -15,6 +15,7 @@ function cloneDraft(state) {
|
|
|
15
15
|
role: normalizeText(state?.role),
|
|
16
16
|
status: state?.status,
|
|
17
17
|
subjectId: normalizeText(state?.subjectId),
|
|
18
|
+
ownerOrganizationId: normalizeText(state?.ownerOrganizationId),
|
|
18
19
|
userClass: normalizeText(state?.userClass),
|
|
19
20
|
type: normalizeText(state?.type),
|
|
20
21
|
active: typeof state?.active === 'boolean' ? state.active : undefined,
|
|
@@ -71,6 +72,11 @@ export class LicenseListSearchEditor {
|
|
|
71
72
|
this.draft = cloneDraft({ ...this.draft, subjectId: value });
|
|
72
73
|
return this;
|
|
73
74
|
}
|
|
75
|
+
/** Limits results to the organization that owns the seat pool. */
|
|
76
|
+
setOwnerOrganizationId(value) {
|
|
77
|
+
this.draft = cloneDraft({ ...this.draft, ownerOrganizationId: value });
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
74
80
|
setUserClass(value) {
|
|
75
81
|
this.draft = cloneDraft({ ...this.draft, userClass: value });
|
|
76
82
|
return this;
|
|
@@ -117,6 +123,7 @@ export class LicenseListSearchEditor {
|
|
|
117
123
|
...(this.draft.role ? { role: this.draft.role } : {}),
|
|
118
124
|
...(resolveStatus(this.draft) ? { status: resolveStatus(this.draft) } : {}),
|
|
119
125
|
...(this.draft.subjectId ? { subjectId: this.draft.subjectId } : {}),
|
|
126
|
+
...(this.draft.ownerOrganizationId ? { ownerOrganizationId: this.draft.ownerOrganizationId } : {}),
|
|
120
127
|
additionalClaims: cloneClaims(this.draft.additionalClaims),
|
|
121
128
|
};
|
|
122
129
|
}
|
|
@@ -151,7 +158,9 @@ export function readLicenseListRecords(body) {
|
|
|
151
158
|
|| entry.id),
|
|
152
159
|
status: normalizeText(meta.status),
|
|
153
160
|
subjectId: normalizeText(meta.subjectId),
|
|
161
|
+
ownerOrganizationId: normalizeText(meta.ownerOrganizationId || claims['License.ownerOrganizationId']),
|
|
154
162
|
email: normalizeText(claims[ClaimsPersonSchemaorg.email]),
|
|
163
|
+
telephone: normalizeText(claims[ClaimsPersonSchemaorg.telephone]),
|
|
155
164
|
role: normalizeText(claims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]),
|
|
156
165
|
category: normalizeText(claims[ClaimsIndividualProductSchemaorg.category]),
|
|
157
166
|
appType: normalizeText(claims[ClaimsIndividualProductSchemaorg.additionalType]),
|
package/dist/utils/license.d.ts
CHANGED
|
@@ -54,11 +54,44 @@ export declare const LicenseStatuses: Readonly<{
|
|
|
54
54
|
readonly Inactive: "inactive";
|
|
55
55
|
}>;
|
|
56
56
|
export type LicenseStatus = typeof LicenseStatuses[keyof typeof LicenseStatuses];
|
|
57
|
+
/**
|
|
58
|
+
* Role families that decide which organization owns the actor's license.
|
|
59
|
+
*
|
|
60
|
+
* This classification is deliberately independent from Consent. A person may
|
|
61
|
+
* receive permissions in either case, but only a FHIR v3 relationship role is
|
|
62
|
+
* a member of the individual's organization and consumes its seat pool.
|
|
63
|
+
*/
|
|
64
|
+
export declare const LicenseRoleKinds: Readonly<{
|
|
65
|
+
readonly IndividualMember: "individual-member";
|
|
66
|
+
readonly Professional: "professional";
|
|
67
|
+
readonly Unsupported: "unsupported";
|
|
68
|
+
}>;
|
|
69
|
+
export type LicenseRoleKind = typeof LicenseRoleKinds[keyof typeof LicenseRoleKinds];
|
|
70
|
+
/**
|
|
71
|
+
* Classifies one canonical or compatibility role token for license ownership.
|
|
72
|
+
*
|
|
73
|
+
* Rules:
|
|
74
|
+
* - FHIR/HL7 v3 `RoleCode` actors are non-employee members of the individual
|
|
75
|
+
* organization and consume one of its licenses.
|
|
76
|
+
* - ISCO/ISCO-08 actors are professionals. Their employer/organization owns
|
|
77
|
+
* their professional license, so a patient's individual pool is untouched.
|
|
78
|
+
* - Unknown role systems are not silently charged to either pool.
|
|
79
|
+
*/
|
|
80
|
+
export declare function classifyLicenseRole(role: string): LicenseRoleKind;
|
|
57
81
|
export type LicenseIssueInput = Readonly<{
|
|
58
|
-
email
|
|
82
|
+
email?: string;
|
|
83
|
+
telephone?: string;
|
|
59
84
|
role: string;
|
|
60
85
|
userClass?: DeviceUserClass;
|
|
61
86
|
type?: DeviceAppType;
|
|
87
|
+
/** Individual/legal organization that owns the seat pool. */
|
|
88
|
+
ownerOrganizationId?: string;
|
|
89
|
+
/** Card/individual DID granted by the invitation after acceptance. */
|
|
90
|
+
subjectDid?: string;
|
|
91
|
+
/** Existing RelatedPerson/contact selected before the invitation. */
|
|
92
|
+
relatedPersonId?: string;
|
|
93
|
+
/** Stable invitation workflow identifier; not the license id or code. */
|
|
94
|
+
invitationId?: string;
|
|
62
95
|
additionalClaims?: LicenseClaims;
|
|
63
96
|
}>;
|
|
64
97
|
export type LicensePurchaseInput = Readonly<{
|
|
@@ -66,6 +99,10 @@ export type LicensePurchaseInput = Readonly<{
|
|
|
66
99
|
userClass?: DeviceUserClass;
|
|
67
100
|
type?: DeviceAppType;
|
|
68
101
|
serialNumbers?: readonly string[];
|
|
102
|
+
price?: number;
|
|
103
|
+
priceCurrency?: string;
|
|
104
|
+
/** Organization whose pool receives the newly materialized seats. */
|
|
105
|
+
ownerOrganizationId?: string;
|
|
69
106
|
additionalClaims?: LicenseClaims;
|
|
70
107
|
}>;
|
|
71
108
|
export type LicenseSearchInput = Readonly<{
|
|
@@ -76,6 +113,7 @@ export type LicenseSearchInput = Readonly<{
|
|
|
76
113
|
role?: string;
|
|
77
114
|
status?: LicenseStatus;
|
|
78
115
|
subjectId?: string;
|
|
116
|
+
ownerOrganizationId?: string;
|
|
79
117
|
additionalClaims?: LicenseClaims;
|
|
80
118
|
}>;
|
|
81
119
|
/**
|
|
@@ -106,6 +144,10 @@ export declare function buildLicenseIssueEntry(input: LicenseIssueInput): {
|
|
|
106
144
|
};
|
|
107
145
|
meta: {
|
|
108
146
|
claims: LicenseClaims;
|
|
147
|
+
ownerOrganizationId?: string;
|
|
148
|
+
subjectDid?: string;
|
|
149
|
+
relatedPersonId?: string;
|
|
150
|
+
invitationId?: string;
|
|
109
151
|
};
|
|
110
152
|
};
|
|
111
153
|
/**
|
|
@@ -130,6 +172,7 @@ export declare function buildLicensePurchaseEntry(input: LicensePurchaseInput):
|
|
|
130
172
|
};
|
|
131
173
|
meta: {
|
|
132
174
|
claims: LicenseClaims;
|
|
175
|
+
ownerOrganizationId?: string;
|
|
133
176
|
};
|
|
134
177
|
};
|
|
135
178
|
/**
|
|
@@ -151,5 +194,6 @@ export declare function buildLicenseSearchEntry(input: LicenseSearchInput): {
|
|
|
151
194
|
claims: LicenseClaims;
|
|
152
195
|
status?: LicenseStatus;
|
|
153
196
|
subjectId?: string;
|
|
197
|
+
ownerOrganizationId?: string;
|
|
154
198
|
};
|
|
155
199
|
};
|
package/dist/utils/license.js
CHANGED
|
@@ -52,6 +52,47 @@ export const LicenseStatuses = Object.freeze({
|
|
|
52
52
|
Active: 'active',
|
|
53
53
|
Inactive: 'inactive',
|
|
54
54
|
});
|
|
55
|
+
/**
|
|
56
|
+
* Role families that decide which organization owns the actor's license.
|
|
57
|
+
*
|
|
58
|
+
* This classification is deliberately independent from Consent. A person may
|
|
59
|
+
* receive permissions in either case, but only a FHIR v3 relationship role is
|
|
60
|
+
* a member of the individual's organization and consumes its seat pool.
|
|
61
|
+
*/
|
|
62
|
+
export const LicenseRoleKinds = Object.freeze({
|
|
63
|
+
IndividualMember: 'individual-member',
|
|
64
|
+
Professional: 'professional',
|
|
65
|
+
Unsupported: 'unsupported',
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Classifies one canonical or compatibility role token for license ownership.
|
|
69
|
+
*
|
|
70
|
+
* Rules:
|
|
71
|
+
* - FHIR/HL7 v3 `RoleCode` actors are non-employee members of the individual
|
|
72
|
+
* organization and consume one of its licenses.
|
|
73
|
+
* - ISCO/ISCO-08 actors are professionals. Their employer/organization owns
|
|
74
|
+
* their professional license, so a patient's individual pool is untouched.
|
|
75
|
+
* - Unknown role systems are not silently charged to either pool.
|
|
76
|
+
*/
|
|
77
|
+
export function classifyLicenseRole(role) {
|
|
78
|
+
const normalized = String(role || '').trim().toLowerCase();
|
|
79
|
+
if (!normalized)
|
|
80
|
+
return LicenseRoleKinds.Unsupported;
|
|
81
|
+
const system = normalized.includes('|') ? normalized.split('|', 1)[0] : normalized;
|
|
82
|
+
if (system === 'isco'
|
|
83
|
+
|| system === 'isco-08'
|
|
84
|
+
|| system === 'org.ilo.isco'
|
|
85
|
+
|| system === 'org.ilo.isco-08'
|
|
86
|
+
|| system.endsWith('/isco-08')) {
|
|
87
|
+
return LicenseRoleKinds.Professional;
|
|
88
|
+
}
|
|
89
|
+
if (system === 'v3-rolecode'
|
|
90
|
+
|| system === 'org.hl7.v3.rolecode'
|
|
91
|
+
|| system.endsWith('/v3-rolecode')) {
|
|
92
|
+
return LicenseRoleKinds.IndividualMember;
|
|
93
|
+
}
|
|
94
|
+
return LicenseRoleKinds.Unsupported;
|
|
95
|
+
}
|
|
55
96
|
function cloneClaims(claims) {
|
|
56
97
|
return { ...(claims || {}) };
|
|
57
98
|
}
|
|
@@ -82,12 +123,31 @@ export function serializeLicenseSerialNumbers(serialNumbers) {
|
|
|
82
123
|
* - seat/app semantics come from `IndividualProduct.*`
|
|
83
124
|
*/
|
|
84
125
|
export function buildLicenseIssueClaims(input) {
|
|
126
|
+
const email = String(input.email || '').trim();
|
|
127
|
+
const telephone = String(input.telephone || '').trim();
|
|
128
|
+
if (!email && !telephone) {
|
|
129
|
+
throw new Error('License issue requires an email or telephone recipient.');
|
|
130
|
+
}
|
|
131
|
+
if (telephone && !/^\+[1-9]\d{6,14}$/.test(telephone)) {
|
|
132
|
+
throw new Error('License issue telephone must use an international ITU format beginning with +.');
|
|
133
|
+
}
|
|
134
|
+
const userClass = input.userClass || DeviceUserClasses.Employee;
|
|
135
|
+
if (userClass === DeviceUserClasses.Individual) {
|
|
136
|
+
const roleKind = classifyLicenseRole(input.role);
|
|
137
|
+
if (roleKind === LicenseRoleKinds.Professional) {
|
|
138
|
+
throw new Error('ISCO professional roles do not consume individual-member licenses.');
|
|
139
|
+
}
|
|
140
|
+
if (roleKind !== LicenseRoleKinds.IndividualMember) {
|
|
141
|
+
throw new Error('Individual-member licenses require a FHIR v3-RoleCode role.');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
85
144
|
const claims = {
|
|
86
145
|
'@context': LicenseClaimContext.SchemaOrg,
|
|
87
146
|
...cloneClaims(input.additionalClaims),
|
|
88
|
-
[ClaimsPersonSchemaorg.email]:
|
|
147
|
+
...(email ? { [ClaimsPersonSchemaorg.email]: email } : {}),
|
|
148
|
+
...(telephone ? { [ClaimsPersonSchemaorg.telephone]: telephone } : {}),
|
|
89
149
|
[ClaimsPersonSchemaorg.hasOccupationalRoleValue]: input.role.trim(),
|
|
90
|
-
[ClaimsIndividualProductSchemaorg.category]: mapLicenseCategoryFromUserClass(
|
|
150
|
+
[ClaimsIndividualProductSchemaorg.category]: mapLicenseCategoryFromUserClass(userClass),
|
|
91
151
|
[ClaimsIndividualProductSchemaorg.additionalType]: input.type || DeviceAppTypes.Mobile,
|
|
92
152
|
};
|
|
93
153
|
return claims;
|
|
@@ -104,6 +164,18 @@ export function buildLicenseIssueEntry(input) {
|
|
|
104
164
|
...buildLicenseIssueClaims(input),
|
|
105
165
|
'@type': LicenseEntryOperations.Issue,
|
|
106
166
|
},
|
|
167
|
+
...(String(input.ownerOrganizationId || '').trim()
|
|
168
|
+
? { ownerOrganizationId: String(input.ownerOrganizationId).trim() }
|
|
169
|
+
: {}),
|
|
170
|
+
...(String(input.subjectDid || '').trim()
|
|
171
|
+
? { subjectDid: String(input.subjectDid).trim() }
|
|
172
|
+
: {}),
|
|
173
|
+
...(String(input.relatedPersonId || '').trim()
|
|
174
|
+
? { relatedPersonId: String(input.relatedPersonId).trim() }
|
|
175
|
+
: {}),
|
|
176
|
+
...(String(input.invitationId || '').trim()
|
|
177
|
+
? { invitationId: String(input.invitationId).trim() }
|
|
178
|
+
: {}),
|
|
107
179
|
},
|
|
108
180
|
};
|
|
109
181
|
}
|
|
@@ -129,6 +201,12 @@ export function buildLicensePurchaseClaims(input) {
|
|
|
129
201
|
if (serializedSerialNumbers) {
|
|
130
202
|
claims[ClaimsOfferSchemaorg.serialNumber] = serializedSerialNumbers;
|
|
131
203
|
}
|
|
204
|
+
if (typeof input.price === 'number' && Number.isFinite(input.price) && input.price >= 0) {
|
|
205
|
+
claims[ClaimsOfferSchemaorg.price] = input.price;
|
|
206
|
+
}
|
|
207
|
+
if (typeof input.priceCurrency === 'string' && input.priceCurrency.trim()) {
|
|
208
|
+
claims[ClaimsOfferSchemaorg.priceCurrency] = input.priceCurrency.trim().toUpperCase();
|
|
209
|
+
}
|
|
132
210
|
return claims;
|
|
133
211
|
}
|
|
134
212
|
/**
|
|
@@ -144,6 +222,9 @@ export function buildLicensePurchaseEntry(input) {
|
|
|
144
222
|
...buildLicensePurchaseClaims(input),
|
|
145
223
|
'@type': LicenseEntryOperations.Purchase,
|
|
146
224
|
},
|
|
225
|
+
...(String(input.ownerOrganizationId || '').trim()
|
|
226
|
+
? { ownerOrganizationId: String(input.ownerOrganizationId).trim() }
|
|
227
|
+
: {}),
|
|
147
228
|
},
|
|
148
229
|
};
|
|
149
230
|
}
|
|
@@ -189,6 +270,9 @@ export function buildLicenseSearchEntry(input) {
|
|
|
189
270
|
...(typeof input.subjectId === 'string' && input.subjectId.trim()
|
|
190
271
|
? { subjectId: input.subjectId.trim() }
|
|
191
272
|
: {}),
|
|
273
|
+
...(typeof input.ownerOrganizationId === 'string' && input.ownerOrganizationId.trim()
|
|
274
|
+
? { ownerOrganizationId: input.ownerOrganizationId.trim() }
|
|
275
|
+
: {}),
|
|
192
276
|
},
|
|
193
277
|
};
|
|
194
278
|
}
|
|
@@ -2,11 +2,15 @@ export type RelatedPersonListRecord = Readonly<{
|
|
|
2
2
|
identifier?: string;
|
|
3
3
|
patient?: string;
|
|
4
4
|
relationship?: string;
|
|
5
|
+
/** Functional `RelatedPerson.role` extension values, separate from kinship. */
|
|
6
|
+
roles: readonly string[];
|
|
5
7
|
name?: string;
|
|
6
8
|
telecom?: string;
|
|
7
9
|
active?: string;
|
|
8
10
|
status?: string;
|
|
9
11
|
resourceId?: string;
|
|
12
|
+
relatedEntityType?: string;
|
|
13
|
+
actorIdentifiers: readonly string[];
|
|
10
14
|
claims: Record<string, unknown>;
|
|
11
15
|
}>;
|
|
12
16
|
export type RelatedPersonListSelection = Readonly<{
|
|
@@ -12,6 +12,17 @@ function extractClaims(entry) {
|
|
|
12
12
|
const resourceClaims = resourceMeta.claims && typeof resourceMeta.claims === 'object' ? resourceMeta.claims : undefined;
|
|
13
13
|
return { ...(resourceClaims || {}), ...(metaClaims || {}) };
|
|
14
14
|
}
|
|
15
|
+
function readClaim(claims, claimKey) {
|
|
16
|
+
if (claims[claimKey] !== undefined)
|
|
17
|
+
return claims[claimKey];
|
|
18
|
+
const context = normalizeText(claims['@context']);
|
|
19
|
+
if (context && claims[`${context.replace(/\.$/, '')}.${claimKey}`] !== undefined) {
|
|
20
|
+
return claims[`${context.replace(/\.$/, '')}.${claimKey}`];
|
|
21
|
+
}
|
|
22
|
+
const suffix = `.${claimKey}`;
|
|
23
|
+
const matchedKey = Object.keys(claims).find((key) => key.endsWith(suffix));
|
|
24
|
+
return matchedKey ? claims[matchedKey] : undefined;
|
|
25
|
+
}
|
|
15
26
|
/**
|
|
16
27
|
* Reads subject-side relationship records from one current GW-style result
|
|
17
28
|
* body into one neutral list shape for frontend screens.
|
|
@@ -29,14 +40,23 @@ export function readRelatedPersonListRecords(body) {
|
|
|
29
40
|
const meta = entry.meta && typeof entry.meta === 'object' ? entry.meta : {};
|
|
30
41
|
const resource = entry.resource && typeof entry.resource === 'object' ? entry.resource : {};
|
|
31
42
|
return {
|
|
32
|
-
identifier: normalizeText(claims
|
|
33
|
-
patient: normalizeText(claims
|
|
34
|
-
relationship: normalizeText(claims
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
identifier: normalizeText(readClaim(claims, RelatedPersonClaim.IdentifierValue) ?? readClaim(claims, RelatedPersonClaim.Identifier)),
|
|
44
|
+
patient: normalizeText(readClaim(claims, RelatedPersonClaim.Patient)),
|
|
45
|
+
relationship: normalizeText(readClaim(claims, RelatedPersonClaim.Relationship)),
|
|
46
|
+
roles: String(readClaim(claims, RelatedPersonClaim.Role) ?? '')
|
|
47
|
+
.split(',')
|
|
48
|
+
.map((value) => value.trim().toUpperCase())
|
|
49
|
+
.filter(Boolean),
|
|
50
|
+
name: normalizeText(readClaim(claims, RelatedPersonClaim.Name)),
|
|
51
|
+
telecom: normalizeText(readClaim(claims, RelatedPersonClaim.Telecom)),
|
|
52
|
+
active: normalizeText(readClaim(claims, RelatedPersonClaim.Active)),
|
|
38
53
|
status: normalizeText(meta.status),
|
|
39
54
|
resourceId: normalizeText(resource.id || entry.id),
|
|
55
|
+
relatedEntityType: normalizeText(readClaim(claims, RelatedPersonClaim.RelatedEntityType)),
|
|
56
|
+
actorIdentifiers: String(readClaim(claims, RelatedPersonClaim.ActorIdentifier) ?? '')
|
|
57
|
+
.split(',')
|
|
58
|
+
.map((value) => value.trim())
|
|
59
|
+
.filter(Boolean),
|
|
40
60
|
claims,
|
|
41
61
|
};
|
|
42
62
|
});
|