@fuego-systems/core 0.1.4

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 (54) hide show
  1. package/dist/constants.d.ts +89 -0
  2. package/dist/constants.d.ts.map +1 -0
  3. package/dist/constants.js +97 -0
  4. package/dist/constants.js.map +1 -0
  5. package/dist/db/types.d.ts +49 -0
  6. package/dist/db/types.d.ts.map +1 -0
  7. package/dist/db/types.js +7 -0
  8. package/dist/db/types.js.map +1 -0
  9. package/dist/fhir/helpers.d.ts +4 -0
  10. package/dist/fhir/helpers.d.ts.map +1 -0
  11. package/dist/fhir/helpers.js +7 -0
  12. package/dist/fhir/helpers.js.map +1 -0
  13. package/dist/fhir/index.d.ts +3 -0
  14. package/dist/fhir/index.d.ts.map +1 -0
  15. package/dist/fhir/index.js +19 -0
  16. package/dist/fhir/index.js.map +1 -0
  17. package/dist/fhir/transaction-utils.d.ts +22 -0
  18. package/dist/fhir/transaction-utils.d.ts.map +1 -0
  19. package/dist/fhir/transaction-utils.js +156 -0
  20. package/dist/fhir/transaction-utils.js.map +1 -0
  21. package/dist/index.d.ts +9 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +23 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/scheduling/appointment.d.ts +15 -0
  26. package/dist/scheduling/appointment.d.ts.map +1 -0
  27. package/dist/scheduling/appointment.js +338 -0
  28. package/dist/scheduling/appointment.js.map +1 -0
  29. package/dist/scheduling/extension.d.ts +58 -0
  30. package/dist/scheduling/extension.d.ts.map +1 -0
  31. package/dist/scheduling/extension.js +3 -0
  32. package/dist/scheduling/extension.js.map +1 -0
  33. package/dist/scheduling/slot.d.ts +15 -0
  34. package/dist/scheduling/slot.d.ts.map +1 -0
  35. package/dist/scheduling/slot.js +291 -0
  36. package/dist/scheduling/slot.js.map +1 -0
  37. package/dist/scheduling/time-span.d.ts +26 -0
  38. package/dist/scheduling/time-span.d.ts.map +1 -0
  39. package/dist/scheduling/time-span.js +143 -0
  40. package/dist/scheduling/time-span.js.map +1 -0
  41. package/dist/search/alias-builder.d.ts +23 -0
  42. package/dist/search/alias-builder.d.ts.map +1 -0
  43. package/dist/search/alias-builder.js +126 -0
  44. package/dist/search/alias-builder.js.map +1 -0
  45. package/dist/search/normalizer.d.ts +25 -0
  46. package/dist/search/normalizer.d.ts.map +1 -0
  47. package/dist/search/normalizer.js +81 -0
  48. package/dist/search/normalizer.js.map +1 -0
  49. package/dist/test/factories.d.ts +10 -0
  50. package/dist/test/factories.d.ts.map +1 -0
  51. package/dist/test/factories.js +68 -0
  52. package/dist/test/factories.js.map +1 -0
  53. package/dist/tsconfig.tsbuildinfo +1 -0
  54. package/package.json +33 -0
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.holdAppointment = holdAppointment;
4
+ exports.bookAppointment = bookAppointment;
5
+ exports.cancelAppointment = cancelAppointment;
6
+ const core_1 = require("@medplum/core");
7
+ const date_fns_1 = require("date-fns");
8
+ const slot_1 = require("./slot");
9
+ async function holdAppointment(medplum, options) {
10
+ return createAppointment(medplum, options.slots, 'pending', 'busy-tentative', options.patientReference);
11
+ }
12
+ async function bookAppointment(medplum, options) {
13
+ var _a, _b, _c, _d, _e;
14
+ const { appointmentReference, slots, patientReference } = options;
15
+ if (appointmentReference) {
16
+ const appointment = await medplum.readReference(appointmentReference);
17
+ if (appointment.status !== 'pending') {
18
+ throw new Error('Expected pending appointment');
19
+ }
20
+ if (!appointment.slot) {
21
+ throw new Error('Slots missing on pending appointment');
22
+ }
23
+ const appointmentSlots = await medplum.searchResources('Slot', {
24
+ _id: appointment.slot.map(slot => (0, core_1.getReferenceString)(slot)).join(','),
25
+ status: 'busy-tentative'
26
+ });
27
+ if (appointmentSlots.length !== appointment.slot.length) {
28
+ throw new Error('Reserved slots not found for pending appointment');
29
+ }
30
+ const appoinmentParameters = {
31
+ resourceType: 'Parameters',
32
+ parameter: [{
33
+ name: 'operation',
34
+ part: [
35
+ { name: 'op', valueCode: 'test' },
36
+ { name: 'path', valueString: '/meta/versionId' },
37
+ { name: 'value', valueString: JSON.stringify((_a = appointment.meta) === null || _a === void 0 ? void 0 : _a.versionId) }
38
+ ]
39
+ }, {
40
+ name: 'operation',
41
+ part: [
42
+ { name: 'op', valueCode: 'replace' },
43
+ { name: 'path', valueString: '/status' },
44
+ { name: 'value', valueString: JSON.stringify('booked') }
45
+ ]
46
+ }]
47
+ };
48
+ if (patientReference) {
49
+ if (appointment.participant.some(participant => participant.actor && (0, slot_1.isResourceReference)('Patient', participant.actor))) {
50
+ throw new Error('Appointment already has a patient participant');
51
+ }
52
+ // Validate patient reference
53
+ await medplum.readReference(patientReference);
54
+ (_b = appoinmentParameters.parameter) === null || _b === void 0 ? void 0 : _b.push({
55
+ name: 'operation',
56
+ part: [
57
+ { name: 'op', valueCode: 'add' },
58
+ { name: 'path', valueString: '/participant/-' },
59
+ {
60
+ name: 'value',
61
+ valueString: JSON.stringify({
62
+ actor: patientReference,
63
+ required: 'required',
64
+ status: 'needs-action'
65
+ })
66
+ }
67
+ ]
68
+ });
69
+ }
70
+ const transaction = {
71
+ resourceType: 'Bundle',
72
+ type: 'transaction',
73
+ entry: [{
74
+ request: {
75
+ method: 'PATCH',
76
+ url: (0, core_1.getReferenceString)(appointment)
77
+ },
78
+ resource: appoinmentParameters
79
+ }]
80
+ };
81
+ for (const slot of appointmentSlots) {
82
+ (_c = transaction.entry) === null || _c === void 0 ? void 0 : _c.push({
83
+ request: {
84
+ method: 'PATCH',
85
+ url: (0, core_1.getReferenceString)(slot)
86
+ },
87
+ resource: {
88
+ resourceType: 'Parameters',
89
+ parameter: [{
90
+ name: 'operation',
91
+ part: [
92
+ { name: 'op', valueCode: 'test' },
93
+ { name: 'path', valueString: '/meta/versionId' },
94
+ { name: 'value', valueString: JSON.stringify((_d = slot.meta) === null || _d === void 0 ? void 0 : _d.versionId) }
95
+ ]
96
+ }, {
97
+ name: 'operation',
98
+ part: [
99
+ { name: 'op', valueCode: 'replace' },
100
+ { name: 'path', valueString: '/status' },
101
+ { name: 'value', valueString: JSON.stringify('busy') }
102
+ ]
103
+ }]
104
+ }
105
+ });
106
+ }
107
+ const result = await medplum.executeBatch(transaction);
108
+ const ok = (_e = result.entry) === null || _e === void 0 ? void 0 : _e.every(entry => { var _a, _b; return ((_b = (_a = entry.response) === null || _a === void 0 ? void 0 : _a.outcome) === null || _b === void 0 ? void 0 : _b.id) === 'ok'; });
109
+ if (ok) {
110
+ return {
111
+ resourceType: 'Bundle',
112
+ type: 'searchset',
113
+ entry: [findAppointmentBundleEntry(result)]
114
+ };
115
+ }
116
+ else {
117
+ return {
118
+ resourceType: 'Bundle',
119
+ type: 'searchset',
120
+ entry: [{
121
+ response: conflictResponse('Appointment update conflict')
122
+ }]
123
+ };
124
+ }
125
+ }
126
+ else if (slots) {
127
+ return createAppointment(medplum, slots, 'booked', 'busy', patientReference);
128
+ }
129
+ throw new Error('Appointment or slots not provided');
130
+ }
131
+ async function cancelAppointment(medplum, appointmentReference) {
132
+ var _a, _b, _c;
133
+ const appointment = await medplum.readReference(appointmentReference);
134
+ if (appointment.status !== 'booked' && appointment.status !== 'pending') {
135
+ throw new Error('Unexpected appointment status');
136
+ }
137
+ const transaction = {
138
+ resourceType: 'Bundle',
139
+ type: 'transaction',
140
+ entry: [{
141
+ request: {
142
+ method: 'PATCH',
143
+ url: (0, core_1.getReferenceString)(appointmentReference)
144
+ },
145
+ resource: {
146
+ resourceType: 'Parameters',
147
+ parameter: [{
148
+ name: 'operation',
149
+ part: [
150
+ { name: 'op', valueCode: 'test' },
151
+ { name: 'path', valueString: '/meta/versionId' },
152
+ { name: 'value', valueString: JSON.stringify((_a = appointment.meta) === null || _a === void 0 ? void 0 : _a.versionId) }
153
+ ]
154
+ }, {
155
+ name: 'operation',
156
+ part: [
157
+ { name: 'op', valueCode: 'replace' },
158
+ { name: 'path', valueString: '/status' },
159
+ { name: 'value', valueString: JSON.stringify('cancelled') }
160
+ ]
161
+ }]
162
+ }
163
+ }]
164
+ };
165
+ if (appointment.slot) {
166
+ for (const slot of appointment.slot) {
167
+ (_b = transaction.entry) === null || _b === void 0 ? void 0 : _b.push({
168
+ request: {
169
+ method: 'DELETE',
170
+ url: (0, core_1.getReferenceString)(slot)
171
+ }
172
+ });
173
+ }
174
+ }
175
+ const result = await medplum.executeBatch(transaction);
176
+ const ok = (_c = result.entry) === null || _c === void 0 ? void 0 : _c.every(entry => { var _a, _b; return ((_b = (_a = entry.response) === null || _a === void 0 ? void 0 : _a.outcome) === null || _b === void 0 ? void 0 : _b.id) === 'ok'; });
177
+ if (ok) {
178
+ return {
179
+ resourceType: 'Bundle',
180
+ type: 'searchset',
181
+ entry: [findAppointmentBundleEntry(result)]
182
+ };
183
+ }
184
+ else {
185
+ return {
186
+ resourceType: 'Bundle',
187
+ type: 'searchset',
188
+ entry: [{
189
+ response: conflictResponse('Appointment update conflict')
190
+ }]
191
+ };
192
+ }
193
+ }
194
+ async function createAppointment(medplum, slots, appoinmentStatus, slotStatus, patientReference) {
195
+ var _a, _b, _c;
196
+ const [first, ...rest] = slots;
197
+ for (const slot of rest) {
198
+ if (first.start !== slot.start || first.end !== slot.end) {
199
+ throw new Error('Slot dates do not match');
200
+ }
201
+ }
202
+ const schedules = await medplum.searchResources('Schedule', {
203
+ _id: slots.map(slot => (0, core_1.getReferenceString)(slot.schedule)).join(','),
204
+ active: true
205
+ });
206
+ for (const slot of slots) {
207
+ if (!schedules.some(schedule => (0, core_1.getReferenceString)(schedule) === (0, core_1.getReferenceString)(slot.schedule))) {
208
+ throw new Error('Active schedule for slot not found');
209
+ }
210
+ }
211
+ if (patientReference) {
212
+ // Validate patient reference
213
+ await medplum.readReference(patientReference);
214
+ }
215
+ const transaction = {
216
+ resourceType: 'Bundle',
217
+ type: 'transaction',
218
+ entry: []
219
+ };
220
+ for (const slot of slots) {
221
+ const parameters = new URLSearchParams();
222
+ parameters.set('schedule', (0, core_1.getReferenceString)(slot.schedule));
223
+ parameters.set('status', 'busy,busy-unavailable,busy-tentative');
224
+ parameters.set('start', 'lt' + (0, date_fns_1.formatISO)(slot.end));
225
+ parameters.set('end', 'gt' + (0, date_fns_1.formatISO)(slot.start));
226
+ slot.id = (0, core_1.generateId)();
227
+ (_a = transaction.entry) === null || _a === void 0 ? void 0 : _a.push({
228
+ fullUrl: 'urn:uuid:' + slot.id,
229
+ request: {
230
+ method: 'POST',
231
+ url: 'Slot',
232
+ ifNoneExist: parameters.toString()
233
+ },
234
+ resource: {
235
+ resourceType: 'Slot',
236
+ status: slotStatus,
237
+ start: slot.start,
238
+ end: slot.end,
239
+ schedule: slot.schedule
240
+ }
241
+ });
242
+ }
243
+ const participants = new Map();
244
+ if (patientReference) {
245
+ participants.set((0, core_1.getReferenceString)(patientReference), {
246
+ actor: patientReference,
247
+ required: 'required',
248
+ status: 'needs-action'
249
+ });
250
+ }
251
+ for (const schedule of schedules) {
252
+ for (const actor of schedule.actor) {
253
+ participants.set((0, core_1.getReferenceString)(actor), {
254
+ actor,
255
+ required: 'required',
256
+ status: (0, slot_1.isResourceReference)('Device', actor) || (0, slot_1.isResourceReference)('Location', actor) ? 'accepted' : 'needs-action'
257
+ });
258
+ }
259
+ }
260
+ (_b = transaction.entry) === null || _b === void 0 ? void 0 : _b.push({
261
+ request: {
262
+ method: 'POST',
263
+ url: 'Appointment'
264
+ },
265
+ resource: {
266
+ resourceType: 'Appointment',
267
+ status: appoinmentStatus,
268
+ start: first.start,
269
+ end: first.end,
270
+ participant: Array.from(participants.values()),
271
+ slot: slots.map(slot => {
272
+ return { reference: 'urn:uuid:' + slot.id };
273
+ })
274
+ }
275
+ });
276
+ const result = await medplum.executeBatch(transaction);
277
+ const created = (_c = result.entry) === null || _c === void 0 ? void 0 : _c.filter(entry => { var _a, _b; return ((_b = (_a = entry.response) === null || _a === void 0 ? void 0 : _a.outcome) === null || _b === void 0 ? void 0 : _b.id) === 'created'; });
278
+ if ((created === null || created === void 0 ? void 0 : created.length) !== slots.length + 1) {
279
+ // Not all resources were created. Using ifNoneExists does not fail the transaction,
280
+ // it returns an existing resource if it matches the parameters.
281
+ // Try to delete all created resources by best effort.
282
+ if (created === null || created === void 0 ? void 0 : created.length) {
283
+ await medplum.executeBatch({
284
+ resourceType: 'Bundle',
285
+ type: 'batch',
286
+ entry: created.map(entry => {
287
+ return {
288
+ request: {
289
+ method: 'DELETE',
290
+ url: (0, core_1.getReferenceString)(entry.resource)
291
+ }
292
+ };
293
+ })
294
+ });
295
+ }
296
+ return {
297
+ resourceType: 'Bundle',
298
+ type: 'searchset',
299
+ entry: [{
300
+ response: conflictResponse('Requested time slots not available'),
301
+ resource: {
302
+ resourceType: 'Appointment',
303
+ status: 'cancelled',
304
+ participant: []
305
+ }
306
+ }]
307
+ };
308
+ }
309
+ else {
310
+ return {
311
+ resourceType: 'Bundle',
312
+ type: 'searchset',
313
+ entry: [findAppointmentBundleEntry(result)]
314
+ };
315
+ }
316
+ }
317
+ function findAppointmentBundleEntry(bundle) {
318
+ var _a;
319
+ const appointmentEntry = (_a = bundle.entry) === null || _a === void 0 ? void 0 : _a.find((entry) => (0, core_1.isResource)(entry.resource, 'Appointment'));
320
+ if (!appointmentEntry) {
321
+ throw new Error('Bundle does not contain appointment');
322
+ }
323
+ return appointmentEntry;
324
+ }
325
+ function conflictResponse(message) {
326
+ return {
327
+ outcome: {
328
+ resourceType: 'OperationOutcome',
329
+ issue: [{
330
+ severity: 'error',
331
+ code: 'conflict',
332
+ diagnostics: message
333
+ }]
334
+ },
335
+ status: '409'
336
+ };
337
+ }
338
+ //# sourceMappingURL=appointment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appointment.js","sourceRoot":"","sources":["../../src/scheduling/appointment.ts"],"names":[],"mappings":"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","sourcesContent":["import { generateId, getReferenceString, isResource, MedplumClient } from '@medplum/core'\nimport {\n Appointment,\n AppointmentParticipant,\n Bundle,\n BundleEntry,\n BundleEntryResponse,\n Parameters,\n Patient,\n Reference,\n Resource,\n Slot\n} from '@medplum/fhirtypes'\nimport { formatISO } from 'date-fns'\nimport { isResourceReference } from './slot'\n\nexport interface HoldAppointmentArguments {\n slots: Slot[]\n patientReference?: Reference<Patient>\n}\n\nexport async function holdAppointment (medplum: MedplumClient, options: HoldAppointmentArguments): Promise<Bundle<Appointment>> {\n return createAppointment(\n medplum,\n options.slots,\n 'pending',\n 'busy-tentative',\n options.patientReference)\n}\n\nexport interface BookAppointmentArguments {\n appointmentReference?: Reference<Appointment>\n slots?: Slot[]\n patientReference?: Reference<Patient>\n}\n\nexport async function bookAppointment (medplum: MedplumClient, options: BookAppointmentArguments): Promise<Bundle<Appointment>> {\n const { appointmentReference, slots, patientReference } = options\n\n if (appointmentReference) {\n const appointment = await medplum.readReference(appointmentReference)\n\n if (appointment.status !== 'pending') {\n throw new Error('Expected pending appointment')\n }\n\n if (!appointment.slot) {\n throw new Error('Slots missing on pending appointment')\n }\n\n const appointmentSlots = await medplum.searchResources('Slot', {\n _id: appointment.slot.map(slot => getReferenceString(slot)).join(','),\n status: 'busy-tentative'\n })\n\n if (appointmentSlots.length !== appointment.slot.length) {\n throw new Error('Reserved slots not found for pending appointment')\n }\n\n const appoinmentParameters: Parameters = {\n resourceType: 'Parameters',\n parameter: [{\n name: 'operation',\n part: [\n { name: 'op', valueCode: 'test' },\n { name: 'path', valueString: '/meta/versionId' },\n { name: 'value', valueString: JSON.stringify(appointment.meta?.versionId) }\n ]\n }, {\n name: 'operation',\n part: [\n { name: 'op', valueCode: 'replace' },\n { name: 'path', valueString: '/status' },\n { name: 'value', valueString: JSON.stringify('booked') }\n ]\n }]\n }\n\n if (patientReference) {\n if (appointment.participant.some(participant => participant.actor && isResourceReference('Patient', participant.actor))) {\n throw new Error('Appointment already has a patient participant')\n }\n\n // Validate patient reference\n await medplum.readReference(patientReference)\n\n appoinmentParameters.parameter?.push({\n name: 'operation',\n part: [\n { name: 'op', valueCode: 'add' },\n { name: 'path', valueString: '/participant/-' },\n {\n name: 'value',\n valueString: JSON.stringify({\n actor: patientReference,\n required: 'required',\n status: 'needs-action'\n } as AppointmentParticipant)\n }\n ]\n })\n }\n\n const transaction: Bundle = {\n resourceType: 'Bundle',\n type: 'transaction',\n entry: [{\n request: {\n method: 'PATCH',\n url: getReferenceString(appointment) as string\n },\n resource: appoinmentParameters\n }]\n }\n\n for (const slot of appointmentSlots) {\n transaction.entry?.push({\n request: {\n method: 'PATCH',\n url: getReferenceString(slot)\n },\n resource: {\n resourceType: 'Parameters',\n parameter: [{\n name: 'operation',\n part: [\n { name: 'op', valueCode: 'test' },\n { name: 'path', valueString: '/meta/versionId' },\n { name: 'value', valueString: JSON.stringify(slot.meta?.versionId) }\n ]\n }, {\n name: 'operation',\n part: [\n { name: 'op', valueCode: 'replace' },\n { name: 'path', valueString: '/status' },\n { name: 'value', valueString: JSON.stringify('busy') }\n ]\n }]\n }\n })\n }\n\n const result = await medplum.executeBatch(transaction)\n const ok = result.entry?.every(entry => entry.response?.outcome?.id === 'ok')\n\n if (ok) {\n return {\n resourceType: 'Bundle',\n type: 'searchset',\n entry: [findAppointmentBundleEntry(result)]\n }\n } else {\n return {\n resourceType: 'Bundle',\n type: 'searchset',\n entry: [{\n response: conflictResponse('Appointment update conflict')\n }]\n }\n }\n } else if (slots) {\n return createAppointment(\n medplum,\n slots,\n 'booked',\n 'busy',\n patientReference)\n }\n\n throw new Error('Appointment or slots not provided')\n}\n\nexport async function cancelAppointment (medplum: MedplumClient, appointmentReference: Reference<Appointment>): Promise<Bundle<Appointment>> {\n const appointment = await medplum.readReference(appointmentReference)\n\n if (appointment.status !== 'booked' && appointment.status !== 'pending') {\n throw new Error('Unexpected appointment status')\n }\n\n const transaction: Bundle = {\n resourceType: 'Bundle',\n type: 'transaction',\n entry: [{\n request: {\n method: 'PATCH',\n url: getReferenceString(appointmentReference) as string\n },\n resource: {\n resourceType: 'Parameters',\n parameter: [{\n name: 'operation',\n part: [\n { name: 'op', valueCode: 'test' },\n { name: 'path', valueString: '/meta/versionId' },\n { name: 'value', valueString: JSON.stringify(appointment.meta?.versionId) }\n ]\n }, {\n name: 'operation',\n part: [\n { name: 'op', valueCode: 'replace' },\n { name: 'path', valueString: '/status' },\n { name: 'value', valueString: JSON.stringify('cancelled') }\n ]\n }]\n }\n }]\n }\n\n if (appointment.slot) {\n for (const slot of appointment.slot) {\n transaction.entry?.push({\n request: {\n method: 'DELETE',\n url: getReferenceString(slot) as string\n }\n })\n }\n }\n\n const result = await medplum.executeBatch(transaction)\n const ok = result.entry?.every(entry => entry.response?.outcome?.id === 'ok')\n\n if (ok) {\n return {\n resourceType: 'Bundle',\n type: 'searchset',\n entry: [findAppointmentBundleEntry(result)]\n }\n } else {\n return {\n resourceType: 'Bundle',\n type: 'searchset',\n entry: [{\n response: conflictResponse('Appointment update conflict')\n }]\n }\n }\n}\n\nasync function createAppointment (\n medplum: MedplumClient,\n slots: Slot[],\n appoinmentStatus: Appointment['status'],\n slotStatus: Slot['status'],\n patientReference?: Reference<Patient>): Promise<Bundle<Appointment>> {\n const [first, ...rest] = slots\n\n for (const slot of rest) {\n if (first.start !== slot.start || first.end !== slot.end) {\n throw new Error('Slot dates do not match')\n }\n }\n\n const schedules = await medplum.searchResources('Schedule', {\n _id: slots.map(slot => getReferenceString(slot.schedule)).join(','),\n active: true\n })\n\n for (const slot of slots) {\n if (!schedules.some(schedule => getReferenceString(schedule) === getReferenceString(slot.schedule))) {\n throw new Error('Active schedule for slot not found')\n }\n }\n\n if (patientReference) {\n // Validate patient reference\n await medplum.readReference(patientReference)\n }\n\n const transaction: Bundle = {\n resourceType: 'Bundle',\n type: 'transaction',\n entry: []\n }\n\n for (const slot of slots) {\n const parameters = new URLSearchParams()\n\n parameters.set('schedule', getReferenceString(slot.schedule) as string)\n parameters.set('status', 'busy,busy-unavailable,busy-tentative')\n parameters.set('start', 'lt' + formatISO(slot.end))\n parameters.set('end', 'gt' + formatISO(slot.start))\n\n slot.id = generateId()\n\n transaction.entry?.push({\n fullUrl: 'urn:uuid:' + slot.id,\n request: {\n method: 'POST',\n url: 'Slot',\n ifNoneExist: parameters.toString()\n },\n resource: {\n resourceType: 'Slot',\n status: slotStatus,\n start: slot.start,\n end: slot.end,\n schedule: slot.schedule\n }\n })\n }\n\n const participants = new Map<string, AppointmentParticipant>()\n\n if (patientReference) {\n participants.set(getReferenceString(patientReference) as string, {\n actor: patientReference,\n required: 'required',\n status: 'needs-action'\n })\n }\n\n for (const schedule of schedules) {\n for (const actor of schedule.actor) {\n participants.set(getReferenceString(actor) as string, {\n actor,\n required: 'required',\n status: isResourceReference('Device', actor) || isResourceReference('Location', actor) ? 'accepted' : 'needs-action'\n })\n }\n }\n\n transaction.entry?.push({\n request: {\n method: 'POST',\n url: 'Appointment'\n },\n resource: {\n resourceType: 'Appointment',\n status: appoinmentStatus,\n start: first.start,\n end: first.end,\n participant: Array.from(participants.values()),\n slot: slots.map(slot => {\n return { reference: 'urn:uuid:' + slot.id }\n })\n }\n })\n\n const result = await medplum.executeBatch(transaction)\n const created = result.entry?.filter(entry => entry.response?.outcome?.id === 'created')\n\n if (created?.length !== slots.length + 1) {\n // Not all resources were created. Using ifNoneExists does not fail the transaction,\n // it returns an existing resource if it matches the parameters.\n // Try to delete all created resources by best effort.\n if (created?.length) {\n await medplum.executeBatch({\n resourceType: 'Bundle',\n type: 'batch',\n entry: created.map(entry => {\n return {\n request: {\n method: 'DELETE',\n url: getReferenceString(entry.resource as Resource) as string\n }\n }\n })\n })\n }\n\n return {\n resourceType: 'Bundle',\n type: 'searchset',\n entry: [{\n response: conflictResponse('Requested time slots not available'),\n resource: {\n resourceType: 'Appointment',\n status: 'cancelled',\n participant: []\n }\n }]\n }\n } else {\n return {\n resourceType: 'Bundle',\n type: 'searchset',\n entry: [findAppointmentBundleEntry(result)]\n }\n }\n}\n\nfunction findAppointmentBundleEntry (bundle: Bundle): BundleEntry<Appointment> {\n const appointmentEntry = bundle.entry?.find((entry): entry is BundleEntry<Appointment> => isResource(entry.resource, 'Appointment'))\n\n if (!appointmentEntry) {\n throw new Error('Bundle does not contain appointment')\n }\n\n return appointmentEntry\n}\n\nfunction conflictResponse (message: string): BundleEntryResponse {\n return {\n outcome: {\n resourceType: 'OperationOutcome',\n issue: [{\n severity: 'error',\n code: 'conflict',\n diagnostics: message\n }]\n },\n status: '409'\n }\n}\n"]}
@@ -0,0 +1,58 @@
1
+ import { ActivityDefinition, CodeableConcept, Duration, Schedule, Timing } from '@medplum/fhirtypes';
2
+ import { REQUIRES_PRACTITIONER_EXTENSION_URL, SCHEDULING_PARAMETERS_EXTENSION_URL } from '../constants';
3
+ export type ServiceTypeSchedulingParameter = {
4
+ url: 'serviceType';
5
+ valueCodeableConcept: CodeableConcept;
6
+ };
7
+ export type AvailabilitySchedulingParameter = {
8
+ url: 'availability';
9
+ valueTiming: Timing;
10
+ };
11
+ export type DurationSchedulingParameter = {
12
+ url: 'duration';
13
+ valueDuration: Duration;
14
+ };
15
+ export type BufferBeforeSchedulingParameter = {
16
+ url: 'bufferBefore';
17
+ valueDuration: Duration;
18
+ };
19
+ export type BufferAfterSchedulingParameter = {
20
+ url: 'bufferAfter';
21
+ valueDuration: Duration;
22
+ };
23
+ export type AlignmentIntervalSchedulingParameter = {
24
+ url: 'alignmentInterval';
25
+ valueDuration: Duration;
26
+ };
27
+ export type AlignmentOffsetSchedulingParameter = {
28
+ url: 'alignmentOffset';
29
+ valueDuration: Duration;
30
+ };
31
+ export type BookingLimitSchedulingParameter = {
32
+ url: 'bookingLimit';
33
+ valueTiming: Timing;
34
+ };
35
+ export type SchedulingParameter = ServiceTypeSchedulingParameter | AvailabilitySchedulingParameter | DurationSchedulingParameter | BufferBeforeSchedulingParameter | BufferAfterSchedulingParameter | AlignmentIntervalSchedulingParameter | AlignmentOffsetSchedulingParameter | BookingLimitSchedulingParameter;
36
+ export type SchedulingParametersExtension = {
37
+ url: typeof SCHEDULING_PARAMETERS_EXTENSION_URL;
38
+ extension: SchedulingParameter[];
39
+ };
40
+ export type WithSchedulingParameters<T extends ActivityDefinition | Schedule> = T & {
41
+ extension: SchedulingParametersExtension[];
42
+ };
43
+ export type RequiresPractitionerExtension = {
44
+ url: typeof REQUIRES_PRACTITIONER_EXTENSION_URL;
45
+ valueBoolean: boolean;
46
+ };
47
+ export type WithRequiresPractitioner<T extends ActivityDefinition> = T & {
48
+ extension: RequiresPractitionerExtension[];
49
+ };
50
+ export type FuegoExtension = SchedulingParametersExtension | RequiresPractitionerExtension;
51
+ export type ExtensionUrl = FuegoExtension['url'];
52
+ export type ExtractExtension<T extends ExtensionUrl> = Extract<FuegoExtension, {
53
+ url: T;
54
+ }>;
55
+ export type WithSchedulingParametersAndRequiresPractitioner<T extends ActivityDefinition> = T & {
56
+ extension: FuegoExtension[];
57
+ };
58
+ //# sourceMappingURL=extension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../../src/scheduling/extension.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,MAAM,EACP,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,mCAAmC,EACnC,mCAAmC,EACpC,MAAM,cAAc,CAAA;AAErB,MAAM,MAAM,8BAA8B,GAAG;IAC3C,GAAG,EAAE,aAAa,CAAC;IACnB,oBAAoB,EAAE,eAAe,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,GAAG,EAAE,cAAc,CAAC;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,GAAG,EAAE,UAAU,CAAC;IAChB,aAAa,EAAE,QAAQ,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,GAAG,EAAE,cAAc,CAAC;IACpB,aAAa,EAAE,QAAQ,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG;IAC3C,GAAG,EAAE,aAAa,CAAC;IACnB,aAAa,EAAE,QAAQ,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,oCAAoC,GAAG;IACjD,GAAG,EAAE,mBAAmB,CAAC;IACzB,aAAa,EAAE,QAAQ,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,kCAAkC,GAAG;IAC/C,GAAG,EAAE,iBAAiB,CAAC;IACvB,aAAa,EAAE,QAAQ,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,GAAG,EAAE,cAAc,CAAC;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,8BAA8B,GAC9D,+BAA+B,GAC/B,2BAA2B,GAC3B,+BAA+B,GAC/B,8BAA8B,GAC9B,oCAAoC,GACpC,kCAAkC,GAClC,+BAA+B,CAAA;AAEjC,MAAM,MAAM,6BAA6B,GAAG;IAC1C,GAAG,EAAE,OAAO,mCAAmC,CAAC;IAChD,SAAS,EAAE,mBAAmB,EAAE,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,wBAAwB,CAAC,CAAC,SAAS,kBAAkB,GAAG,QAAQ,IAAI,CAAC,GAAG;IAClF,SAAS,EAAE,6BAA6B,EAAE,CAAA;CAC3C,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,GAAG,EAAE,OAAO,mCAAmC,CAAC;IAChD,YAAY,EAAE,OAAO,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,wBAAwB,CAAC,CAAC,SAAS,kBAAkB,IAAI,CAAC,GAAG;IACvE,SAAS,EAAE,6BAA6B,EAAE,CAAA;CAC3C,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,6BAA6B,GAAG,6BAA6B,CAAA;AAC1F,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;AAChD,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,YAAY,IAAI,OAAO,CAAC,cAAc,EAAE;IAAE,GAAG,EAAE,CAAC,CAAA;CAAE,CAAC,CAAA;AAE1F,MAAM,MAAM,+CAA+C,CAAC,CAAC,SAAS,kBAAkB,IAAI,CAAC,GAAG;IAC9F,SAAS,EAAE,cAAc,EAAE,CAAA;CAC5B,CAAA"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.js","sourceRoot":"","sources":["../../src/scheduling/extension.ts"],"names":[],"mappings":"","sourcesContent":["import {\n ActivityDefinition,\n CodeableConcept,\n Duration,\n Schedule,\n Timing\n} from '@medplum/fhirtypes'\nimport {\n REQUIRES_PRACTITIONER_EXTENSION_URL,\n SCHEDULING_PARAMETERS_EXTENSION_URL\n} from '../constants'\n\nexport type ServiceTypeSchedulingParameter = {\n url: 'serviceType',\n valueCodeableConcept: CodeableConcept\n}\n\nexport type AvailabilitySchedulingParameter = {\n url: 'availability',\n valueTiming: Timing\n}\n\nexport type DurationSchedulingParameter = {\n url: 'duration',\n valueDuration: Duration\n}\n\nexport type BufferBeforeSchedulingParameter = {\n url: 'bufferBefore',\n valueDuration: Duration\n}\n\nexport type BufferAfterSchedulingParameter = {\n url: 'bufferAfter',\n valueDuration: Duration\n}\n\nexport type AlignmentIntervalSchedulingParameter = {\n url: 'alignmentInterval',\n valueDuration: Duration\n}\n\nexport type AlignmentOffsetSchedulingParameter = {\n url: 'alignmentOffset',\n valueDuration: Duration\n}\n\nexport type BookingLimitSchedulingParameter = {\n url: 'bookingLimit',\n valueTiming: Timing\n}\n\nexport type SchedulingParameter = ServiceTypeSchedulingParameter |\n AvailabilitySchedulingParameter |\n DurationSchedulingParameter |\n BufferBeforeSchedulingParameter |\n BufferAfterSchedulingParameter |\n AlignmentIntervalSchedulingParameter |\n AlignmentOffsetSchedulingParameter |\n BookingLimitSchedulingParameter\n\nexport type SchedulingParametersExtension = {\n url: typeof SCHEDULING_PARAMETERS_EXTENSION_URL,\n extension: SchedulingParameter[]\n}\n\nexport type WithSchedulingParameters<T extends ActivityDefinition | Schedule> = T & {\n extension: SchedulingParametersExtension[]\n}\n\nexport type RequiresPractitionerExtension = {\n url: typeof REQUIRES_PRACTITIONER_EXTENSION_URL,\n valueBoolean: boolean\n}\n\nexport type WithRequiresPractitioner<T extends ActivityDefinition> = T & {\n extension: RequiresPractitionerExtension[]\n}\n\nexport type FuegoExtension = SchedulingParametersExtension | RequiresPractitionerExtension\nexport type ExtensionUrl = FuegoExtension['url']\nexport type ExtractExtension<T extends ExtensionUrl> = Extract<FuegoExtension, { url: T }>\n\nexport type WithSchedulingParametersAndRequiresPractitioner<T extends ActivityDefinition> = T & {\n extension: FuegoExtension[]\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { MedplumClient } from '@medplum/core';
2
+ import { ActivityDefinition, Bundle, CodeableConcept, Device, Extension, ExtractResource, Reference, ResourceType, Slot } from '@medplum/fhirtypes';
3
+ import { ExtensionUrl, ExtractExtension } from './extension';
4
+ export interface FindAvailableSlotsArguments {
5
+ mainActivityDefinition: ActivityDefinition;
6
+ addonActivityDefinitions: ActivityDefinition[];
7
+ devices: Device[];
8
+ start: Date;
9
+ end?: Date;
10
+ }
11
+ export declare function findAvailableSlots(medplum: MedplumClient, options: FindAvailableSlotsArguments): Promise<Bundle<Slot>>;
12
+ export declare function isExtension<U extends ExtensionUrl>(url: U, extension: Extension): extension is ExtractExtension<U>;
13
+ export declare function isResourceReference<RT extends ResourceType>(resourceType: RT, reference: Reference): reference is Reference<ExtractResource<RT>>;
14
+ export declare function codeableConceptEqual(a: CodeableConcept, b: CodeableConcept): boolean;
15
+ //# sourceMappingURL=slot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot.d.ts","sourceRoot":"","sources":["../../src/scheduling/slot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,aAAa,EAAQ,MAAM,eAAe,CAAA;AACxF,OAAO,EACL,kBAAkB,EAClB,MAAM,EAEN,eAAe,EACf,MAAM,EAEN,SAAS,EACT,eAAe,EACf,SAAS,EACT,YAAY,EAEZ,IAAI,EAEL,MAAM,oBAAoB,CAAA;AAuB3B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAsD,MAAM,aAAa,CAAA;AAEhH,MAAM,WAAW,2BAA2B;IAC1C,sBAAsB,EAAE,kBAAkB,CAAA;IAC1C,wBAAwB,EAAE,kBAAkB,EAAE,CAAA;IAC9C,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,CAAC,EAAE,IAAI,CAAA;CACX;AA8FD,wBAAsB,kBAAkB,CAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAuG7H;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,EAAG,GAAG,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,GAAG,SAAS,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAEnH;AAED,wBAAgB,mBAAmB,CAAC,EAAE,SAAS,YAAY,EACzD,YAAY,EAAE,EAAE,EAChB,SAAS,EAAE,SAAS,GAAG,SAAS,IAAI,SAAS,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAEnE;AAED,wBAAgB,oBAAoB,CAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,eAAe,GAAG,OAAO,CAQrF"}