@medplum/react-scheduling 5.1.38 → 5.1.40

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.
@@ -1,6 +1,8 @@
1
1
  import type { Appointment } from '@medplum/fhirtypes';
2
+ import type { Bundle } from '@medplum/fhirtypes';
2
3
  import type { Coding } from '@medplum/fhirtypes';
3
4
  import type { Dereference } from '@medplum/core';
5
+ import type { Extension } from '@medplum/fhirtypes';
4
6
  import type { HealthcareService } from '@medplum/fhirtypes';
5
7
  import type { HealthcareServiceAvailableTime } from '@medplum/fhirtypes';
6
8
  import type { JSX } from 'react';
@@ -9,8 +11,8 @@ import type { MantineThemeColors } from '@mantine/core';
9
11
  import type { MedplumClient } from '@medplum/core';
10
12
  import type { OperationOutcome } from '@medplum/fhirtypes';
11
13
  import type { Patient } from '@medplum/fhirtypes';
14
+ import type { ReactNode } from 'react';
12
15
  import type { Reference } from '@medplum/fhirtypes';
13
- import type { Resource } from '@medplum/fhirtypes';
14
16
  import type { Schedule } from '@medplum/fhirtypes';
15
17
  import type { SchedulingRequirement } from '@medplum/core';
16
18
  import type { Slot } from '@medplum/fhirtypes';
@@ -19,8 +21,8 @@ import type { WithId } from '@medplum/core';
19
21
 
20
22
  /**
21
23
  * One way of holding an appointment: a set of actors whose schedules `$find`
22
- * intersects in a single request. A role contributes as many actors as were
23
- * chosen for it, since everything chosen attends.
24
+ * intersects in a single request. Each requirement contributes exactly one
25
+ * actor, since a row is satisfied by any one of its alternatives.
24
26
  */
25
27
  export declare interface ActorCombination {
26
28
  /** Matches `getActorGroupKey` of the appointments offered for these actors. */
@@ -31,26 +33,70 @@ export declare interface ActorCombination {
31
33
  }
32
34
 
33
35
  /**
34
- * What an appointment is being asked for: the schedules chosen, per actor type.
35
- * Everything named attends.
36
+ * One thing an appointment needs, of a single actor type.
37
+ *
38
+ * The candidates in it are alternatives: a row naming two
39
+ * providers asks for *either* of them rather than both.
36
40
  */
37
- export declare type ActorSelections = Partial<Record<SchedulingActorType, readonly ScheduleCandidate[]>>;
41
+ export declare interface ActorRequirement {
42
+ readonly id: string;
43
+ readonly candidates: readonly ScheduleCandidate[];
44
+ }
45
+
46
+ /**
47
+ * What an appointment is being asked for, per actor type.
48
+ *
49
+ * Each actor type holds a list of requirements, and the two directions read
50
+ * differently: requirements are ANDed, so two provider rows ask for two
51
+ * providers, while the candidates within one row are ORed.
52
+ */
53
+ export declare type ActorSelections = Partial<Record<SchedulingActorType, readonly ActorRequirement[]>>;
38
54
 
39
55
  export declare function addDays(date: Date, days: number): Date;
40
56
 
41
57
  /**
42
- * Chooses the actors an appointment is held on, for one actor type.
58
+ * Chooses which actors of one type can hold an appointment.
43
59
  *
44
- * Everything chosen attends: `$find` intersects the schedules behind them,
45
- * so naming a second actor narrows the times to the ones both are free for.
60
+ * The names in one of these fields are **alternatives** to each other.
61
+ * Several actors that all have to attend go in a field each - see {@link AppointmentActorSelections},
62
+ * which arranges these into rows.
46
63
  *
47
- * Schedules are searched for as the name is typed (via `AsyncAutocomplete`).
64
+ * The results are `Schedule` resources, bound to the actors that can hold the appointment.
48
65
  *
49
66
  * @param props - The React props.
50
67
  * @returns The field for one actor type.
51
68
  */
52
69
  export declare function AppointmentActorSelect(props: AppointmentActorSelectProps): JSX.Element;
53
70
 
71
+ /**
72
+ * Asks what an appointment needs by actor type (e.g., provider, room, device).
73
+ *
74
+ * Each actor type gets a stack of rows. **The rows are ANDed and the names
75
+ * within a row are ORed**: two provider rows ask for two providers, while two
76
+ * names in one row ask for either of them.
77
+ *
78
+ * A row is one alternative set, so it is also one dimension of the search:
79
+ * `getActorCombinations` takes one name from each row, and every way of doing
80
+ * that is one `$find` request.
81
+ *
82
+ * @param props - The React props.
83
+ * @returns One group of rows per bookable actor type.
84
+ */
85
+ export declare function AppointmentActorSelections(props: AppointmentActorSelectionsProps): JSX.Element;
86
+
87
+ export declare interface AppointmentActorSelectionsProps {
88
+ /** What the appointment is being asked for so far, per actor type. */
89
+ readonly value: ActorSelections;
90
+ /** The service being booked. Nothing is offered until it resolves. */
91
+ readonly service: Reference<HealthcareService> | WithId<HealthcareService> | undefined;
92
+ /** The site being booked at. Actors sited elsewhere are left out. */
93
+ readonly location?: Reference<Location_2> | WithId<Location_2>;
94
+ readonly disabled?: boolean;
95
+ /** What is wrong with a type's rows, shown under them. None by default. */
96
+ readonly errors?: Partial<Record<BookableActorType, string>>;
97
+ readonly onChange: (selections: ActorSelections) => void;
98
+ }
99
+
54
100
  export declare interface AppointmentActorSelectProps {
55
101
  readonly actorType: BookableActorType;
56
102
  /** The service being booked. Nothing is offered until it resolves. */
@@ -65,19 +111,25 @@ export declare interface AppointmentActorSelectProps {
65
111
  readonly onChange: (candidates: readonly ScheduleCandidate[]) => void;
66
112
  readonly error?: string;
67
113
  readonly disabled?: boolean;
114
+ /** Names the field. Defaults to the actor type's own label. */
115
+ readonly label?: ReactNode;
116
+ /** The line under the field. None by default. */
117
+ readonly description?: ReactNode;
118
+ /** What the empty field invites. Defaults to searching the actor type by name. */
119
+ readonly placeholder?: string;
120
+ /** Whether the field must be answered. Defaults to whether its actor type is required. */
121
+ readonly required?: boolean;
122
+ /** Whether to mark the field with an asterisk. Defaults to `required`. */
123
+ readonly withAsterisk?: boolean;
68
124
  }
69
125
 
70
126
  /** What a booking wrote, as `Appointment/$book` returned it. */
71
- export declare interface AppointmentBooking {
72
- readonly appointment: WithId<Appointment>;
73
- /** The times reserved for it, one per schedule it is held on. */
74
- readonly slots: readonly WithId<Slot>[];
75
- }
127
+ export declare type AppointmentBooking = AppointmentWrite;
76
128
 
77
129
  /**
78
130
  * The booking form, writing the booking itself.
79
131
  *
80
- * Wraps {@link AppointmentProposalForm}: posts `Appointment/$book`, announces the
132
+ * Wraps {@link AppointmentProposalForm}: writes the booking, announces the
81
133
  * appointment and every time it reserved so views reading them refresh, then
82
134
  * reports what was written through `onBooked` — the only required prop.
83
135
  *
@@ -88,7 +140,7 @@ export declare interface AppointmentBooking {
88
140
  */
89
141
  export declare function AppointmentBookingForm(props: AppointmentBookingFormProps): JSX.Element;
90
142
 
91
- export declare interface AppointmentBookingFormProps extends Omit<AppointmentProposalFormProps, 'onBook'> {
143
+ export declare interface AppointmentBookingFormProps extends Omit<AppointmentProposalFormProps, 'onSubmit' | 'mode' | 'ignoreAppointment'> {
92
144
  /**
93
145
  * Called with what the booking wrote.
94
146
  *
@@ -147,19 +199,13 @@ export declare interface AppointmentOptionRowProps {
147
199
 
148
200
  /**
149
201
  * Gathers what a visit is held on, finds a time every one of them is free, and
150
- * hands the proposal out to be booked.
151
- *
152
- * One field per scheduling role, each searching the schedules bookable for the
153
- * chosen visit type. Everything named attends, because `$find` intersects their
154
- * schedules — so naming a second room narrows the times rather than widening them.
202
+ * hands the proposal out to be written.
155
203
  *
156
- * Only a time the search offered can be chosen: the field holding it accepts no
157
- * input, so nothing can be booked onto time nobody checked availability for.
158
- *
159
- * Writes nothing and announces nothing: `onBook` owns that. Mount this to do
204
+ * Writes nothing and announces nothing: `onSubmit` owns that. Mount this to do
160
205
  * something other than `$book` with the proposal — hold it through `$hold`, or
161
206
  * write it inside a transaction of your own. {@link AppointmentBookingForm} is the
162
- * one that books.
207
+ * one that books, and {@link AppointmentRescheduleForm} the one that moves a visit
208
+ * already on file.
163
209
  *
164
210
  * @param props - The React props.
165
211
  * @returns The form.
@@ -167,6 +213,8 @@ export declare interface AppointmentOptionRowProps {
167
213
  export declare function AppointmentProposalForm(props: AppointmentProposalFormProps): JSX.Element;
168
214
 
169
215
  export declare interface AppointmentProposalFormProps {
216
+ /** What the proposal is for. Defaults to booking a new visit. */
217
+ readonly mode?: AppointmentProposalMode;
170
218
  /** Pre-fills where the visit is, for a host that already knows. */
171
219
  readonly defaultLocation?: WithId<Location_2>;
172
220
  /** Pre-fills the visit type, for a deep link or a reschedule. */
@@ -174,9 +222,23 @@ export declare interface AppointmentProposalFormProps {
174
222
  /** Pre-fills who the visit is for, for a host launching from a patient's chart. */
175
223
  readonly defaultPatient?: WithId<Patient>;
176
224
  /**
177
- * The day the time search opens on. Defaults to today.
225
+ * Pre-fills who and what the visit is held on, for a host that already knows — the
226
+ * actors an appointment being moved is currently held on, say.
227
+ *
228
+ * Read once, at mount: the fields own what they are asked for afterwards.
229
+ */
230
+ readonly defaultSelections?: ActorSelections;
231
+ /**
232
+ * An appointment whose own times are not to count as taken.
178
233
  *
179
- * A day, not a time: only a time `$find` offered can be chosen.
234
+ * For a form searching on behalf of an appointment that already exists: without it,
235
+ * the time it holds blocks every search that keeps any of the actors holding it, and
236
+ * moving a visit to a different room at the same hour finds nothing.
237
+ */
238
+ readonly ignoreAppointment?: Reference<Appointment> | WithId<Appointment>;
239
+ /**
240
+ * The day the time search opens on, and the day a typed time starts out on.
241
+ * Defaults to today.
180
242
  */
181
243
  readonly defaultStart?: Date;
182
244
  /**
@@ -209,12 +271,78 @@ export declare interface AppointmentProposalFormProps {
209
271
  /** The ValueSet the diagnosis code field binds to. Defaults to the full ICD-10-CM value set. */
210
272
  readonly diagnosisBinding?: string;
211
273
  /**
212
- * Performs the booking with the proposal the form assembled.
274
+ * Writes the proposal the form assembled.
275
+ *
276
+ * Resolving marks the form written, so it stops offering to write until an answer
277
+ * changes; rejecting shows the reason as the write's refusal, every answer kept.
213
278
  *
214
- * Resolving marks the form booked, so it stops offering to book until an answer
215
- * changes; rejecting shows the reason as the booking's refusal, every answer kept.
279
+ * In `book` mode the proposal carries the patient and the visit type's required
280
+ * codes; in `reschedule` mode it is the time as `$find` offered it, untouched.
281
+ */
282
+ readonly onSubmit: (proposal: Appointment, options: BookOptions) => void | Promise<void>;
283
+ /** Extensions to put on every appointment this form books. */
284
+ readonly appointmentExtensions?: readonly Extension[];
285
+ /**
286
+ * Allows for manual entry of a time and length, bypassing the `$find` search and
287
+ * `$book` endpoint.
216
288
  */
217
- readonly onBook: (proposal: Appointment) => void | Promise<void>;
289
+ readonly canBypassSchedulingRules?: boolean;
290
+ }
291
+
292
+ /**
293
+ * What the proposal the form assembles is for, which decides what it asks for.
294
+ *
295
+ * `book` gathers everything a new visit is written with: who it is for, and whatever
296
+ * its visit type requires. `reschedule` gathers only what decides the time, because
297
+ * `Appointment/[id]/$reschedule` moves a visit already on file and writes nothing else
298
+ * about it — asking for details it would drop is asking for them under false pretences.
299
+ *
300
+ * The visit type goes the same way: in `reschedule` mode a `defaultService` is shown
301
+ * rather than asked for, since the operation is measured against it but will not change
302
+ * it. Without one it is still asked, because the search cannot run without a visit type.
303
+ */
304
+ export declare type AppointmentProposalMode = 'book' | 'reschedule';
305
+
306
+ /** What a reschedule wrote, as `Appointment/[id]/$reschedule` returned it. */
307
+ export declare type AppointmentReschedule = AppointmentWrite;
308
+
309
+ /**
310
+ * The form that moves a visit already on file, to another time or another set of actors.
311
+ *
312
+ * Wraps {@link AppointmentProposalForm}, opened on what the appointment is held on now
313
+ * and searching as though it were not: the time it occupies is the time it is being
314
+ * moved off, so `$find` is told to ignore it, which is what lets the same hour in a
315
+ * different room be found.
316
+ *
317
+ * Posts `Appointment/[id]/$reschedule`, announces the appointment, the times it gave up
318
+ * and the times it took, so views reading them refresh, then reports what was written
319
+ * through `onRescheduled`.
320
+ *
321
+ * Only the time and the actors are asked for. Everything else on the appointment — who
322
+ * it is for, its status, its visit type, whatever clinical detail it carries — is left
323
+ * exactly as it was, because that is all the operation will write. The visit type is
324
+ * shown rather than offered: `$reschedule` takes none, reading the one the appointment
325
+ * is on file for instead, since changing what a visit *is* would leave its required
326
+ * codes, its authorization, and anything applied at booking keyed to a type it no
327
+ * longer has.
328
+ *
329
+ * @param props - The React props.
330
+ * @returns The form.
331
+ */
332
+ export declare function AppointmentRescheduleForm(props: AppointmentRescheduleFormProps): JSX.Element;
333
+
334
+ export declare interface AppointmentRescheduleFormProps extends Omit<AppointmentProposalFormProps, 'onSubmit' | 'mode' | 'defaultService' | 'defaultSelections' | 'defaultPatient' | 'ignoreAppointment' | 'mrnSystem' | 'procedureBinding' | 'diagnosisBinding' | 'canBypassSchedulingRules' | 'appointmentExtensions'> {
335
+ /** The appointment being moved. */
336
+ readonly appointment: WithId<Appointment>;
337
+ /**
338
+ * Called with what the move wrote.
339
+ *
340
+ * The answers stay on screen and the form stops offering to move until one of them
341
+ * changes, so a host can keep this mounted without it writing twice. A failure here
342
+ * is logged rather than shown as a refusal: the appointment has already moved by the
343
+ * time it runs.
344
+ */
345
+ readonly onRescheduled: (reschedule: AppointmentReschedule) => void | Promise<void>;
218
346
  }
219
347
 
220
348
  /**
@@ -238,6 +366,8 @@ export declare interface AppointmentServiceSelectProps {
238
366
  /** A chosen site, which narrows the services on offer to the ones held there. */
239
367
  readonly location?: WithId<Location_2> | Reference<Location_2>;
240
368
  readonly label?: string;
369
+ readonly placeholder?: string;
370
+ readonly required?: boolean;
241
371
  readonly error?: string;
242
372
  readonly disabled?: boolean;
243
373
  }
@@ -246,7 +376,7 @@ export declare interface AppointmentServiceSelectProps {
246
376
  export declare interface AppointmentSlotGroup {
247
377
  /** Stable key derived from the actors, so React keys survive a refetch. */
248
378
  readonly key: string;
249
- readonly actors: readonly SchedulingActor[];
379
+ readonly actors: readonly SchedulingActorValue[];
250
380
  readonly durationMinutes: number;
251
381
  /** Sorted by start time. */
252
382
  readonly appointments: readonly Appointment[];
@@ -270,13 +400,22 @@ export declare interface AppointmentSlotGroupCardProps {
270
400
  readonly disabled?: boolean;
271
401
  }
272
402
 
403
+ /** What a scheduling operation wrote: the appointment, and the times it holds. */
404
+ export declare interface AppointmentWrite {
405
+ readonly appointment: WithId<Appointment>;
406
+ /** The times reserved for it, one set per schedule it is held on. */
407
+ readonly slots: readonly WithId<Slot>[];
408
+ }
409
+
273
410
  /**
274
411
  * Actor types whose schedules may be offered for booking, in the order they are
275
412
  * asked about.
276
413
  *
277
- * Each type becomes one question, because `$find` intersects the schedules it is
278
- * given: a time is offered only when the practitioner *and* the location *and*
279
- * the device are all free for it.
414
+ * Each type becomes its own question, because `$find` intersects the schedules it
415
+ * is given: a time is offered only when the practitioner *and* the location *and*
416
+ * the device are all free for it. Asking for *either* of two practitioners is a
417
+ * second request rather than a second schedule on the same one — see
418
+ * `getActorCombinations`.
280
419
  *
281
420
  * Does not include `PractitionerRole` to prevent double-booking a `Practitioner`
282
421
  * who holds multiple roles. A practitioner is booked on their `Practitioner`,
@@ -297,6 +436,12 @@ export declare interface BookingRequirementValues {
297
436
  readonly medicalNecessity: boolean;
298
437
  }
299
438
 
439
+ /** What `onBook` is told about the proposal it was handed. */
440
+ export declare interface BookOptions {
441
+ /** True when the time was typed (& unvalidated) rather than chosen from the search. */
442
+ readonly manual: boolean;
443
+ }
444
+
300
445
  export declare function Calendar(props: CalendarProps): JSX.Element;
301
446
 
302
447
  export declare interface CalendarProps {
@@ -325,6 +470,26 @@ declare interface CommonProps {
325
470
  readonly onCancel?: () => void;
326
471
  }
327
472
 
473
+ /**
474
+ * Counts the combinations the selections expand into, without building them.
475
+ *
476
+ * An upper bound rather than an exact count: the ones that would name the same
477
+ * actor twice are only recognised while building. Enough to decide whether the
478
+ * product is worth expanding at all.
479
+ *
480
+ * @param selections - What has been chosen.
481
+ * @returns How many ways there are of satisfying every row, or 0 when nothing
482
+ * has been chosen.
483
+ */
484
+ export declare function countActorCombinations(selections: ActorSelections): number;
485
+
486
+ /**
487
+ * Opens a new requirement row.
488
+ * @param candidates - What it starts out asking for. Empty by default.
489
+ * @returns The row.
490
+ */
491
+ export declare function createActorRequirement(candidates?: readonly ScheduleCandidate[]): ActorRequirement;
492
+
328
493
  /** The days a search covers. Either end may be open. */
329
494
  export declare interface DateRange {
330
495
  readonly start?: Date;
@@ -426,11 +591,17 @@ export declare interface FormatZonedTimeOptions {
426
591
  * Builds the sets of actors an appointment could be held on.
427
592
  *
428
593
  * One combination is one `$find` request: the schedules within it are
429
- * intersected, so its times are the times all of those actors are free.
594
+ * intersected, so its times are the times all of those actors are free. Rows are
595
+ * ANDed and the alternatives within a row are ORed, so the combinations are the
596
+ * product of the rows — one alternative taken from each.
597
+ *
598
+ * Ordered as an odometer with the last row turning fastest, so the first
599
+ * combination is every row's first pick and the rounds the search runs stay
600
+ * predictable.
430
601
  *
431
602
  * @param selections - What has been chosen.
432
- * @returns One combination holding every chosen actor, in role order, or an
433
- * empty list when nothing is chosen.
603
+ * @returns One combination per way of satisfying every row, or an empty list
604
+ * when nothing is chosen.
434
605
  */
435
606
  export declare function getActorCombinations(selections: ActorSelections): ActorCombination[];
436
607
 
@@ -449,16 +620,16 @@ export declare function getActorGroupKey(appointment: Appointment): string;
449
620
  export declare function getActorsKey(actors: readonly Reference[]): string;
450
621
 
451
622
  /**
452
- * Extracts the resource type from a reference to an actor.
623
+ * Extracts the resource type of an actor, given as itself or as a reference.
453
624
  *
454
625
  * Only supports references that may be used for Scheduling operations, which are
455
626
  * those that have a qualified `reference` attribute. The attribute must be present,
456
627
  * and may not be a reference to a "contained" resource (eg. `{ reference: "#cid" }`)
457
628
  *
458
- * @param reference - A Reference to a schedulable resource
459
- * @returns The type that the reference refers to.
629
+ * @param actor - A schedulable resource, or a Reference to one
630
+ * @returns The type of the actor.
460
631
  */
461
- export declare function getActorType(reference: SchedulingActor): SchedulingActorType;
632
+ export declare function getActorType(actor: SchedulingActorValue): SchedulingActorType;
462
633
 
463
634
  /**
464
635
  * Provides the label we use for scheduling resource types in this UI
@@ -468,6 +639,14 @@ export declare function getActorType(reference: SchedulingActor): SchedulingActo
468
639
  */
469
640
  export declare function getActorTypeLabel(resourceType: SchedulingActorType): string;
470
641
 
642
+ /**
643
+ * Get the actor(s) of an appointment.
644
+ * @param appointment - The proposed appointment.
645
+ * @param actorResources - Map of actor resources that have already been previously loaded.
646
+ * @returns List of actors. Resource if it's already loaded, reference otherwise.
647
+ */
648
+ export declare function getAppointmentActors(appointment: Appointment | undefined, actorResources?: ReadonlyMap<string, SchedulingActorResource>): SchedulingActorValue[];
649
+
471
650
  /**
472
651
  * Returns a key uniquely identifying a proposed appointment.
473
652
  *
@@ -547,28 +726,51 @@ export declare function getFindWindowError(range: DateRange): string | undefined
547
726
  /**
548
727
  * Returns the input type to use for a date or time field.
549
728
  *
550
- * JSDOM does not fire change events for `<input type="date">` or
551
- * `<input type="time">`, so tests get a plain text field, matching what
729
+ * JSDOM does not fire change events for `<input type="date">`, `<input type="time">`
730
+ * or `<input type="datetime-local">`, so tests get a plain text field, matching what
552
731
  * `DateTimeInput` does.
553
732
  *
554
733
  * @param type - The native input type to use outside of tests.
555
734
  * @returns The input type for the current environment.
556
735
  */
557
- export declare function getNativeInputType(type: 'date' | 'time'): string;
736
+ export declare function getNativeInputType(type: 'date' | 'time' | 'datetime-local'): string;
737
+
738
+ /**
739
+ * Returns every row across every actor type, in the order they are asked about.
740
+ * @param selections - What has been chosen.
741
+ * @returns The rows, empty ones included.
742
+ */
743
+ export declare function getRequirements(selections: ActorSelections): ActorRequirement[];
744
+
745
+ /**
746
+ * Flattens the loaded actor resources into a map, keyed by their reference.
747
+ * @param selections - What has been chosen.
748
+ * @returns The resource behind each chosen actor the search was able to include.
749
+ */
750
+ export declare function getSelectedActorResources(selections: ActorSelections): Map<string, SchedulingActorResource>;
558
751
 
559
752
  /**
560
- * Returns everything chosen, across actor types
753
+ * Returns everything chosen, across rows and actor types.
561
754
  * @param selections - What has been chosen.
562
- * @returns The chosen candidates in `BOOKABLE_ACTOR_TYPES` order
755
+ * @returns The chosen candidates in `BOOKABLE_ACTOR_TYPES` order, then row order.
563
756
  */
564
757
  export declare function getSelectedCandidates(selections: ActorSelections): ScheduleCandidate[];
565
758
 
566
759
  /**
567
760
  * Reports why the current selections cannot be searched, if they cannot.
568
761
  * @param selections - What has been chosen.
569
- * @returns A message to show the user, or undefined when the search can run.
762
+ * @returns The blocker to show the user, or undefined when the search can run.
570
763
  */
571
- export declare function getSelectionError(selections: ActorSelections): string | undefined;
764
+ export declare function getSelectionError(selections: ActorSelections): SelectionBlocker | undefined;
765
+
766
+ /**
767
+ * Finds the rows that cannot all be filled at once, when there are any. Specifically happens
768
+ * when two rows offer the same resource, so one resource cannot fill both halves of a visit.
769
+ * @param selections - What has been chosen.
770
+ * @returns The first type whose rows have no answer between them, or undefined when
771
+ * every type can be satisfied.
772
+ */
773
+ export declare function getUnsatisfiableRows(selections: ActorSelections): UnsatisfiableRows | undefined;
572
774
 
573
775
  /**
574
776
  * Search window for a calendar day in the given timezone, starting from now if that day is already underway.
@@ -587,9 +789,11 @@ export declare function getZonedDayRange(day: Date, timezone?: string): Required
587
789
  * @param searched - Days to list whether or not they offer anything, so a searched day
588
790
  * that came back empty still shows up rather than going missing. Read on the local
589
791
  * calendar, matching how a day is picked.
792
+ * @param actorResources - The actors' own resources, keyed by reference, for
793
+ * whichever of them the caller has already read. See {@link getAppointmentActors}.
590
794
  * @returns Days in ascending order, each holding its groups.
591
795
  */
592
- export declare function groupAppointmentsByDay(appointments: readonly Appointment[], timezone?: string, searched?: DateRange): AppointmentDay[];
796
+ export declare function groupAppointmentsByDay(appointments: readonly Appointment[], timezone?: string, searched?: DateRange, actorResources?: ReadonlyMap<string, SchedulingActorResource>): AppointmentDay[];
593
797
 
594
798
  /**
595
799
  * Whether every requirement a visit type names has been answered.
@@ -624,6 +828,15 @@ export declare function isRequirementAnswered(requirement: SchedulingRequirement
624
828
  */
625
829
  export declare function isViewerTimezone(timezone: string | undefined, viewer?: string): boolean;
626
830
 
831
+ /**
832
+ * The most alternatives worth expanding into requests at all.
833
+ *
834
+ * The search runs a few combinations at a time, so this is not the size of a
835
+ * round: it is the point past which the whole product is more than anyone is
836
+ * going to sit through, whatever order it is asked in.
837
+ */
838
+ export declare const MAX_ACTOR_COMBINATIONS = 100;
839
+
627
840
  /**
628
841
  * The longest window `Appointment/$find` accepts. Requests wider than this are
629
842
  * rejected outright, so callers have to keep their own date range inside it.
@@ -670,6 +883,14 @@ export declare interface MultiCalendarSource {
670
883
  */
671
884
  export declare function parseDayKey(key: string): Date;
672
885
 
886
+ /**
887
+ * Reads a native datetime input's value as an instant in a timezone.
888
+ * @param value - A `YYYY-MM-DDTHH:MM` value, as the input reports it.
889
+ * @param timezone - IANA timezone identifier. Defaults to the browser's.
890
+ * @returns The instant, or undefined while the value is not a complete date and time.
891
+ */
892
+ export declare function parseZonedDateTimeInput(value: string, timezone?: string): Date | undefined;
893
+
673
894
  /**
674
895
  * Reads a wall-clock time on a given day as an instant in a timezone.
675
896
  *
@@ -680,6 +901,18 @@ export declare function parseDayKey(key: string): Date;
680
901
  */
681
902
  export declare function parseZonedTime(day: Date, time: string, timezone?: string): Date | undefined;
682
903
 
904
+ /**
905
+ * Reads what a scheduling operation wrote out of the bundle it answers with.
906
+ *
907
+ * `$book` and `$reschedule` both answer with the appointment and every Slot they
908
+ * created, in one transaction bundle.
909
+ *
910
+ * @param written - The bundle the operation returned.
911
+ * @param operation - What was called, for the error a bundle without an appointment raises.
912
+ * @returns The appointment and the times reserved for it.
913
+ */
914
+ export declare function readAppointmentWrite(written: Bundle<WithId<Appointment> | WithId<Slot>>, operation: string): AppointmentWrite;
915
+
683
916
  /**
684
917
  * Actor types that must be chosen before a search can run.
685
918
  *
@@ -688,6 +921,35 @@ export declare function parseZonedTime(day: Date, time: string, timezone?: strin
688
921
  */
689
922
  export declare const REQUIRED_ACTOR_TYPES: ReadonlySet<SchedulingActorType>;
690
923
 
924
+ export declare interface RescheduleDefaults {
925
+ /** The visit type the appointment is on file under, where it records one. */
926
+ readonly service: WithId<HealthcareService> | undefined;
927
+ /** Who and what it is held on now, arranged into the rows a form asks for them in. */
928
+ readonly selections: ActorSelections;
929
+ /**
930
+ * Actors the visit is held on whose schedules cannot be offered back, named where they
931
+ * could be read.
932
+ *
933
+ * A Schedule that is inactive, no longer names the visit type, or is held on an actor
934
+ * this form does not book leaves its actors out of the rows above — and a move writes
935
+ * the actors it is given, so these are about to be dropped off the visit.
936
+ */
937
+ readonly droppedActors: readonly SchedulingActor[];
938
+ /**
939
+ * Set when the visit type, or a Slot or Schedule the visit is held on, could not be read.
940
+ *
941
+ * `$reschedule` reads those same references, so no move from here would be accepted.
942
+ */
943
+ readonly error: unknown;
944
+ /**
945
+ * True until the reads settle.
946
+ *
947
+ * A form reads its defaults once, at mount, so one mounted while this is true would
948
+ * open on nothing and never take them.
949
+ */
950
+ readonly loading: boolean;
951
+ }
952
+
691
953
  /**
692
954
  * Edits weekly availability for one visit service type, either as a Schedule's
693
955
  * override of the service hours or as the service's own default hours.
@@ -713,7 +975,7 @@ export declare type ScheduleAvailabilityEditorProps = ScheduleOverrideEditorProp
713
975
  export declare interface ScheduleCandidate {
714
976
  readonly schedule: WithId<Schedule>;
715
977
  /** The actor itself, when the search was able to include it. */
716
- readonly actorResource: WithId<Resource> | undefined;
978
+ readonly actorResource: SchedulingActorResource | undefined;
717
979
  }
718
980
 
719
981
  /**
@@ -734,7 +996,15 @@ export declare interface ScheduleOverrideEditorProps extends CommonProps {
734
996
  */
735
997
  export declare type SchedulingActor = Schedule['actor'][number];
736
998
 
737
- export declare type SchedulingActorType = Dereference<SchedulingActor>['resourceType'];
999
+ /** A loaded actor resource. */
1000
+ export declare type SchedulingActorResource = WithId<Dereference<SchedulingActor>>;
1001
+
1002
+ export declare type SchedulingActorType = SchedulingActorResource['resourceType'];
1003
+
1004
+ /**
1005
+ * An actor as a reference, or as the resource when it has already been read.
1006
+ */
1007
+ export declare type SchedulingActorValue = SchedulingActor | SchedulingActorResource;
738
1008
 
739
1009
  /**
740
1010
  * A data-coordination component pairing {@link CalendarsPanel} with {@link MultiCalendar}.
@@ -746,9 +1016,9 @@ export declare type SchedulingActorType = Dereference<SchedulingActor>['resource
746
1016
  * The form writes the booking and announces what it wrote, which is what puts the
747
1017
  * new appointment on the calendar beside it — a host supplies no data for any of it.
748
1018
  * What was written is reported through `onBooked`, for a host that wants to say so.
749
- * - Shows what is booked: clicking an appointment opens {@link AppointmentDetails} in a
750
- * drawer over the calendar, describing the visit and offering to cancel it. Cancelling
751
- * is what takes the time back off the calendar, again without a host supplying anything.
1019
+ * - Shows what is booked: clicking an appointment opens {@link AppointmentDetails} in the
1020
+ * same pane the booking form uses, describing the visit and offering to cancel or
1021
+ * reschedule it.
752
1022
  * - Highlights the time last chosen, wherever it was chosen: the click that opened the
753
1023
  * pane, then whatever the form's time search settles on, and nothing while the form
754
1024
  * holds no time. The calendar is never moved to reach it — a highlight off the week
@@ -767,11 +1037,32 @@ export declare interface SchedulingWorkspaceProps {
767
1037
  readonly diagnosisBinding?: string;
768
1038
  readonly onBooked?: (booking: AppointmentBooking) => void | Promise<void>;
769
1039
  readonly onCancelled?: (appointment: WithId<Appointment>) => void | Promise<void>;
1040
+ readonly onRescheduled?: (reschedule: AppointmentReschedule) => void | Promise<void>;
770
1041
  /**
771
1042
  * Overrides the value set the appointment detail view offers cancellation reasons
772
1043
  * from, for a host coding them against its own terminology.
773
1044
  */
774
1045
  readonly appointmentCancellationReasonValueSet?: string;
1046
+ /**
1047
+ * Lets the booking form take a typed time and length, placing a visit the scheduling
1048
+ * rules would refuse: over occupied or blocked time, past the configured capacity,
1049
+ * or at a time or length the visit type does not offer.
1050
+ *
1051
+ * Passing it draws the fields; it enforces nothing. Which users get it is the host
1052
+ * application's responsibility.
1053
+ *
1054
+ * Such a booking is sent as a transaction, so the appointment and its Slots commit
1055
+ * together on projects with the `transaction-bundles` feature enabled. Without it they
1056
+ * are applied as a plain batch, where an appointment that failed to write would leave
1057
+ * Slots holding no visit.
1058
+ * @see https://www.medplum.com/docs/fhir-datastore/fhir-batch-requests#batches-vs-transactions
1059
+ */
1060
+ readonly canBypassSchedulingRules?: boolean;
1061
+ /**
1062
+ * Extensions to put on every appointment booked from this workspace. See
1063
+ * {@link AppointmentProposalFormProps.appointmentExtensions}.
1064
+ */
1065
+ readonly appointmentExtensions?: readonly Extension[];
775
1066
  }
776
1067
 
777
1068
  /**
@@ -801,6 +1092,20 @@ export declare interface SearchScheduleCandidatesOptions {
801
1092
  readonly count?: number;
802
1093
  }
803
1094
 
1095
+ /** Why the current selections cannot be searched. */
1096
+ export declare interface SelectionBlocker {
1097
+ /** A whole sentence to show the user. */
1098
+ readonly message: string;
1099
+ readonly severity: SelectionBlockerSeverity;
1100
+ }
1101
+
1102
+ /**
1103
+ * Whether a blocker is a form that is not finished yet, or answers that cannot work.
1104
+ *
1105
+ * Only `invalid` is the user's to undo, so only it is worth showing as an error.
1106
+ */
1107
+ export declare type SelectionBlockerSeverity = 'incomplete' | 'invalid';
1108
+
804
1109
  /**
805
1110
  * Props for editing a service's own default hours, in place of any one calendar's override.
806
1111
  * @param schedule - Omitted, which is what selects this mode.
@@ -816,10 +1121,10 @@ export declare interface ServiceDefaultEditorProps extends CommonProps {
816
1121
  /**
817
1122
  * Immutably gives a Schedule its own hours for a HealthcareService, in place of the service default.
818
1123
  * Reads back through `getEffectiveAvailability`; to drop the calendar back to the default, clear the
819
- * parameter with `clearScheduleParameter(schedule, service, 'availability')` from `@medplum/core`.
1124
+ * parameter with `clearScheduleSchedulingParameter(schedule, service, 'availability')` from `@medplum/core`.
820
1125
  *
821
1126
  * Availability is the one parameter with a typed wrapper, because it is the only one that is not a single
822
- * `value[x]`: `bufferBefore` and the rest go through `setScheduleParameter` directly, already legible as
1127
+ * `value[x]`: `bufferBefore` and the rest go through `setScheduleSchedulingParameter` directly, already legible as
823
1128
  * `{ url: 'bufferBefore', valueDuration: { value: 10, unit: 'min' } }`. Availability is a repeating nested
824
1129
  * structure, so hand-building it at every call site would mean re-deriving the encoding.
825
1130
  *
@@ -852,6 +1157,16 @@ export declare type TimeOfDay = 'any' | 'morning' | 'afternoon';
852
1157
  */
853
1158
  export declare function toCodings(elements: readonly ValueSetExpansionContains[]): Coding[];
854
1159
 
1160
+ export declare function toScheduleCandidate(schedule: WithId<Schedule>, service: WithId<HealthcareService> | undefined, actors: Map<string, SchedulingActorResource>): ScheduleCandidate | undefined;
1161
+
1162
+ /** Rows that cannot all be filled at once, and where to say so. */
1163
+ export declare interface UnsatisfiableRows {
1164
+ /** The actor type whose rows have no answer between them. */
1165
+ readonly actorType: BookableActorType;
1166
+ /** What to do about it, to show against those rows. */
1167
+ readonly message: string;
1168
+ }
1169
+
855
1170
  /**
856
1171
  * Searches for the times an appointment could be held at.
857
1172
  *
@@ -872,6 +1187,14 @@ export declare interface UseProposedAppointmentsOptions {
872
1187
  readonly range: DateRange;
873
1188
  /** Times to ask for per combination. Defaults to 20. */
874
1189
  readonly count?: number;
1190
+ /**
1191
+ * An appointment whose own times are not to count as taken.
1192
+ *
1193
+ * For searching on behalf of an appointment that already exists: the times it holds
1194
+ * are the times it is being moved off, and left standing they would block every
1195
+ * search that keeps any of the actors it is held on.
1196
+ */
1197
+ readonly ignoreAppointment?: Reference<Appointment> | WithId<Appointment>;
875
1198
  }
876
1199
 
877
1200
  export declare interface UseProposedAppointmentsResult {
@@ -888,6 +1211,23 @@ export declare interface UseProposedAppointmentsResult {
888
1211
  readonly windowError: string | undefined;
889
1212
  }
890
1213
 
1214
+ /**
1215
+ * Reads what an appointment is currently held on, for a form that offers to move it.
1216
+ *
1217
+ * The visit type comes off `Appointment.serviceType`, which carries a reference to the
1218
+ * HealthcareService it was booked under; the actors come off the Slots it holds, which
1219
+ * name the Schedules — the appointment's own participants would not say which of an
1220
+ * actor's schedules is the one being moved off.
1221
+ *
1222
+ * An actor that cannot be read keeps its schedule, shown under the name the schedule
1223
+ * gives it: the move is written against the schedule, not the actor.
1224
+ *
1225
+ * @param appointment - The appointment being moved.
1226
+ * @returns Its visit type and actors, whether they are still being read, who a move
1227
+ * would drop off it, and why it could not be read, where it could not.
1228
+ */
1229
+ export declare function useRescheduleDefaults(appointment: WithId<Appointment>): RescheduleDefaults;
1230
+
891
1231
  /**
892
1232
  * Loads the Appointments for a set of schedules within a date range and keeps them live.
893
1233
  *