Repository navigation
HTML-882: allow creating appointment in edit mode in ScheduleAppoinme… #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,6 +99,10 @@ public class ScheduleAppointmentElement implements HtmlGeneratorElement, FormSub | |
| // Appointment fetched in VIEW/EDIT mode constructor | ||
| private Appointment existingAppointment; | ||
|
|
||
| // True when this element should render the scheduling form and handle submission: | ||
| // always in ENTER mode, and in EDIT mode when no appointment has been scheduled yet. | ||
| private boolean allowScheduling; | ||
|
|
||
| public ScheduleAppointmentElement(FormEntryContext context, Map<String, String> parameters) throws BadFormDesignException { | ||
| appointmentUuidConceptMapping = parameters.get("appointmentUuidConcept"); | ||
| tagControlId = parameters.get("controlId"); | ||
|
|
@@ -112,7 +116,10 @@ public ScheduleAppointmentElement(FormEntryContext context, Map<String, String> | |
| + appointmentUuidConceptMapping); | ||
| } | ||
|
|
||
| if (context.getMode() != Mode.ENTER) { | ||
| if (context.getMode() == Mode.ENTER) { | ||
| allowScheduling = true; | ||
| } | ||
| else { | ||
| // VIEW/EDIT: claim the UUID obs so HFE doesn't warn about an unmatched obs, | ||
| // then fetch the appointment for display in generateHtml(). | ||
| Obs uuidObs = StringUtils.isNotBlank(tagControlId) | ||
|
|
@@ -127,9 +134,14 @@ public ScheduleAppointmentElement(FormEntryContext context, Map<String, String> | |
| // appointments module unavailable | ||
| } | ||
| } | ||
| // EDIT mode with no previously-scheduled appointment: let the user schedule one now. | ||
| if (context.getMode() == Mode.EDIT && uuidObs == null) { | ||
| allowScheduling = true; | ||
| } | ||
|
Comment on lines
+148
to
+151
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes an appointment mandatory in EDIT mode rather than merely possible. I ran the same api-tests probe against both branches. On master, editing an encounter that carries no appointment-uuid obs submits cleanly. On this branch the identical edit comes back with seven errors (Location, Service, Appointment type, Appointment date, Appointment time, Duration, Provider) and never reaches Merged as is, that means adding My suggestion is to make scheduling opt-in whenever the mode is EDIT: turn the yes/no choice on regardless of the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thoughts @chibongho , this does need to be fixed if it does work as Daniel says
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this should be fixed. I'll make it so in EDIT mode, if no appointment has been scheduled yet, it defaults to "Don't schedule appointment" instead, regardless of the |
||
| } | ||
| else { | ||
| // ENTER mode: set up all widgets | ||
|
|
||
| if (allowScheduling) { | ||
| // set up all widgets | ||
| optional = "true".equalsIgnoreCase(parameters.get("optional")); | ||
| id = parameters.get("id"); | ||
| scheduleChoiceWidget = new TextFieldWidget(); | ||
|
|
@@ -269,7 +281,7 @@ public ScheduleAppointmentElement(FormEntryContext context, Map<String, String> | |
|
|
||
| @Override | ||
| public String generateHtml(FormEntryContext context) { | ||
| if (context.getMode() != Mode.ENTER) { | ||
| if (!allowScheduling) { | ||
| return generateViewEditHtml(); | ||
| } | ||
|
|
||
|
|
@@ -433,7 +445,7 @@ private String generateViewEditHtml() { | |
|
|
||
| @Override | ||
| public Collection<FormSubmissionError> validateSubmission(FormEntryContext context, HttpServletRequest submission) { | ||
| if (context.getMode() != Mode.ENTER) { | ||
| if (!allowScheduling) { | ||
| return Collections.emptyList(); | ||
| } | ||
|
|
||
|
|
@@ -499,8 +511,9 @@ public Collection<FormSubmissionError> validateSubmission(FormEntryContext conte | |
|
|
||
| @Override | ||
| public void handleSubmission(FormEntrySession session, HttpServletRequest submission) { | ||
| // EDIT mode is intentionally a no-op: the element does not update existing appointments. | ||
| if (session.getContext().getMode() != Mode.ENTER) { | ||
| // Only schedule when the form was rendered for scheduling (ENTER, or EDIT with no existing | ||
| // appointment). Otherwise this is a no-op: the element never updates an existing appointment. | ||
| if (!allowScheduling) { | ||
| return; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -233,6 +233,37 @@ public void handleSubmission_withoutControlId_shouldNotStampUuidObsWithFormPath( | |
| assertNull(obsToCreate.get(0).getFormFieldPath()); | ||
| } | ||
|
|
||
| // --- EDIT mode: scheduling allowed only when no appointment exists yet --- | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two exercise
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add EDIT mode tests in |
||
|
|
||
| @Test | ||
| public void handleSubmission_editMode_noExistingAppointment_shouldScheduleAppointment() throws Exception { | ||
| when(context.getMode()).thenReturn(Mode.EDIT); | ||
| when(context.removeExistingObs(concept, (Concept) null)).thenReturn(null); | ||
|
|
||
| ScheduleAppointmentElement element = new ScheduleAppointmentElement(context, enterParams()); | ||
|
|
||
| element.handleSubmission(session, buildRequest()); | ||
|
|
||
| assertEquals(1, actions.getObsToCreate().size()); | ||
| assertEquals(1, actions.getAppointmentsToCreate().size()); | ||
| } | ||
|
|
||
| @Test | ||
| public void handleSubmission_editMode_existingAppointment_shouldBeNoOp() throws Exception { | ||
| Obs obs = new Obs(); | ||
| obs.setValueText(APPT_UUID); | ||
| when(context.getMode()).thenReturn(Mode.EDIT); | ||
| when(context.removeExistingObs(concept, (Concept) null)).thenReturn(obs); | ||
| when(appointmentsService.getAppointmentByUuid(APPT_UUID)).thenReturn(new Appointment()); | ||
|
|
||
| ScheduleAppointmentElement element = new ScheduleAppointmentElement(context, enterParams()); | ||
|
|
||
| element.handleSubmission(session, new MockHttpServletRequest()); | ||
|
|
||
| assertEquals(0, actions.getObsToCreate().size()); | ||
| assertEquals(0, actions.getAppointmentsToCreate().size()); | ||
| } | ||
|
|
||
| // --- Helpers --- | ||
|
|
||
| private Map<String, String> params(String... kvs) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With a
controlIdset, this lookup only matches obs whoseformFieldPathcarries that control id, so an appointment recorded while the tag had nocontrolIdisn't found. Under the new rule,uuidObs == nullnow means "offer another one".I reproduced it in api-tests: seed encounter 101 with an appointment-uuid obs whose
formFieldPathis null (exactly whathandleSubmissionwrites when nocontrolIdis configured), then open it in EDIT withcontrolId="appt1"on the tag. Master renders the "No appointment scheduled" message, wrong but harmless. This branch renders the full scheduling form, and submitting it leaves the encounter with two appointment-uuid obs and the patient with two live appointments. The encounter then stops rendering at all, because two obs sharing a control id makegetObsFromExistingObsthrowIllegalStateException: Multiple obs are matching the control id 'appt1'in both VIEW and EDIT (that last part is framework behaviour, identical on master).Adding a
controlIdto an existing tag is a routine form edit, so I'd rather this didn't hinge on the two lookups agreeing. Before concluding that nothing was scheduled, check whether the encounter has any obs foruuidConceptat all (theexistingObsentry, read ahead of the remove) instead of trusting the controlId-scoped match. That also covers the mirror case, where acontrolIdgets dropped from a tag and the already-stampedformFieldPathmakesremoveExistingObsmiss.It takes a deliberate form change to trigger, so I don't think it has to hold up the merge if you'd rather split it out, but it's this PR that turns the mismatch from a cosmetic one into duplicate clinical data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, this problem happens when we first record an appointment in a form with no
controlId, then change the form so that the tag has acontrolId, and then if we EDIT the form, it doesn't know that the prior appointment has already been created.Given that we expect users to version bump the form if they make edits to it, and that this is also how
controlIdin the<obs>tag works (if we change the controlId of<obs>, prior obs recordings will not be matched properly onto the right field), I think this is fine? Let me know if you disagree.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with what @chibongho says above (the onus is on the implementer to upgrade the form version). Alternatively, since this is a new tag, we could just mandate that a controlId is used? (This is some that should be used for all data points, but it didn't exist back in the early days).