diff --git a/api-tests/pom.xml b/api-tests/pom.xml
index 7e231352a..786767c64 100644
--- a/api-tests/pom.xml
+++ b/api-tests/pom.xml
@@ -61,6 +61,10 @@
org.openmrs.module
providermanagement-api
+
+ org.bahmni.module
+ appointments-api
+
org.openmrs.test
openmrs-test
diff --git a/api-tests/src/test/java/org/openmrs/module/htmlformentry/AppointmentTagTest.java b/api-tests/src/test/java/org/openmrs/module/htmlformentry/AppointmentTagTest.java
new file mode 100644
index 000000000..676b73d35
--- /dev/null
+++ b/api-tests/src/test/java/org/openmrs/module/htmlformentry/AppointmentTagTest.java
@@ -0,0 +1,287 @@
+package org.openmrs.module.htmlformentry;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.openmrs.api.context.Context;
+import org.openmrs.module.appointments.model.Appointment;
+import org.openmrs.module.appointments.model.AppointmentStatus;
+import org.openmrs.module.appointments.service.AppointmentsService;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+public class AppointmentTagTest extends BaseHtmlFormEntryTest {
+
+ @Before
+ public void loadTestData() throws Exception {
+ executeVersionedDataSet("org/openmrs/module/htmlformentry/data/RegressionTest-data-openmrs-2.3.xml");
+ executeVersionedDataSet("org/openmrs/module/htmlformentry/data/appointmentCheckInTest.xml");
+ }
+
+ @Test
+ public void testAppointmentCheckInTag_shouldDisplayCheckboxesForScheduledAppointmentsForPatient() throws Exception {
+ new RegressionTestHelper() {
+
+ @Override
+ public String getFormName() {
+ return "appointmentCheckInForm";
+ }
+
+ @Override
+ public String[] widgetLabels() {
+ return new String[] { "Date:", "Location:", "Provider:", "Appointments:" };
+ }
+
+ @Override
+ public void testBlankFormHtml(String html) {
+ TestUtil.assertFuzzyContains("value=\"05f2ad92-1cc8-4cec-bf54-9cac0200746d\"/>", html);
+ TestUtil.assertFuzzyContains("2024-08-16, 13:00:00 - of Cos, Hippocrates - Never Never Land", html);
+ TestUtil.assertFuzzyContains("value=\"75504r42-3ca8-11e3-bf2b-0800271c1111\"/>", html);
+ TestUtil.assertFuzzyContains("2024-08-15, 12:00:00 - of Cos, Hippocrates - Xanadu", html);
+ }
+ }.run();
+ }
+
+ @Test
+ public void testAppointmentCheckInTag_shouldCheckPatientInForSingleAppointment() throws Exception {
+ final Date date = new Date();
+ new RegressionTestHelper() {
+
+ @Override
+ public String getFormName() {
+ return "appointmentCheckInForm";
+ }
+
+ @Override
+ public String[] widgetLabels() {
+ return new String[] { "Date:", "Location:", "Provider:", "Appointments:" };
+ }
+
+ @Override
+ public void setupRequest(MockHttpServletRequest request, Map widgets) {
+ request.addParameter(widgets.get("Date:"), dateAsString(date));
+ request.addParameter(widgets.get("Location:"), "2");
+ request.addParameter(widgets.get("Provider:"), "502");
+ request.addParameter(widgets.get("Appointments:"), "05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ }
+
+ @Override
+ public void testResults(SubmissionResults results) {
+ results.assertNoErrors();
+ results.assertEncounterCreated();
+ Appointment appointment1 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ Assert.assertEquals(AppointmentStatus.CheckedIn, appointment1.getStatus());
+ Assert.assertEquals(results.getEncounterCreated(),
+ appointment1.getFulfillingEncounters().stream().findFirst().get());
+
+ // just confirm the other appointment has not been checked in or linked to the encounter
+ Appointment appointment2 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("75504r42-3ca8-11e3-bf2b-0800271c1111");
+ Assert.assertEquals(AppointmentStatus.Scheduled, appointment2.getStatus());
+ Assert.assertEquals(0, appointment2.getFulfillingEncounters().size());
+ }
+ }.run();
+ }
+
+ @Test
+ public void testAppointmentCheckInTag_shouldCheckPatientInForMultipleAppointments() throws Exception {
+ final Date date = new Date();
+ new RegressionTestHelper() {
+
+ @Override
+ public String getFormName() {
+ return "appointmentCheckInForm";
+ }
+
+ @Override
+ public String[] widgetLabels() {
+ return new String[] { "Date:", "Location:", "Provider:", "Appointments:" };
+ }
+
+ @Override
+ public void setupRequest(MockHttpServletRequest request, Map widgets) {
+ request.addParameter(widgets.get("Date:"), dateAsString(date));
+ request.addParameter(widgets.get("Location:"), "2");
+ request.addParameter(widgets.get("Provider:"), "502");
+ request.addParameter(widgets.get("Appointments:"), "05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ request.addParameter(widgets.get("Appointments:").replace("_1", "_2"),
+ "75504r42-3ca8-11e3-bf2b-0800271c1111");
+ }
+
+ @Override
+ public void testResults(SubmissionResults results) {
+ results.assertNoErrors();
+ results.assertEncounterCreated();
+
+ Appointment appointment1 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ Assert.assertEquals(AppointmentStatus.CheckedIn, appointment1.getStatus());
+ Assert.assertEquals(results.getEncounterCreated(),
+ appointment1.getFulfillingEncounters().stream().findFirst().get());
+
+ Appointment appointment2 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("75504r42-3ca8-11e3-bf2b-0800271c1111");
+ Assert.assertEquals(AppointmentStatus.CheckedIn, appointment2.getStatus());
+ Assert.assertEquals(results.getEncounterCreated(),
+ appointment2.getFulfillingEncounters().stream().findFirst().get());
+ }
+ }.run();
+ }
+
+ @Test
+ public void testAppointmentCheckInTag_editShouldDisassociateEncounterFromAppointmentAndAssociateNewEncounter()
+ throws Exception {
+ final Date date = new Date();
+ new RegressionTestHelper() {
+
+ @Override
+ public String getFormName() {
+ return "appointmentCheckInForm";
+ }
+
+ @Override
+ public String[] widgetLabels() {
+ return new String[] { "Date:", "Location:", "Provider:", "Appointments:" };
+ }
+
+ @Override
+ public void setupRequest(MockHttpServletRequest request, Map widgets) {
+ request.addParameter(widgets.get("Date:"), dateAsString(date));
+ request.addParameter(widgets.get("Location:"), "2");
+ request.addParameter(widgets.get("Provider:"), "502");
+ request.addParameter(widgets.get("Appointments:"), "05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ }
+
+ @Override
+ public boolean doEditEncounter() {
+ return true;
+ }
+
+ @Override
+ public String[] widgetLabelsForEdit() {
+ return new String[] { "Appointments:" };
+ }
+
+ public void setupEditRequest(MockHttpServletRequest request, Map widgets) {
+ request.addParameter(widgets.get("Appointments:"), "");
+ request.addParameter(widgets.get("Appointments:").replace("_1", "_2"),
+ "75504r42-3ca8-11e3-bf2b-0800271c1111");
+ }
+
+ @Override
+ public void testResults(SubmissionResults results) {
+ results.assertNoErrors();
+ results.assertEncounterCreated();
+
+ // sanity check
+ Appointment appointment1 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ Assert.assertEquals(AppointmentStatus.CheckedIn, appointment1.getStatus());
+ Assert.assertEquals(results.getEncounterCreated(),
+ appointment1.getFulfillingEncounters().stream().findFirst().get());
+
+ Appointment appointment2 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("75504r42-3ca8-11e3-bf2b-0800271c1111");
+ Assert.assertEquals(AppointmentStatus.Scheduled, appointment2.getStatus());
+ Assert.assertEquals(0, appointment2.getFulfillingEncounters().size());
+ }
+
+ // confirm that the associated appointment is now checked by default
+ @Override
+ public void testEditFormHtml(String html) {
+ TestUtil.assertFuzzyContains("value=\"05f2ad92-1cc8-4cec-bf54-9cac0200746d\" checked=\"true\"/>", html);
+ }
+
+ @Override
+ public void testEditedResults(SubmissionResults results) {
+ results.assertNoErrors();
+ results.assertEncounterCreated();
+
+ Appointment appointment1 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ Assert.assertEquals(AppointmentStatus.CheckedIn, appointment1.getStatus()); // Note that we are NOT changing the status back to Scheduled
+ // TODO debug after testing IRL
+ Assert.assertEquals(0, appointment1.getFulfillingEncounters().size()); // but encounter should be removed
+
+ Appointment appointment2 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("75504r42-3ca8-11e3-bf2b-0800271c1111");
+ Assert.assertEquals(AppointmentStatus.CheckedIn, appointment2.getStatus());
+ Assert.assertEquals(results.getEncounterCreated(),
+ appointment2.getFulfillingEncounters().stream().findFirst().get());
+ }
+ }.run();
+ }
+
+ @Test
+ public void testAppointmentCheckInTag_editingFormWithoutChangingStatusShouldNotCauseError() throws Exception {
+ final Date date = new Date();
+ new RegressionTestHelper() {
+
+ @Override
+ public String getFormName() {
+ return "appointmentCheckInForm";
+ }
+
+ @Override
+ public String[] widgetLabels() {
+ return new String[] { "Date:", "Location:", "Provider:", "Appointments:" };
+ }
+
+ @Override
+ public void setupRequest(MockHttpServletRequest request, Map widgets) {
+ request.addParameter(widgets.get("Date:"), dateAsString(date));
+ request.addParameter(widgets.get("Location:"), "2");
+ request.addParameter(widgets.get("Provider:"), "502");
+ request.addParameter(widgets.get("Appointments:"), "05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ }
+
+ @Override
+ public boolean doEditEncounter() {
+ return true;
+ }
+
+ @Override
+ public String[] widgetLabelsForEdit() {
+ return new String[] { "Appointments:" };
+ }
+
+ public void setupEditRequest(MockHttpServletRequest request, Map widgets) {
+ // upon edit keep the same checked
+ request.addParameter(widgets.get("Appointments:"), "05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ }
+
+ @Override
+ public void testResults(SubmissionResults results) {
+ results.assertNoErrors();
+ results.assertEncounterCreated();
+
+ // sanity check
+ Appointment appointment1 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ Assert.assertEquals(AppointmentStatus.CheckedIn, appointment1.getStatus());
+ Assert.assertEquals(results.getEncounterCreated(),
+ appointment1.getFulfillingEncounters().stream().findFirst().get());
+ }
+
+ @Override
+ public void testEditedResults(SubmissionResults results) {
+ results.assertNoErrors();
+ results.assertEncounterCreated();
+
+ Appointment appointment1 = Context.getService(AppointmentsService.class)
+ .getAppointmentByUuid("05f2ad92-1cc8-4cec-bf54-9cac0200746d");
+ Assert.assertEquals(AppointmentStatus.CheckedIn, appointment1.getStatus());
+ Assert.assertEquals(results.getEncounterCreated(),
+ appointment1.getFulfillingEncounters().stream().findFirst().get());
+ }
+ }.run();
+ }
+}
+
+// TODOS: make sure test to remove works
+// TODOs: review specs
+// TODOs: add to check in form for SL and format/test IRL!
+// TODOs ticket separate functionality to pass in appointment to automatically mark as checked in
diff --git a/api-tests/src/test/resources/test-hibernate.cfg.xml b/api-tests/src/test/resources/test-hibernate.cfg.xml
index a237592da..e99851903 100644
--- a/api-tests/src/test/resources/test-hibernate.cfg.xml
+++ b/api-tests/src/test/resources/test-hibernate.cfg.xml
@@ -5,6 +5,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/api/pom.xml b/api/pom.xml
index bb0f99977..b79f82cbd 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -45,6 +45,11 @@
org.openmrs.module
providermanagement-api
+
+ org.bahmni.module
+ appointments-api
+
+
diff --git a/api/src/main/java/org/openmrs/module/htmlformentry/FormEntryContext.java b/api/src/main/java/org/openmrs/module/htmlformentry/FormEntryContext.java
index 4690f8885..59acbe7eb 100644
--- a/api/src/main/java/org/openmrs/module/htmlformentry/FormEntryContext.java
+++ b/api/src/main/java/org/openmrs/module/htmlformentry/FormEntryContext.java
@@ -151,21 +151,31 @@ public Mode getMode() {
* @param widget the widget to register
* @return the field id used to identify this widget in the HTML Form
*/
- public String registerWidget(Widget widget) {
+ // TODO update documentation
+ public String registerWidget(Widget widget, String fieldName) {
if (fieldNames.containsKey(widget))
throw new IllegalArgumentException("This widget is already registered");
- int thisVal = 0;
- synchronized (sequenceNextVal) {
- thisVal = sequenceNextVal;
- sequenceNextVal = sequenceNextVal + 1;
+
+ if (StringUtils.isBlank(fieldName)) {
+ int thisVal = 0;
+ synchronized (sequenceNextVal) {
+ thisVal = sequenceNextVal;
+ sequenceNextVal = sequenceNextVal + 1;
+ }
+ fieldName = "w" + thisVal;
}
- String fieldName = "w" + thisVal;
+
fieldNames.put(widget, fieldName);
if (log.isTraceEnabled())
log.trace("Registered widget " + widget.getClass() + " as " + fieldName);
return fieldName;
}
+ // TODO document
+ public String registerWidget(Widget widget) {
+ return registerWidget(widget, null);
+ }
+
/**
* Registers an error widget within the Context
*
diff --git a/api/src/main/java/org/openmrs/module/htmlformentry/FormEntrySession.java b/api/src/main/java/org/openmrs/module/htmlformentry/FormEntrySession.java
index baadf0542..1dc08d95b 100644
--- a/api/src/main/java/org/openmrs/module/htmlformentry/FormEntrySession.java
+++ b/api/src/main/java/org/openmrs/module/htmlformentry/FormEntrySession.java
@@ -28,6 +28,9 @@
import org.openmrs.Relationship;
import org.openmrs.api.ObsService;
import org.openmrs.api.context.Context;
+import org.openmrs.module.appointments.model.Appointment;
+import org.openmrs.module.appointments.model.AppointmentStatus;
+import org.openmrs.module.appointments.service.AppointmentsService;
import org.openmrs.module.htmlformentry.FormEntryContext.Mode;
import org.openmrs.module.htmlformentry.property.ExitFromCareProperty;
import org.openmrs.module.htmlformentry.velocity.VelocityContextContentProvider;
@@ -44,6 +47,7 @@
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -636,6 +640,31 @@ public void applyActions() throws BadFormDesignException {
}
}
+ // handle appointments (needs to happen after encounter is saved?)
+ if (submissionActions.getAppointmentsToMarkCheckedInAndAssociateWithEncounter() != null) {
+ for (Appointment appointment : submissionActions.getAppointmentsToMarkCheckedInAndAssociateWithEncounter()) {
+ if (appointment.getStatus() == AppointmentStatus.Scheduled) {
+ Context.getService(AppointmentsService.class).changeStatus(appointment,
+ AppointmentStatus.CheckedIn.toString(), encounter.getEncounterDatetime());
+ }
+ if (appointment.getFulfillingEncounters() != null) {
+ appointment.getFulfillingEncounters().add(encounter);
+ } else {
+ appointment.setFulfillingEncounters(Collections.singleton(encounter));
+ }
+ Context.getService(AppointmentsService.class).validateAndSave(appointment);
+ }
+ }
+
+ if (submissionActions.getAppointmentsToDisassociateFromEncounter() != null) {
+ for (Appointment appointment : submissionActions.getAppointmentsToDisassociateFromEncounter()) {
+ if (appointment.getFulfillingEncounters() != null) {
+ appointment.getFulfillingEncounters().remove(encounter);
+ Context.getService(AppointmentsService.class).validateAndSave(appointment);
+ }
+ }
+ }
+
//deal with relationships
if (submissionActions.getRelationshipsToCreate() != null) {
for (Relationship r : submissionActions.getRelationshipsToCreate()) {
@@ -986,7 +1015,7 @@ public void applyActions() throws BadFormDesignException {
exitFromCareProperty.getReasonExitConcept());
}
}
-
+
// handle any custom actions (for an example of a custom action, see: https://github.com/PIH/openmrs-module-appointmentschedulingui/commit/e2cda8de1caa8a45d319ae4fbf7714c90c9adb8b)
if (submissionActions.getCustomFormSubmissionActions() != null) {
for (CustomFormSubmissionAction customFormSubmissionAction : submissionActions
diff --git a/api/src/main/java/org/openmrs/module/htmlformentry/FormSubmissionActions.java b/api/src/main/java/org/openmrs/module/htmlformentry/FormSubmissionActions.java
index d74cdcf2c..736931b7b 100644
--- a/api/src/main/java/org/openmrs/module/htmlformentry/FormSubmissionActions.java
+++ b/api/src/main/java/org/openmrs/module/htmlformentry/FormSubmissionActions.java
@@ -19,6 +19,7 @@
import org.openmrs.ProgramWorkflowState;
import org.openmrs.Relationship;
import org.openmrs.api.context.Context;
+import org.openmrs.module.appointments.model.Appointment;
import org.openmrs.module.htmlformentry.property.ExitFromCareProperty;
import org.openmrs.util.OpenmrsUtil;
@@ -79,6 +80,10 @@ public class FormSubmissionActions {
private List identifiersToVoid = new Vector();
+ private List appointmentsToMarkCheckedInAndAssociateWithEncounter = new Vector();
+
+ private List appointmentsToDisassociateFromEncounter = new Vector();
+
private ExitFromCareProperty exitFromCareProperty;
private List customFormSubmissionActions;
@@ -946,4 +951,21 @@ public List getCustomFormSubmissionActions() {
public void setCustomFormSubmissionActions(List customFormSubmissionActions) {
this.customFormSubmissionActions = customFormSubmissionActions;
}
+
+ public List getAppointmentsToMarkCheckedInAndAssociateWithEncounter() {
+ return appointmentsToMarkCheckedInAndAssociateWithEncounter;
+ }
+
+ public void setAppointmentsToMarkCheckedInAndAssociateWithEncounter(
+ List appointmentsToMarkCheckedInAndAssociateWithEncounter) {
+ this.appointmentsToMarkCheckedInAndAssociateWithEncounter = appointmentsToMarkCheckedInAndAssociateWithEncounter;
+ }
+
+ public List getAppointmentsToDisassociateFromEncounter() {
+ return appointmentsToDisassociateFromEncounter;
+ }
+
+ public void setAppointmentsToDisassociateFromEncounter(List appointmentsToDisassociateFromEncounter) {
+ this.appointmentsToDisassociateFromEncounter = appointmentsToDisassociateFromEncounter;
+ }
}
diff --git a/api/src/main/java/org/openmrs/module/htmlformentry/ObsGroupComponent.java b/api/src/main/java/org/openmrs/module/htmlformentry/ObsGroupComponent.java
index 2c7acfaa3..695360915 100644
--- a/api/src/main/java/org/openmrs/module/htmlformentry/ObsGroupComponent.java
+++ b/api/src/main/java/org/openmrs/module/htmlformentry/ObsGroupComponent.java
@@ -92,7 +92,7 @@ public Drug getAnswerDrug() {
public void setAnswerDrug(Drug answerDrug) {
this.answerDrug = answerDrug;
}
-
+
public static int supportingRank(List obsGroupComponents, Set obsSet) {
int rank = 0;
diff --git a/api/src/main/java/org/openmrs/module/htmlformentry/element/AppointmentsElement.java b/api/src/main/java/org/openmrs/module/htmlformentry/element/AppointmentsElement.java
new file mode 100644
index 000000000..1227ea7a5
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/htmlformentry/element/AppointmentsElement.java
@@ -0,0 +1,88 @@
+package org.openmrs.module.htmlformentry.element;
+
+import javax.servlet.http.HttpServletRequest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+
+import org.joda.time.DateTime;
+import org.openmrs.Patient;
+import org.openmrs.api.context.Context;
+import org.openmrs.module.appointments.model.Appointment;
+import org.openmrs.module.appointments.model.AppointmentSearchRequest;
+import org.openmrs.module.appointments.model.AppointmentStatus;
+import org.openmrs.module.appointments.service.AppointmentsService;
+import org.openmrs.module.htmlformentry.FormEntryContext;
+import org.openmrs.module.htmlformentry.FormEntrySession;
+import org.openmrs.module.htmlformentry.FormSubmissionError;
+import org.openmrs.module.htmlformentry.action.FormSubmissionControllerAction;
+import org.openmrs.module.htmlformentry.widget.AppointmentsWidget;
+
+public class AppointmentsElement implements HtmlGeneratorElement, FormSubmissionControllerAction {
+
+ private AppointmentsWidget appointmentsWidget;
+
+ private List appointments = new ArrayList<>();
+
+ public AppointmentsElement(FormEntryContext context) {
+ Patient patient = context.getExistingPatient();
+ if (patient != null) {
+
+ // first, get all scheduled appointments for this patient
+ AppointmentSearchRequest request = new AppointmentSearchRequest();
+ request.setPatientUuid(patient.getUuid());
+ request.setStartDate(new DateTime().minusYears(1000).toDate()); // TODO hack, we want all appts for patient regardless of start date, but start date is required, this will start to fail in a thousand years
+ appointments = Context.getService(AppointmentsService.class).search(request);
+
+ appointments.sort(Comparator.comparing(Appointment::getStartDateTime).reversed());
+ appointments.removeIf(appointment -> appointment.getStatus() != AppointmentStatus.Scheduled
+ && (appointment.getFulfillingEncounters() == null
+ && !appointment.getFulfillingEncounters().contains(context.getExistingEncounter())));
+ }
+ }
+
+ @Override
+ public String generateHtml(FormEntryContext context) {
+ appointmentsWidget = new AppointmentsWidget(appointments, context);
+ return appointmentsWidget.generateHtml(context);
+ }
+
+ @Override
+ public Collection validateSubmission(FormEntryContext context, HttpServletRequest submission) {
+ return null;
+ }
+
+ @Override
+ public void handleSubmission(FormEntrySession session, HttpServletRequest submission) {
+ List selectedAppointmentUuids = (List) appointmentsWidget.getValue(session.getContext(), submission);
+ List appointmentsToMarkCheckedIn = new ArrayList<>();
+ List appointmentsToDisassociateFromEncounter = new ArrayList<>();
+
+ // find appointments that need to be marked as checked in
+ for (String uuid : selectedAppointmentUuids) {
+ appointments.stream().filter(appointment -> appointment.getUuid().equals(uuid)).findFirst()
+ .ifPresent(appointment -> {
+ appointmentsToMarkCheckedIn.add(appointment);
+ });
+ }
+ if (appointmentsToMarkCheckedIn.size() > 0) {
+ session.getSubmissionActions()
+ .setAppointmentsToMarkCheckedInAndAssociateWithEncounter(appointmentsToMarkCheckedIn);
+ }
+
+ // find appointments that need to be disassociated from the encounter
+ appointments.stream()
+ .filter(appointment -> (!selectedAppointmentUuids.contains(appointment.getUuid())
+ && appointment.getFulfillingEncounters() != null
+ && appointment.getFulfillingEncounters().contains(session.getEncounter())))
+ .forEach(appointment -> {
+ appointmentsToDisassociateFromEncounter.add(appointment);
+ });
+ if (appointmentsToDisassociateFromEncounter.size() > 0) {
+ session.getSubmissionActions()
+ .setAppointmentsToDisassociateFromEncounter(appointmentsToDisassociateFromEncounter);
+ }
+ }
+}
diff --git a/api/src/main/java/org/openmrs/module/htmlformentry/handler/AppointmentsTagHandler.java b/api/src/main/java/org/openmrs/module/htmlformentry/handler/AppointmentsTagHandler.java
new file mode 100644
index 000000000..15eb2ee19
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/htmlformentry/handler/AppointmentsTagHandler.java
@@ -0,0 +1,27 @@
+package org.openmrs.module.htmlformentry.handler;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.openmrs.module.htmlformentry.FormEntrySession;
+import org.openmrs.module.htmlformentry.FormSubmissionController;
+import org.openmrs.module.htmlformentry.element.AppointmentsElement;
+
+public class AppointmentsTagHandler extends SubstitutionTagHandler {
+
+ @Override
+ protected List createAttributeDescriptors() {
+ List attributeDescriptors = new ArrayList();
+ return Collections.unmodifiableList(attributeDescriptors);
+ }
+
+ @Override
+ protected String getSubstitution(FormEntrySession session, FormSubmissionController controllerActions,
+ Map parameters) {
+ AppointmentsElement element = new AppointmentsElement(session.getContext());
+ session.getSubmissionController().addAction(element);
+ return element.generateHtml(session.getContext());
+ }
+}
diff --git a/api/src/main/java/org/openmrs/module/htmlformentry/widget/AppointmentsWidget.java b/api/src/main/java/org/openmrs/module/htmlformentry/widget/AppointmentsWidget.java
new file mode 100644
index 000000000..bb6ed89c1
--- /dev/null
+++ b/api/src/main/java/org/openmrs/module/htmlformentry/widget/AppointmentsWidget.java
@@ -0,0 +1,94 @@
+package org.openmrs.module.htmlformentry.widget;
+
+import javax.servlet.http.HttpServletRequest;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.openmrs.api.context.Context;
+import org.openmrs.module.appointments.model.Appointment;
+import org.openmrs.module.htmlformentry.FormEntryContext;
+import org.openmrs.module.htmlformentry.HtmlFormEntryConstants;
+import org.openmrs.module.htmlformentry.HtmlFormEntryUtil;
+import org.springframework.util.StringUtils;
+
+public class AppointmentsWidget implements Widget {
+
+ private List appointments;
+
+ private List checkboxWidgets = new ArrayList();
+
+ public AppointmentsWidget(List appointments, FormEntryContext context) {
+ this.appointments = appointments;
+ String fieldName = context.registerWidget(this);
+
+ // TODO: document why we need the special registration for form consistency
+ int i = 1;
+ for (Appointment appointment : appointments) {
+ CheckboxWidget checkboxWidget = new CheckboxWidget();
+ checkboxWidget.setLabel(
+ dateTimeFormat().format(appointment.getStartDateTime()) + " - " + renderProviderNames(appointment) + " - "
+ + (appointment.getLocation() != null ? appointment.getLocation().getName() : ""));
+ checkboxWidget.setValue(appointment.getUuid());
+ if (appointment.getFulfillingEncounters() != null && context.getExistingEncounter() != null
+ && appointment.getFulfillingEncounters().contains(context.getExistingEncounter())) {
+ checkboxWidget.setInitialValue(appointment.getUuid());
+ }
+ context.registerWidget(checkboxWidget, fieldName + "_" + i++);
+ checkboxWidgets.add(checkboxWidget);
+ }
+
+ }
+
+ @Override
+ public void setInitialValue(Object initialValue) {
+ // TODO?
+ }
+
+ @Override
+ public String generateHtml(FormEntryContext context) {
+ if (appointments == null || appointments.isEmpty()) {
+ return "No appointments found"; // TODO translate, style
+ }
+
+ return checkboxWidgets.stream().map(checkboxWidget -> {
+ return checkboxWidget.generateHtml(context);
+ }).collect(Collectors.joining());
+ }
+
+ @Override
+ public Object getValue(FormEntryContext context, HttpServletRequest request) {
+ List selectedAppointmentUuids = new ArrayList();
+ for (CheckboxWidget checkboxWidget : checkboxWidgets) {
+ String value = (String) checkboxWidget.getValue(context, request);
+ if (value != null) {
+ selectedAppointmentUuids.add(value);
+ }
+ }
+ return selectedAppointmentUuids;
+ }
+
+ // TODO move to util method?
+ private SimpleDateFormat dateTimeFormat() {
+ String df = Context.getAdministrationService().getGlobalProperty(HtmlFormEntryConstants.GP_FORMATTER_DATETIME,
+ "yyyy-MM-dd, HH:mm:ss");
+ if (StringUtils.hasText(df)) {
+ return new SimpleDateFormat(df, Context.getLocale());
+ } else {
+ return Context.getDateTimeFormat();
+ }
+ }
+
+ private String renderProviderNames(Appointment appointment) {
+ if (appointment.getProviders() != null) {
+ return appointment.getProviders().stream().map(provider -> {
+ return provider.getProvider().getPerson() != null ? HtmlFormEntryUtil.getFullNameWithFamilyNameFirst(
+ provider.getProvider().getPerson().getPersonName()) : provider.getProvider().getName();
+ }).collect(Collectors.joining("; "));
+ }
+ return "";
+
+ }
+}
diff --git a/api/src/main/resources/moduleApplicationContext.xml b/api/src/main/resources/moduleApplicationContext.xml
index e19f18a58..12061c426 100644
--- a/api/src/main/resources/moduleApplicationContext.xml
+++ b/api/src/main/resources/moduleApplicationContext.xml
@@ -113,6 +113,9 @@
+
+
+
diff --git a/api/src/test/resources/org/openmrs/module/htmlformentry/data/appointmentCheckInTest.xml b/api/src/test/resources/org/openmrs/module/htmlformentry/data/appointmentCheckInTest.xml
new file mode 100644
index 000000000..6484483c4
--- /dev/null
+++ b/api/src/test/resources/org/openmrs/module/htmlformentry/data/appointmentCheckInTest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/api/src/test/resources/org/openmrs/module/htmlformentry/include/appointmentCheckInForm.xml b/api/src/test/resources/org/openmrs/module/htmlformentry/include/appointmentCheckInForm.xml
new file mode 100644
index 000000000..b4321892e
--- /dev/null
+++ b/api/src/test/resources/org/openmrs/module/htmlformentry/include/appointmentCheckInForm.xml
@@ -0,0 +1,8 @@
+
+ Date:
+ Location:
+ Provider:
+
+ Appointments:
+
+
diff --git a/omod/src/main/resources/config.xml b/omod/src/main/resources/config.xml
index 7074847cd..b4b6eac98 100644
--- a/omod/src/main/resources/config.xml
+++ b/omod/src/main/resources/config.xml
@@ -22,6 +22,7 @@
org.openmrs.module.legacyui
org.openmrs.module.metadatamapping
org.openmrs.module.providermanagement
+ org.bahmni.module.appointments
diff --git a/pom.xml b/pom.xml
index c7b9c65f0..2b456d2a0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -125,6 +125,12 @@
2.11.0
provided
+
+ org.bahmni.module
+ appointments-api
+ 2.0.0-SNAPSHOT
+ provided
+
org.codehaus.jackson
jackson-mapper-asl