diff --git a/api/pom.xml b/api/pom.xml index 64f741fed..4421b46a5 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -149,6 +149,13 @@ provided + + + + com.squareup.okhttp + okhttp + 2.7.5 + diff --git a/api/src/main/java/org/openmrs/module/appointments/notification/SmsSender.java b/api/src/main/java/org/openmrs/module/appointments/notification/SmsSender.java new file mode 100644 index 000000000..43e312cc3 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/appointments/notification/SmsSender.java @@ -0,0 +1,7 @@ +package org.openmrs.module.appointments.notification; + +import java.util.List; + +public interface SmsSender { + void send(String message, List to); +} diff --git a/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultSmsSender.java b/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultSmsSender.java new file mode 100644 index 000000000..529774d76 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultSmsSender.java @@ -0,0 +1,24 @@ +package org.openmrs.module.appointments.notification.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openmrs.api.AdministrationService; +import org.openmrs.module.appointments.notification.SmsSender; + +import java.util.List; + +public class DefaultSmsSender implements SmsSender { + + private Log log = LogFactory.getLog(this.getClass()); + + private AdministrationService administrationService; + + public DefaultSmsSender(AdministrationService administrationService) { + this.administrationService = administrationService; + } + + @Override + public void send(String message, List to) { + //Write your implementation based on your SMS API + } +} diff --git a/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultTCAppointmentSmsNotifier.java b/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultTCAppointmentSmsNotifier.java new file mode 100644 index 000000000..d600c2004 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultTCAppointmentSmsNotifier.java @@ -0,0 +1,123 @@ +package org.openmrs.module.appointments.notification.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openmrs.Patient; +import org.openmrs.PersonAttribute; +import org.openmrs.api.context.Context; +import org.openmrs.module.appointments.model.Appointment; +import org.openmrs.module.appointments.model.AppointmentKind; +import org.openmrs.module.appointments.model.AppointmentProvider; +import org.openmrs.module.appointments.model.AppointmentServiceDefinition; +import org.openmrs.module.appointments.notification.*; +import org.openmrs.util.LocaleUtility; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class DefaultTCAppointmentSmsNotifier implements AppointmentEventNotifier { + + private final static String PROP_PATIENT_SMS_TEMPLATE = "bahmni.appointment.teleConsultation.patientSmsNotificationTemplate"; + private final static String PROP_SEND_TC_APPT_SMS = "bahmni.appointment.teleConsultation.sendSms"; + private final static String PROP_SEND_TC_APPT_CONTACT_ATTRIBUTE = "bahmni.appointment.teleConsultation.contactAttribute"; + + private static final String MEDIUM_SMS = "SMS"; + private static final String SMS_NOT_CONFIGURED = "Notification can not be sent to patient. Mobile number not configured."; + private static final String SMS_SENT = "SMS sent to Patient"; + private static final String SMS_FAILURE = "Failed to send sms to patient"; + private static final String SMS_NOT_SENT = "SMS notification for tele-consultation not configured to be sent to patient."; + + + private Log log = LogFactory.getLog(this.getClass()); + private SmsSender smsSender; + + public DefaultTCAppointmentSmsNotifier() {} + public DefaultTCAppointmentSmsNotifier(SmsSender smsSender) { + this.smsSender = smsSender; + } + + @Override + public String getMedium() { + return MEDIUM_SMS; + } + + @Override + public boolean isApplicable(final Appointment appointment) { + boolean sendSmsToPatient = shouldSendSmsToPatient(); + if (!sendSmsToPatient) { + log.warn(SMS_NOT_SENT); + } + if (appointment.getAppointmentKind() != null && appointment.getAppointmentKind().equals(AppointmentKind.Virtual)) { + return sendSmsToPatient; + } + return false; + } + + @Override + public NotificationResult sendNotification(final Appointment appointment) throws NotificationException { + Patient patient = appointment.getPatient(); + String contactAttribute = Context.getAdministrationService().getGlobalProperty(PROP_SEND_TC_APPT_CONTACT_ATTRIBUTE); + PersonAttribute patientContactAttribute = patient.getPerson().getAttribute(contactAttribute); + Set providers = appointment.getProviders(); + List contacts = new ArrayList<>(); + + if (patientContactAttribute != null) { + contacts.add(patientContactAttribute.getValue()); + } + + if (providers != null) { + for (AppointmentProvider provider : providers) { + PersonAttribute providerContactAttribute = provider.getProvider().getPerson().getAttribute(contactAttribute); + if (providerContactAttribute != null) { + contacts.add(providerContactAttribute.getValue()); + } + } + } + + if (contacts.size() > 0) { + String patientName = appointment.getPatient().getGivenName(); + String message = getMessage(patientName, appointment.getService(), + providers, appointment.getStartDateTime(), + appointment.getTeleHealthVideoLink()); + try { + log.info("Sending sms through: " + smsSender.getClass()); + smsSender.send(message, contacts); + return new NotificationResult("", "SMS", NotificationResult.SUCCESS_STATUS, SMS_SENT); + } catch (Exception e) { + log.error(SMS_FAILURE, e); + throw new NotificationException(SMS_FAILURE, e); + } + } else { + log.warn(SMS_NOT_CONFIGURED); + return new NotificationResult(null, "SMS", NotificationResult.GENERAL_ERROR, SMS_NOT_CONFIGURED); + } + } + + private String getMessage(String patientName, + AppointmentServiceDefinition service, + Set providers, + Date appointmentDate, String link) { + String smsTemplate = Context.getAdministrationService().getGlobalProperty(PROP_PATIENT_SMS_TEMPLATE); + String practitioners = + providers != null ? + providers.stream() + .map(appointmentProvider -> appointmentProvider.getProvider().getName()) + .collect(Collectors.joining(",")) + : ""; + Object[] arguments = {patientName, practitioners, appointmentDate, link}; + if (smsTemplate == null || "".equals(smsTemplate)) { + return Context.getMessageSourceService().getMessage(PROP_PATIENT_SMS_TEMPLATE, arguments, LocaleUtility.getDefaultLocale()); + } else { + return new MessageFormat(smsTemplate).format(arguments); + } + } + + private boolean shouldSendSmsToPatient() { + String shouldSendSMS = Context.getAdministrationService().getGlobalProperty(PROP_SEND_TC_APPT_SMS, "false"); + return Boolean.valueOf(shouldSendSMS); + } +} diff --git a/api/src/main/resources/moduleApplicationContext.xml b/api/src/main/resources/moduleApplicationContext.xml index 3a40e543f..feee00843 100644 --- a/api/src/main/resources/moduleApplicationContext.xml +++ b/api/src/main/resources/moduleApplicationContext.xml @@ -28,9 +28,15 @@ + + + + + + @@ -38,6 +44,7 @@ + diff --git a/api/src/test/java/org/openmrs/module/appointments/service/impl/DefaultTCAppointmentSmsNotifierTest.java b/api/src/test/java/org/openmrs/module/appointments/service/impl/DefaultTCAppointmentSmsNotifierTest.java new file mode 100644 index 000000000..62d842e14 --- /dev/null +++ b/api/src/test/java/org/openmrs/module/appointments/service/impl/DefaultTCAppointmentSmsNotifierTest.java @@ -0,0 +1,93 @@ +package org.openmrs.module.appointments.service.impl; + +import org.databene.commons.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.AdditionalMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openmrs.Patient; +import org.openmrs.PersonAttribute; +import org.openmrs.PersonAttributeType; +import org.openmrs.api.AdministrationService; +import org.openmrs.api.context.Context; +import org.openmrs.messagesource.MessageSourceService; +import org.openmrs.module.appointments.model.Appointment; +import org.openmrs.module.appointments.notification.AppointmentEventNotifier; +import org.openmrs.module.appointments.notification.NotificationException; +import org.openmrs.module.appointments.notification.SmsSender; +import org.openmrs.module.appointments.notification.impl.DefaultTCAppointmentPatientEmailNotifier; +import org.openmrs.module.appointments.notification.impl.DefaultTCAppointmentSmsNotifier; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(Context.class) +public class DefaultTCAppointmentSmsNotifierTest { + private static final String BAHMNI_APPOINTMENT_TELE_CONSULTATION_SMS_NOTIFICATION_TEMPLATE = "bahmni.appointment.teleConsultation.patientSmsNotificationTemplate"; + private static final String BAHMNI_APPOINTMENT_TELE_CONSULTATION_CONTACT_ATTRIBUTE = "bahmni.appointment.teleConsultation.contactAttribute"; + private AppointmentEventNotifier tcAppointmentEventNotifier; + + @Mock + private AdministrationService administrationService; + + @Mock + private SmsSender smsSender; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + mockStatic(Context.class); + tcAppointmentEventNotifier = new DefaultTCAppointmentSmsNotifier(smsSender); + PowerMockito.when(Context.getAdministrationService()).thenReturn(administrationService); + when(administrationService.getGlobalProperty(BAHMNI_APPOINTMENT_TELE_CONSULTATION_SMS_NOTIFICATION_TEMPLATE)).thenReturn("Email body"); + when(administrationService.getGlobalProperty(BAHMNI_APPOINTMENT_TELE_CONSULTATION_CONTACT_ATTRIBUTE)).thenReturn("primaryContact"); + } + + @Test + public void shouldSendTeleconsultationAppointmentLinkSms() throws Exception { + Appointment appointment = buildAppointment(); + tcAppointmentEventNotifier.sendNotification(appointment); + List contacts = new ArrayList<>(); + contacts.add("+61411111111"); + verify(smsSender).send( + eq("Email body"), + eq(contacts)); + } + + @Test + public void shouldThrowExceptionIfSendingFails() throws NotificationException { + Appointment appointment = buildAppointment(); + doThrow(new RuntimeException()).when(smsSender).send(any(), any()); + expectedException.expect(NotificationException.class); + tcAppointmentEventNotifier.sendNotification(appointment); + } + + private Appointment buildAppointment() { + Appointment appointment = new Appointment(); + Patient patient = new Patient(); + patient.setUuid("patientUuid"); + PersonAttributeType personAttributeType = new PersonAttributeType(); + personAttributeType.setName("primaryContact"); + patient.addAttribute(new PersonAttribute(personAttributeType, "+61411111111")); + appointment.setPatient(patient); + return appointment; + } +} diff --git a/omod/src/main/resources/config.xml b/omod/src/main/resources/config.xml index 65cf88ff7..2e6100fcb 100644 --- a/omod/src/main/resources/config.xml +++ b/omod/src/main/resources/config.xml @@ -96,6 +96,24 @@ false Whether email should be sent for tele-consultation + + bahmni.appointment.teleConsultation.sendSms + false + Whether sms should be sent for tele-consultation + + + bahmni.appointment.teleConsultation.patientSmsNotificationTemplate + + Hi {0}, Your tele-consultation appointment with {1} for {2} has been scheduled! Please use this link {3}. If you have any questions, please reach out to administration for assistance. See you soon! + + Template to use while sending sms. Format is define like in Java MessageFormat + + + + bahmni.appointment.teleConsultation.contactAttribute + primaryContact + Person attribute from which the contact should be taken for sending sms +