@openhi/constructs 0.0.100 → 0.0.101
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.
|
@@ -4886,55 +4886,6 @@ function getDefaultPostgresQueryRunner() {
|
|
|
4886
4886
|
return cached;
|
|
4887
4887
|
}
|
|
4888
4888
|
|
|
4889
|
-
// src/data/operations/data/appointment/appointment-search-by-date-operation.ts
|
|
4890
|
-
var DEFAULT_LIMIT = 100;
|
|
4891
|
-
function buildSearchAppointmentsByDateSql(opts) {
|
|
4892
|
-
const datePredicates = buildAppointmentDateSearchPredicateSql(
|
|
4893
|
-
opts.dateConstraints
|
|
4894
|
-
);
|
|
4895
|
-
const lines = [
|
|
4896
|
-
"SELECT resource_id AS id, resource",
|
|
4897
|
-
"FROM resources",
|
|
4898
|
-
"WHERE tenant_id = :tenantId",
|
|
4899
|
-
" AND workspace_id = :workspaceId",
|
|
4900
|
-
" AND resource_type = 'Appointment'",
|
|
4901
|
-
" AND deleted_at IS NULL"
|
|
4902
|
-
];
|
|
4903
|
-
for (const fragment of datePredicates) {
|
|
4904
|
-
lines.push(` AND ${fragment}`);
|
|
4905
|
-
}
|
|
4906
|
-
lines.push("ORDER BY last_updated DESC");
|
|
4907
|
-
lines.push("LIMIT :limit;");
|
|
4908
|
-
return lines.join("\n");
|
|
4909
|
-
}
|
|
4910
|
-
async function searchAppointmentsByDateOperation(params) {
|
|
4911
|
-
const { context, dateConstraints } = params;
|
|
4912
|
-
if (dateConstraints.length === 0) {
|
|
4913
|
-
throw new Error(
|
|
4914
|
-
"searchAppointmentsByDateOperation requires at least one dateConstraint"
|
|
4915
|
-
);
|
|
4916
|
-
}
|
|
4917
|
-
const { tenantId, workspaceId } = context;
|
|
4918
|
-
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
4919
|
-
const limit = params.limit ?? DEFAULT_LIMIT;
|
|
4920
|
-
const sql = buildSearchAppointmentsByDateSql({ dateConstraints });
|
|
4921
|
-
const queryParams = [
|
|
4922
|
-
{ name: "tenantId", value: tenantId },
|
|
4923
|
-
{ name: "workspaceId", value: workspaceId },
|
|
4924
|
-
{ name: "limit", value: limit },
|
|
4925
|
-
...buildAppointmentDateSearchPredicateParams(dateConstraints)
|
|
4926
|
-
];
|
|
4927
|
-
const rows = await runner.query(sql, queryParams);
|
|
4928
|
-
const entries = rows.map((row) => ({
|
|
4929
|
-
id: row.id,
|
|
4930
|
-
resource: {
|
|
4931
|
-
...row.resource,
|
|
4932
|
-
id: row.id
|
|
4933
|
-
}
|
|
4934
|
-
}));
|
|
4935
|
-
return { entries, total: entries.length };
|
|
4936
|
-
}
|
|
4937
|
-
|
|
4938
4889
|
// src/data/operations/data/encounter/encounter-period-search-predicate.ts
|
|
4939
4890
|
var PERIOD_SEARCH_PREFIXES = [
|
|
4940
4891
|
"gt",
|
|
@@ -4987,7 +4938,7 @@ function buildPeriodSearchPredicateParams(constraints) {
|
|
|
4987
4938
|
}
|
|
4988
4939
|
|
|
4989
4940
|
// src/data/operations/data/encounter/encounter-search-by-patient-operation.ts
|
|
4990
|
-
var
|
|
4941
|
+
var DEFAULT_LIMIT = 100;
|
|
4991
4942
|
function buildOpenHiResourceUrn(opts) {
|
|
4992
4943
|
return `urn:ohi:${opts.tenantId}:${opts.workspaceId}:${opts.resourceType}:${opts.resourceId}`;
|
|
4993
4944
|
}
|
|
@@ -5017,7 +4968,7 @@ async function searchEncountersByPatientOperation(params) {
|
|
|
5017
4968
|
const periodConstraints = params.periodConstraints ?? [];
|
|
5018
4969
|
const { tenantId, workspaceId } = context;
|
|
5019
4970
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
5020
|
-
const limit = params.limit ??
|
|
4971
|
+
const limit = params.limit ?? DEFAULT_LIMIT;
|
|
5021
4972
|
const containmentRelative = JSON.stringify({
|
|
5022
4973
|
subject: { reference: `Patient/${patientId}` }
|
|
5023
4974
|
});
|
|
@@ -5051,8 +5002,155 @@ async function searchEncountersByPatientOperation(params) {
|
|
|
5051
5002
|
return { entries, total: entries.length };
|
|
5052
5003
|
}
|
|
5053
5004
|
|
|
5054
|
-
// src/data/operations/data/
|
|
5005
|
+
// src/data/operations/data/reference-containment-predicate.ts
|
|
5006
|
+
var REFERENCE_CONTAINMENT_SQL_FRAGMENT = "(resource @> :containmentRelative::jsonb OR resource @> :containmentUrn::jsonb)";
|
|
5007
|
+
function parseTypedReference(s) {
|
|
5008
|
+
const match = /^([A-Za-z][A-Za-z0-9_]*)\/([^\s/]+)$/.exec(s);
|
|
5009
|
+
if (!match) {
|
|
5010
|
+
return void 0;
|
|
5011
|
+
}
|
|
5012
|
+
return { resourceType: match[1], resourceId: match[2] };
|
|
5013
|
+
}
|
|
5014
|
+
function wrapReferenceInShape(shape, reference) {
|
|
5015
|
+
switch (shape.kind) {
|
|
5016
|
+
case "scalar":
|
|
5017
|
+
return { [shape.field]: { reference } };
|
|
5018
|
+
case "array-of-references":
|
|
5019
|
+
return { [shape.field]: [{ reference }] };
|
|
5020
|
+
case "array-of-objects":
|
|
5021
|
+
return { [shape.field]: [{ [shape.subfield]: { reference } }] };
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
function buildReferenceContainmentPayload(params) {
|
|
5025
|
+
const parsed = parseTypedReference(params.reference);
|
|
5026
|
+
if (!parsed) {
|
|
5027
|
+
throw new Error(
|
|
5028
|
+
`Reference "${params.reference}" is not a valid typed reference (<ResourceType>/<id>).`
|
|
5029
|
+
);
|
|
5030
|
+
}
|
|
5031
|
+
const urn = buildOpenHiResourceUrn({
|
|
5032
|
+
tenantId: params.tenantId,
|
|
5033
|
+
workspaceId: params.workspaceId,
|
|
5034
|
+
resourceType: parsed.resourceType,
|
|
5035
|
+
resourceId: parsed.resourceId
|
|
5036
|
+
});
|
|
5037
|
+
return {
|
|
5038
|
+
containmentRelative: JSON.stringify(
|
|
5039
|
+
wrapReferenceInShape(params.shape, params.reference)
|
|
5040
|
+
),
|
|
5041
|
+
containmentUrn: JSON.stringify(wrapReferenceInShape(params.shape, urn))
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
|
|
5045
|
+
// src/data/operations/data/appointment/appointment-search-by-actor-operation.ts
|
|
5046
|
+
var DEFAULT_LIMIT2 = 100;
|
|
5047
|
+
function buildSearchAppointmentsByActorSql(opts) {
|
|
5048
|
+
const datePredicates = buildAppointmentDateSearchPredicateSql(
|
|
5049
|
+
opts?.dateConstraints ?? []
|
|
5050
|
+
);
|
|
5051
|
+
const lines = [
|
|
5052
|
+
"SELECT resource_id AS id, resource",
|
|
5053
|
+
"FROM resources",
|
|
5054
|
+
"WHERE tenant_id = :tenantId",
|
|
5055
|
+
" AND workspace_id = :workspaceId",
|
|
5056
|
+
" AND resource_type = 'Appointment'",
|
|
5057
|
+
" AND deleted_at IS NULL",
|
|
5058
|
+
` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`
|
|
5059
|
+
];
|
|
5060
|
+
for (const fragment of datePredicates) {
|
|
5061
|
+
lines.push(` AND ${fragment}`);
|
|
5062
|
+
}
|
|
5063
|
+
lines.push("ORDER BY last_updated DESC");
|
|
5064
|
+
lines.push("LIMIT :limit;");
|
|
5065
|
+
return lines.join("\n");
|
|
5066
|
+
}
|
|
5067
|
+
async function searchAppointmentsByActorOperation(params) {
|
|
5068
|
+
const { context, actorReference } = params;
|
|
5069
|
+
const dateConstraints = params.dateConstraints ?? [];
|
|
5070
|
+
const { tenantId, workspaceId } = context;
|
|
5071
|
+
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
5072
|
+
const limit = params.limit ?? DEFAULT_LIMIT2;
|
|
5073
|
+
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
5074
|
+
shape: {
|
|
5075
|
+
kind: "array-of-objects",
|
|
5076
|
+
field: "participant",
|
|
5077
|
+
subfield: "actor"
|
|
5078
|
+
},
|
|
5079
|
+
reference: actorReference,
|
|
5080
|
+
tenantId,
|
|
5081
|
+
workspaceId
|
|
5082
|
+
});
|
|
5083
|
+
const sql = buildSearchAppointmentsByActorSql({ dateConstraints });
|
|
5084
|
+
const queryParams = [
|
|
5085
|
+
{ name: "tenantId", value: tenantId },
|
|
5086
|
+
{ name: "workspaceId", value: workspaceId },
|
|
5087
|
+
{ name: "containmentRelative", value: containmentRelative },
|
|
5088
|
+
{ name: "containmentUrn", value: containmentUrn },
|
|
5089
|
+
{ name: "limit", value: limit },
|
|
5090
|
+
...buildAppointmentDateSearchPredicateParams(dateConstraints)
|
|
5091
|
+
];
|
|
5092
|
+
const rows = await runner.query(sql, queryParams);
|
|
5093
|
+
const entries = rows.map((row) => ({
|
|
5094
|
+
id: row.id,
|
|
5095
|
+
resource: {
|
|
5096
|
+
...row.resource,
|
|
5097
|
+
id: row.id
|
|
5098
|
+
}
|
|
5099
|
+
}));
|
|
5100
|
+
return { entries, total: entries.length };
|
|
5101
|
+
}
|
|
5102
|
+
|
|
5103
|
+
// src/data/operations/data/appointment/appointment-search-by-date-operation.ts
|
|
5055
5104
|
var DEFAULT_LIMIT3 = 100;
|
|
5105
|
+
function buildSearchAppointmentsByDateSql(opts) {
|
|
5106
|
+
const datePredicates = buildAppointmentDateSearchPredicateSql(
|
|
5107
|
+
opts.dateConstraints
|
|
5108
|
+
);
|
|
5109
|
+
const lines = [
|
|
5110
|
+
"SELECT resource_id AS id, resource",
|
|
5111
|
+
"FROM resources",
|
|
5112
|
+
"WHERE tenant_id = :tenantId",
|
|
5113
|
+
" AND workspace_id = :workspaceId",
|
|
5114
|
+
" AND resource_type = 'Appointment'",
|
|
5115
|
+
" AND deleted_at IS NULL"
|
|
5116
|
+
];
|
|
5117
|
+
for (const fragment of datePredicates) {
|
|
5118
|
+
lines.push(` AND ${fragment}`);
|
|
5119
|
+
}
|
|
5120
|
+
lines.push("ORDER BY last_updated DESC");
|
|
5121
|
+
lines.push("LIMIT :limit;");
|
|
5122
|
+
return lines.join("\n");
|
|
5123
|
+
}
|
|
5124
|
+
async function searchAppointmentsByDateOperation(params) {
|
|
5125
|
+
const { context, dateConstraints } = params;
|
|
5126
|
+
if (dateConstraints.length === 0) {
|
|
5127
|
+
throw new Error(
|
|
5128
|
+
"searchAppointmentsByDateOperation requires at least one dateConstraint"
|
|
5129
|
+
);
|
|
5130
|
+
}
|
|
5131
|
+
const { tenantId, workspaceId } = context;
|
|
5132
|
+
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
5133
|
+
const limit = params.limit ?? DEFAULT_LIMIT3;
|
|
5134
|
+
const sql = buildSearchAppointmentsByDateSql({ dateConstraints });
|
|
5135
|
+
const queryParams = [
|
|
5136
|
+
{ name: "tenantId", value: tenantId },
|
|
5137
|
+
{ name: "workspaceId", value: workspaceId },
|
|
5138
|
+
{ name: "limit", value: limit },
|
|
5139
|
+
...buildAppointmentDateSearchPredicateParams(dateConstraints)
|
|
5140
|
+
];
|
|
5141
|
+
const rows = await runner.query(sql, queryParams);
|
|
5142
|
+
const entries = rows.map((row) => ({
|
|
5143
|
+
id: row.id,
|
|
5144
|
+
resource: {
|
|
5145
|
+
...row.resource,
|
|
5146
|
+
id: row.id
|
|
5147
|
+
}
|
|
5148
|
+
}));
|
|
5149
|
+
return { entries, total: entries.length };
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
// src/data/operations/data/appointment/appointment-search-by-patient-operation.ts
|
|
5153
|
+
var DEFAULT_LIMIT4 = 100;
|
|
5056
5154
|
function buildSearchAppointmentsByPatientSql(opts) {
|
|
5057
5155
|
const datePredicates = buildAppointmentDateSearchPredicateSql(
|
|
5058
5156
|
opts?.dateConstraints ?? []
|
|
@@ -5079,7 +5177,7 @@ async function searchAppointmentsByPatientOperation(params) {
|
|
|
5079
5177
|
const dateConstraints = params.dateConstraints ?? [];
|
|
5080
5178
|
const { tenantId, workspaceId } = context;
|
|
5081
5179
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
5082
|
-
const limit = params.limit ??
|
|
5180
|
+
const limit = params.limit ?? DEFAULT_LIMIT4;
|
|
5083
5181
|
const containmentRelative = JSON.stringify({
|
|
5084
5182
|
participant: [{ actor: { reference: `Patient/${patientId}` } }]
|
|
5085
5183
|
});
|
|
@@ -5166,11 +5264,45 @@ function sendInvalidSearch400(res, diagnostics) {
|
|
|
5166
5264
|
}
|
|
5167
5265
|
async function listAppointmentsRoute(req, res) {
|
|
5168
5266
|
const patientId = singleStringQueryParam(req, "patient");
|
|
5267
|
+
const actorRef = singleStringQueryParam(req, "actor");
|
|
5169
5268
|
const parsed = parseAppointmentDateConstraints(req);
|
|
5170
5269
|
if (isError(parsed)) {
|
|
5171
5270
|
return sendInvalidSearch400(res, parsed.error);
|
|
5172
5271
|
}
|
|
5173
5272
|
const dateConstraints = parsed;
|
|
5273
|
+
if (patientId !== void 0 && actorRef !== void 0) {
|
|
5274
|
+
return sendInvalidSearch400(
|
|
5275
|
+
res,
|
|
5276
|
+
"?patient= and ?actor= cannot be combined on the same request."
|
|
5277
|
+
);
|
|
5278
|
+
}
|
|
5279
|
+
if (actorRef !== void 0) {
|
|
5280
|
+
if (parseTypedReference(actorRef) === void 0) {
|
|
5281
|
+
return sendInvalidSearch400(
|
|
5282
|
+
res,
|
|
5283
|
+
`?actor must be a typed reference like "Practitioner/<id>"; got "${actorRef}".`
|
|
5284
|
+
);
|
|
5285
|
+
}
|
|
5286
|
+
const ctx = req.openhiContext;
|
|
5287
|
+
try {
|
|
5288
|
+
const result = await searchAppointmentsByActorOperation({
|
|
5289
|
+
context: ctx,
|
|
5290
|
+
actorReference: actorRef,
|
|
5291
|
+
dateConstraints
|
|
5292
|
+
});
|
|
5293
|
+
const bundle = buildSearchsetBundle(
|
|
5294
|
+
BASE_PATH.APPOINTMENT,
|
|
5295
|
+
result.entries
|
|
5296
|
+
);
|
|
5297
|
+
return res.json(bundle);
|
|
5298
|
+
} catch (err) {
|
|
5299
|
+
return sendOperationOutcome500(
|
|
5300
|
+
res,
|
|
5301
|
+
err,
|
|
5302
|
+
"GET /Appointment?actor= search error:"
|
|
5303
|
+
);
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5174
5306
|
if (patientId !== void 0) {
|
|
5175
5307
|
const ctx = req.openhiContext;
|
|
5176
5308
|
try {
|
|
@@ -12762,7 +12894,7 @@ async function listEncountersOperation(params) {
|
|
|
12762
12894
|
}
|
|
12763
12895
|
|
|
12764
12896
|
// src/data/operations/data/encounter/encounter-search-by-date-operation.ts
|
|
12765
|
-
var
|
|
12897
|
+
var DEFAULT_LIMIT5 = 100;
|
|
12766
12898
|
function buildSearchEncountersByDateSql(opts) {
|
|
12767
12899
|
const periodPredicates = buildPeriodSearchPredicateSql(
|
|
12768
12900
|
opts.periodConstraints
|
|
@@ -12791,7 +12923,7 @@ async function searchEncountersByDateOperation(params) {
|
|
|
12791
12923
|
}
|
|
12792
12924
|
const { tenantId, workspaceId } = context;
|
|
12793
12925
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
12794
|
-
const limit = params.limit ??
|
|
12926
|
+
const limit = params.limit ?? DEFAULT_LIMIT5;
|
|
12795
12927
|
const sql = buildSearchEncountersByDateSql({ periodConstraints });
|
|
12796
12928
|
const queryParams = [
|
|
12797
12929
|
{ name: "tenantId", value: tenantId },
|
|
@@ -12810,6 +12942,64 @@ async function searchEncountersByDateOperation(params) {
|
|
|
12810
12942
|
return { entries, total: entries.length };
|
|
12811
12943
|
}
|
|
12812
12944
|
|
|
12945
|
+
// src/data/operations/data/encounter/encounter-search-by-participant-operation.ts
|
|
12946
|
+
var DEFAULT_LIMIT6 = 100;
|
|
12947
|
+
function buildSearchEncountersByParticipantSql(opts) {
|
|
12948
|
+
const periodPredicates = buildPeriodSearchPredicateSql(
|
|
12949
|
+
opts?.periodConstraints ?? []
|
|
12950
|
+
);
|
|
12951
|
+
const lines = [
|
|
12952
|
+
"SELECT resource_id AS id, resource",
|
|
12953
|
+
"FROM resources",
|
|
12954
|
+
"WHERE tenant_id = :tenantId",
|
|
12955
|
+
" AND workspace_id = :workspaceId",
|
|
12956
|
+
" AND resource_type = 'Encounter'",
|
|
12957
|
+
" AND deleted_at IS NULL",
|
|
12958
|
+
` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`
|
|
12959
|
+
];
|
|
12960
|
+
for (const fragment of periodPredicates) {
|
|
12961
|
+
lines.push(` AND ${fragment}`);
|
|
12962
|
+
}
|
|
12963
|
+
lines.push("ORDER BY last_updated DESC");
|
|
12964
|
+
lines.push("LIMIT :limit;");
|
|
12965
|
+
return lines.join("\n");
|
|
12966
|
+
}
|
|
12967
|
+
async function searchEncountersByParticipantOperation(params) {
|
|
12968
|
+
const { context, participantReference } = params;
|
|
12969
|
+
const periodConstraints = params.periodConstraints ?? [];
|
|
12970
|
+
const { tenantId, workspaceId } = context;
|
|
12971
|
+
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
12972
|
+
const limit = params.limit ?? DEFAULT_LIMIT6;
|
|
12973
|
+
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
12974
|
+
shape: {
|
|
12975
|
+
kind: "array-of-objects",
|
|
12976
|
+
field: "participant",
|
|
12977
|
+
subfield: "individual"
|
|
12978
|
+
},
|
|
12979
|
+
reference: participantReference,
|
|
12980
|
+
tenantId,
|
|
12981
|
+
workspaceId
|
|
12982
|
+
});
|
|
12983
|
+
const sql = buildSearchEncountersByParticipantSql({ periodConstraints });
|
|
12984
|
+
const queryParams = [
|
|
12985
|
+
{ name: "tenantId", value: tenantId },
|
|
12986
|
+
{ name: "workspaceId", value: workspaceId },
|
|
12987
|
+
{ name: "containmentRelative", value: containmentRelative },
|
|
12988
|
+
{ name: "containmentUrn", value: containmentUrn },
|
|
12989
|
+
{ name: "limit", value: limit },
|
|
12990
|
+
...buildPeriodSearchPredicateParams(periodConstraints)
|
|
12991
|
+
];
|
|
12992
|
+
const rows = await runner.query(sql, queryParams);
|
|
12993
|
+
const entries = rows.map((row) => ({
|
|
12994
|
+
id: row.id,
|
|
12995
|
+
resource: {
|
|
12996
|
+
...row.resource,
|
|
12997
|
+
id: row.id
|
|
12998
|
+
}
|
|
12999
|
+
}));
|
|
13000
|
+
return { entries, total: entries.length };
|
|
13001
|
+
}
|
|
13002
|
+
|
|
12813
13003
|
// src/data/rest-api/routes/data/encounter/encounter-list-route.ts
|
|
12814
13004
|
function singleStringQueryParam2(req, name) {
|
|
12815
13005
|
const v = req.query[name];
|
|
@@ -12859,11 +13049,42 @@ function sendInvalidSearch4002(res, diagnostics) {
|
|
|
12859
13049
|
}
|
|
12860
13050
|
async function listEncountersRoute(req, res) {
|
|
12861
13051
|
const patientId = singleStringQueryParam2(req, "patient");
|
|
13052
|
+
const participantRef = singleStringQueryParam2(req, "participant");
|
|
12862
13053
|
const parsed = parseEncounterDateConstraints(req);
|
|
12863
13054
|
if (isError2(parsed)) {
|
|
12864
13055
|
return sendInvalidSearch4002(res, parsed.error);
|
|
12865
13056
|
}
|
|
12866
13057
|
const periodConstraints = parsed;
|
|
13058
|
+
if (patientId !== void 0 && participantRef !== void 0) {
|
|
13059
|
+
return sendInvalidSearch4002(
|
|
13060
|
+
res,
|
|
13061
|
+
"?patient= and ?participant= cannot be combined on the same request."
|
|
13062
|
+
);
|
|
13063
|
+
}
|
|
13064
|
+
if (participantRef !== void 0) {
|
|
13065
|
+
if (parseTypedReference(participantRef) === void 0) {
|
|
13066
|
+
return sendInvalidSearch4002(
|
|
13067
|
+
res,
|
|
13068
|
+
`?participant must be a typed reference like "Practitioner/<id>"; got "${participantRef}".`
|
|
13069
|
+
);
|
|
13070
|
+
}
|
|
13071
|
+
const ctx = req.openhiContext;
|
|
13072
|
+
try {
|
|
13073
|
+
const result = await searchEncountersByParticipantOperation({
|
|
13074
|
+
context: ctx,
|
|
13075
|
+
participantReference: participantRef,
|
|
13076
|
+
periodConstraints
|
|
13077
|
+
});
|
|
13078
|
+
const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
|
|
13079
|
+
return res.json(bundle);
|
|
13080
|
+
} catch (err) {
|
|
13081
|
+
return sendOperationOutcome500(
|
|
13082
|
+
res,
|
|
13083
|
+
err,
|
|
13084
|
+
"GET /Encounter?participant= search error:"
|
|
13085
|
+
);
|
|
13086
|
+
}
|
|
13087
|
+
}
|
|
12867
13088
|
if (patientId !== void 0) {
|
|
12868
13089
|
const ctx = req.openhiContext;
|
|
12869
13090
|
try {
|
|
@@ -24664,8 +24885,91 @@ async function listPatientsOperation(params) {
|
|
|
24664
24885
|
);
|
|
24665
24886
|
}
|
|
24666
24887
|
|
|
24888
|
+
// src/data/operations/data/patient/patient-search-by-general-practitioner-operation.ts
|
|
24889
|
+
var DEFAULT_LIMIT7 = 100;
|
|
24890
|
+
function buildSearchPatientsByGeneralPractitionerSql() {
|
|
24891
|
+
return [
|
|
24892
|
+
"SELECT resource_id AS id, resource",
|
|
24893
|
+
"FROM resources",
|
|
24894
|
+
"WHERE tenant_id = :tenantId",
|
|
24895
|
+
" AND workspace_id = :workspaceId",
|
|
24896
|
+
" AND resource_type = 'Patient'",
|
|
24897
|
+
" AND deleted_at IS NULL",
|
|
24898
|
+
` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`,
|
|
24899
|
+
"ORDER BY last_updated DESC",
|
|
24900
|
+
"LIMIT :limit;"
|
|
24901
|
+
].join("\n");
|
|
24902
|
+
}
|
|
24903
|
+
async function searchPatientsByGeneralPractitionerOperation(params) {
|
|
24904
|
+
const { context, generalPractitionerReference } = params;
|
|
24905
|
+
const { tenantId, workspaceId } = context;
|
|
24906
|
+
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
24907
|
+
const limit = params.limit ?? DEFAULT_LIMIT7;
|
|
24908
|
+
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
24909
|
+
shape: { kind: "array-of-references", field: "generalPractitioner" },
|
|
24910
|
+
reference: generalPractitionerReference,
|
|
24911
|
+
tenantId,
|
|
24912
|
+
workspaceId
|
|
24913
|
+
});
|
|
24914
|
+
const sql = buildSearchPatientsByGeneralPractitionerSql();
|
|
24915
|
+
const queryParams = [
|
|
24916
|
+
{ name: "tenantId", value: tenantId },
|
|
24917
|
+
{ name: "workspaceId", value: workspaceId },
|
|
24918
|
+
{ name: "containmentRelative", value: containmentRelative },
|
|
24919
|
+
{ name: "containmentUrn", value: containmentUrn },
|
|
24920
|
+
{ name: "limit", value: limit }
|
|
24921
|
+
];
|
|
24922
|
+
const rows = await runner.query(sql, queryParams);
|
|
24923
|
+
const entries = rows.map((row) => ({
|
|
24924
|
+
id: row.id,
|
|
24925
|
+
resource: { ...row.resource, id: row.id }
|
|
24926
|
+
}));
|
|
24927
|
+
return { entries, total: entries.length };
|
|
24928
|
+
}
|
|
24929
|
+
|
|
24667
24930
|
// src/data/rest-api/routes/data/patient/patient-list-route.ts
|
|
24931
|
+
function singleStringQueryParam3(req, name) {
|
|
24932
|
+
const v = req.query[name];
|
|
24933
|
+
if (typeof v !== "string") {
|
|
24934
|
+
return void 0;
|
|
24935
|
+
}
|
|
24936
|
+
const trimmed = v.trim();
|
|
24937
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
24938
|
+
}
|
|
24939
|
+
function sendInvalidSearch4003(res, diagnostics) {
|
|
24940
|
+
return res.status(400).json({
|
|
24941
|
+
resourceType: "OperationOutcome",
|
|
24942
|
+
issue: [{ severity: "error", code: "invalid", diagnostics }]
|
|
24943
|
+
});
|
|
24944
|
+
}
|
|
24668
24945
|
async function listPatientsRoute(req, res) {
|
|
24946
|
+
const generalPractitionerRef = singleStringQueryParam3(
|
|
24947
|
+
req,
|
|
24948
|
+
"general-practitioner"
|
|
24949
|
+
);
|
|
24950
|
+
if (generalPractitionerRef !== void 0) {
|
|
24951
|
+
if (parseTypedReference(generalPractitionerRef) === void 0) {
|
|
24952
|
+
return sendInvalidSearch4003(
|
|
24953
|
+
res,
|
|
24954
|
+
`?general-practitioner must be a typed reference like "Practitioner/<id>"; got "${generalPractitionerRef}".`
|
|
24955
|
+
);
|
|
24956
|
+
}
|
|
24957
|
+
const ctx = req.openhiContext;
|
|
24958
|
+
try {
|
|
24959
|
+
const result = await searchPatientsByGeneralPractitionerOperation({
|
|
24960
|
+
context: ctx,
|
|
24961
|
+
generalPractitionerReference: generalPractitionerRef
|
|
24962
|
+
});
|
|
24963
|
+
const bundle = buildSearchsetBundle(BASE_PATH.PATIENT, result.entries);
|
|
24964
|
+
return res.json(bundle);
|
|
24965
|
+
} catch (err) {
|
|
24966
|
+
return sendOperationOutcome500(
|
|
24967
|
+
res,
|
|
24968
|
+
err,
|
|
24969
|
+
"GET /Patient?general-practitioner= search error:"
|
|
24970
|
+
);
|
|
24971
|
+
}
|
|
24972
|
+
}
|
|
24669
24973
|
return handleListRoute({
|
|
24670
24974
|
req,
|
|
24671
24975
|
res,
|
|
@@ -28552,8 +28856,90 @@ async function listSchedulesOperation(params) {
|
|
|
28552
28856
|
);
|
|
28553
28857
|
}
|
|
28554
28858
|
|
|
28859
|
+
// src/data/operations/data/schedule/schedule-search-by-actor-operation.ts
|
|
28860
|
+
var DEFAULT_LIMIT8 = 100;
|
|
28861
|
+
function buildSearchSchedulesByActorSql() {
|
|
28862
|
+
return [
|
|
28863
|
+
"SELECT resource_id AS id, resource",
|
|
28864
|
+
"FROM resources",
|
|
28865
|
+
"WHERE tenant_id = :tenantId",
|
|
28866
|
+
" AND workspace_id = :workspaceId",
|
|
28867
|
+
" AND resource_type = 'Schedule'",
|
|
28868
|
+
" AND deleted_at IS NULL",
|
|
28869
|
+
` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`,
|
|
28870
|
+
"ORDER BY last_updated DESC",
|
|
28871
|
+
"LIMIT :limit;"
|
|
28872
|
+
].join("\n");
|
|
28873
|
+
}
|
|
28874
|
+
async function searchSchedulesByActorOperation(params) {
|
|
28875
|
+
const { context, actorReference } = params;
|
|
28876
|
+
const { tenantId, workspaceId } = context;
|
|
28877
|
+
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
28878
|
+
const limit = params.limit ?? DEFAULT_LIMIT8;
|
|
28879
|
+
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
28880
|
+
shape: { kind: "array-of-references", field: "actor" },
|
|
28881
|
+
reference: actorReference,
|
|
28882
|
+
tenantId,
|
|
28883
|
+
workspaceId
|
|
28884
|
+
});
|
|
28885
|
+
const queryParams = [
|
|
28886
|
+
{ name: "tenantId", value: tenantId },
|
|
28887
|
+
{ name: "workspaceId", value: workspaceId },
|
|
28888
|
+
{ name: "containmentRelative", value: containmentRelative },
|
|
28889
|
+
{ name: "containmentUrn", value: containmentUrn },
|
|
28890
|
+
{ name: "limit", value: limit }
|
|
28891
|
+
];
|
|
28892
|
+
const rows = await runner.query(
|
|
28893
|
+
buildSearchSchedulesByActorSql(),
|
|
28894
|
+
queryParams
|
|
28895
|
+
);
|
|
28896
|
+
const entries = rows.map((row) => ({
|
|
28897
|
+
id: row.id,
|
|
28898
|
+
resource: { ...row.resource, id: row.id }
|
|
28899
|
+
}));
|
|
28900
|
+
return { entries, total: entries.length };
|
|
28901
|
+
}
|
|
28902
|
+
|
|
28555
28903
|
// src/data/rest-api/routes/data/schedule/schedule-list-route.ts
|
|
28904
|
+
function singleStringQueryParam4(req, name) {
|
|
28905
|
+
const v = req.query[name];
|
|
28906
|
+
if (typeof v !== "string") {
|
|
28907
|
+
return void 0;
|
|
28908
|
+
}
|
|
28909
|
+
const trimmed = v.trim();
|
|
28910
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
28911
|
+
}
|
|
28912
|
+
function sendInvalidSearch4004(res, diagnostics) {
|
|
28913
|
+
return res.status(400).json({
|
|
28914
|
+
resourceType: "OperationOutcome",
|
|
28915
|
+
issue: [{ severity: "error", code: "invalid", diagnostics }]
|
|
28916
|
+
});
|
|
28917
|
+
}
|
|
28556
28918
|
async function listSchedulesRoute(req, res) {
|
|
28919
|
+
const actorRef = singleStringQueryParam4(req, "actor");
|
|
28920
|
+
if (actorRef !== void 0) {
|
|
28921
|
+
if (parseTypedReference(actorRef) === void 0) {
|
|
28922
|
+
return sendInvalidSearch4004(
|
|
28923
|
+
res,
|
|
28924
|
+
`?actor must be a typed reference like "Practitioner/<id>"; got "${actorRef}".`
|
|
28925
|
+
);
|
|
28926
|
+
}
|
|
28927
|
+
const ctx = req.openhiContext;
|
|
28928
|
+
try {
|
|
28929
|
+
const result = await searchSchedulesByActorOperation({
|
|
28930
|
+
context: ctx,
|
|
28931
|
+
actorReference: actorRef
|
|
28932
|
+
});
|
|
28933
|
+
const bundle = buildSearchsetBundle(BASE_PATH.SCHEDULE, result.entries);
|
|
28934
|
+
return res.json(bundle);
|
|
28935
|
+
} catch (err) {
|
|
28936
|
+
return sendOperationOutcome500(
|
|
28937
|
+
res,
|
|
28938
|
+
err,
|
|
28939
|
+
"GET /Schedule?actor= search error:"
|
|
28940
|
+
);
|
|
28941
|
+
}
|
|
28942
|
+
}
|
|
28557
28943
|
return handleListRoute({
|
|
28558
28944
|
req,
|
|
28559
28945
|
res,
|
|
@@ -32234,8 +32620,164 @@ async function listTasksOperation(params) {
|
|
|
32234
32620
|
);
|
|
32235
32621
|
}
|
|
32236
32622
|
|
|
32623
|
+
// src/data/operations/data/task/task-search-by-owner-operation.ts
|
|
32624
|
+
var DEFAULT_LIMIT9 = 100;
|
|
32625
|
+
function buildSearchTasksByOwnerSql() {
|
|
32626
|
+
return [
|
|
32627
|
+
"SELECT resource_id AS id, resource",
|
|
32628
|
+
"FROM resources",
|
|
32629
|
+
"WHERE tenant_id = :tenantId",
|
|
32630
|
+
" AND workspace_id = :workspaceId",
|
|
32631
|
+
" AND resource_type = 'Task'",
|
|
32632
|
+
" AND deleted_at IS NULL",
|
|
32633
|
+
` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`,
|
|
32634
|
+
"ORDER BY last_updated DESC",
|
|
32635
|
+
"LIMIT :limit;"
|
|
32636
|
+
].join("\n");
|
|
32637
|
+
}
|
|
32638
|
+
async function searchTasksByOwnerOperation(params) {
|
|
32639
|
+
const { context, ownerReference } = params;
|
|
32640
|
+
const { tenantId, workspaceId } = context;
|
|
32641
|
+
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
32642
|
+
const limit = params.limit ?? DEFAULT_LIMIT9;
|
|
32643
|
+
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
32644
|
+
shape: { kind: "scalar", field: "owner" },
|
|
32645
|
+
reference: ownerReference,
|
|
32646
|
+
tenantId,
|
|
32647
|
+
workspaceId
|
|
32648
|
+
});
|
|
32649
|
+
const queryParams = [
|
|
32650
|
+
{ name: "tenantId", value: tenantId },
|
|
32651
|
+
{ name: "workspaceId", value: workspaceId },
|
|
32652
|
+
{ name: "containmentRelative", value: containmentRelative },
|
|
32653
|
+
{ name: "containmentUrn", value: containmentUrn },
|
|
32654
|
+
{ name: "limit", value: limit }
|
|
32655
|
+
];
|
|
32656
|
+
const rows = await runner.query(
|
|
32657
|
+
buildSearchTasksByOwnerSql(),
|
|
32658
|
+
queryParams
|
|
32659
|
+
);
|
|
32660
|
+
const entries = rows.map((row) => ({
|
|
32661
|
+
id: row.id,
|
|
32662
|
+
resource: { ...row.resource, id: row.id }
|
|
32663
|
+
}));
|
|
32664
|
+
return { entries, total: entries.length };
|
|
32665
|
+
}
|
|
32666
|
+
|
|
32667
|
+
// src/data/operations/data/task/task-search-by-requester-operation.ts
|
|
32668
|
+
var DEFAULT_LIMIT10 = 100;
|
|
32669
|
+
function buildSearchTasksByRequesterSql() {
|
|
32670
|
+
return [
|
|
32671
|
+
"SELECT resource_id AS id, resource",
|
|
32672
|
+
"FROM resources",
|
|
32673
|
+
"WHERE tenant_id = :tenantId",
|
|
32674
|
+
" AND workspace_id = :workspaceId",
|
|
32675
|
+
" AND resource_type = 'Task'",
|
|
32676
|
+
" AND deleted_at IS NULL",
|
|
32677
|
+
` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`,
|
|
32678
|
+
"ORDER BY last_updated DESC",
|
|
32679
|
+
"LIMIT :limit;"
|
|
32680
|
+
].join("\n");
|
|
32681
|
+
}
|
|
32682
|
+
async function searchTasksByRequesterOperation(params) {
|
|
32683
|
+
const { context, requesterReference } = params;
|
|
32684
|
+
const { tenantId, workspaceId } = context;
|
|
32685
|
+
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
32686
|
+
const limit = params.limit ?? DEFAULT_LIMIT10;
|
|
32687
|
+
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
32688
|
+
shape: { kind: "scalar", field: "requester" },
|
|
32689
|
+
reference: requesterReference,
|
|
32690
|
+
tenantId,
|
|
32691
|
+
workspaceId
|
|
32692
|
+
});
|
|
32693
|
+
const queryParams = [
|
|
32694
|
+
{ name: "tenantId", value: tenantId },
|
|
32695
|
+
{ name: "workspaceId", value: workspaceId },
|
|
32696
|
+
{ name: "containmentRelative", value: containmentRelative },
|
|
32697
|
+
{ name: "containmentUrn", value: containmentUrn },
|
|
32698
|
+
{ name: "limit", value: limit }
|
|
32699
|
+
];
|
|
32700
|
+
const rows = await runner.query(
|
|
32701
|
+
buildSearchTasksByRequesterSql(),
|
|
32702
|
+
queryParams
|
|
32703
|
+
);
|
|
32704
|
+
const entries = rows.map((row) => ({
|
|
32705
|
+
id: row.id,
|
|
32706
|
+
resource: { ...row.resource, id: row.id }
|
|
32707
|
+
}));
|
|
32708
|
+
return { entries, total: entries.length };
|
|
32709
|
+
}
|
|
32710
|
+
|
|
32237
32711
|
// src/data/rest-api/routes/data/task/task-list-route.ts
|
|
32712
|
+
function singleStringQueryParam5(req, name) {
|
|
32713
|
+
const v = req.query[name];
|
|
32714
|
+
if (typeof v !== "string") {
|
|
32715
|
+
return void 0;
|
|
32716
|
+
}
|
|
32717
|
+
const trimmed = v.trim();
|
|
32718
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
32719
|
+
}
|
|
32720
|
+
function sendInvalidSearch4005(res, diagnostics) {
|
|
32721
|
+
return res.status(400).json({
|
|
32722
|
+
resourceType: "OperationOutcome",
|
|
32723
|
+
issue: [{ severity: "error", code: "invalid", diagnostics }]
|
|
32724
|
+
});
|
|
32725
|
+
}
|
|
32238
32726
|
async function listTasksRoute(req, res) {
|
|
32727
|
+
const ownerRef = singleStringQueryParam5(req, "owner");
|
|
32728
|
+
const requesterRef = singleStringQueryParam5(req, "requester");
|
|
32729
|
+
if (ownerRef !== void 0 && requesterRef !== void 0) {
|
|
32730
|
+
return sendInvalidSearch4005(
|
|
32731
|
+
res,
|
|
32732
|
+
"?owner= and ?requester= cannot be combined on the same request."
|
|
32733
|
+
);
|
|
32734
|
+
}
|
|
32735
|
+
if (ownerRef !== void 0) {
|
|
32736
|
+
if (parseTypedReference(ownerRef) === void 0) {
|
|
32737
|
+
return sendInvalidSearch4005(
|
|
32738
|
+
res,
|
|
32739
|
+
`?owner must be a typed reference like "Practitioner/<id>"; got "${ownerRef}".`
|
|
32740
|
+
);
|
|
32741
|
+
}
|
|
32742
|
+
const ctx = req.openhiContext;
|
|
32743
|
+
try {
|
|
32744
|
+
const result = await searchTasksByOwnerOperation({
|
|
32745
|
+
context: ctx,
|
|
32746
|
+
ownerReference: ownerRef
|
|
32747
|
+
});
|
|
32748
|
+
const bundle = buildSearchsetBundle(BASE_PATH.TASK, result.entries);
|
|
32749
|
+
return res.json(bundle);
|
|
32750
|
+
} catch (err) {
|
|
32751
|
+
return sendOperationOutcome500(
|
|
32752
|
+
res,
|
|
32753
|
+
err,
|
|
32754
|
+
"GET /Task?owner= search error:"
|
|
32755
|
+
);
|
|
32756
|
+
}
|
|
32757
|
+
}
|
|
32758
|
+
if (requesterRef !== void 0) {
|
|
32759
|
+
if (parseTypedReference(requesterRef) === void 0) {
|
|
32760
|
+
return sendInvalidSearch4005(
|
|
32761
|
+
res,
|
|
32762
|
+
`?requester must be a typed reference like "Practitioner/<id>"; got "${requesterRef}".`
|
|
32763
|
+
);
|
|
32764
|
+
}
|
|
32765
|
+
const ctx = req.openhiContext;
|
|
32766
|
+
try {
|
|
32767
|
+
const result = await searchTasksByRequesterOperation({
|
|
32768
|
+
context: ctx,
|
|
32769
|
+
requesterReference: requesterRef
|
|
32770
|
+
});
|
|
32771
|
+
const bundle = buildSearchsetBundle(BASE_PATH.TASK, result.entries);
|
|
32772
|
+
return res.json(bundle);
|
|
32773
|
+
} catch (err) {
|
|
32774
|
+
return sendOperationOutcome500(
|
|
32775
|
+
res,
|
|
32776
|
+
err,
|
|
32777
|
+
"GET /Task?requester= search error:"
|
|
32778
|
+
);
|
|
32779
|
+
}
|
|
32780
|
+
}
|
|
32239
32781
|
return handleListRoute({
|
|
32240
32782
|
req,
|
|
32241
32783
|
res,
|