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..deae75ee8 --- /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.Date; + +public interface SmsSender { + void send(String recipientMobileNumber, String patientName, String smsType, String serviceName, Date startDateTime, String teleLink); +} diff --git a/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultAppointmentPatientSmsNotifier.java b/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultAppointmentPatientSmsNotifier.java new file mode 100644 index 000000000..f9bafb0f4 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultAppointmentPatientSmsNotifier.java @@ -0,0 +1,81 @@ +package org.openmrs.module.appointments.notification.impl; + +import org.apache.commons.lang3.StringUtils; +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.notification.AppointmentEventNotifier; +import org.openmrs.module.appointments.notification.NotificationException; +import org.openmrs.module.appointments.notification.NotificationResult; +import org.openmrs.module.appointments.notification.SmsSender; + +public class DefaultAppointmentPatientSmsNotifier implements AppointmentEventNotifier { + private Log log = LogFactory.getLog(this.getClass()); + + private final static String PROP_SEND_APPT_SMS = "bahmni.appointment.sendSms"; + + private static final String SMS_NOT_CONFIGURED = "SMS notification can not be sent to patient. Phone number not configured."; + private static final String SMS_SENT = "SMS sent to patient"; + private static final String MEDIUM_SMS = "SMS"; + private static final String SMS_FAILURE = "Failed to send sms to patient"; + private static final String SMS_NOT_SENT = "SMS notification not configured to be sent to patient."; + + private SmsSender smsSender; + + public DefaultAppointmentPatientSmsNotifier() {} + + public DefaultAppointmentPatientSmsNotifier(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); + } + return sendSmsToPatient; + } + + @Override + public NotificationResult sendNotification(final Appointment appointment) throws NotificationException { + Patient patient = appointment.getPatient(); + PersonAttribute patientPhoneAttribute = patient.getPerson().getAttribute("phoneNumber"); + if (patientPhoneAttribute != null) { + String patientPhoneNumber = patientPhoneAttribute.getValue(); + String patientName = appointment.getPatient().getGivenName(); + String apptServiceName = appointment.getService().getName(); + String teleLink = StringUtils.isNotBlank(appointment.getTeleHealthVideoLink()) ? appointment.getTeleHealthVideoLink() : ""; + String smsType = appointment.getAppointmentKind().toString(); + try { + log.info("Sending sms through: " + smsSender.getClass()); + smsSender.send(patientPhoneNumber, patientName, smsType, apptServiceName, appointment.getStartDateTime(), teleLink); + return new NotificationResult("", "SMS", 0, 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", 1, SMS_NOT_CONFIGURED); + } + } + + private boolean shouldSendSmsToPatient() { + String shouldSendEmail = Context.getAdministrationService().getGlobalProperty(PROP_SEND_APPT_SMS, "false"); + return Boolean.valueOf(shouldSendEmail); + } + + public void setSmsSender(SmsSender smsSender) { + log.warn("Replacing default SmsSender: " + this.smsSender + ", with:" + smsSender); + this.smsSender = smsSender; + } +} 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..a67070cc6 --- /dev/null +++ b/api/src/main/java/org/openmrs/module/appointments/notification/impl/DefaultSmsSender.java @@ -0,0 +1,180 @@ +package org.openmrs.module.appointments.notification.impl; + +import org.apache.commons.lang.StringUtils; +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 org.openmrs.util.OpenmrsUtil; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Properties; + +public class DefaultSmsSender implements SmsSender { + + private Log log = LogFactory.getLog(this.getClass()); + + private static final String SMS_PROPERTIES_FILENAME = "sms-config.properties"; + private static Properties smsSessionProperties = null; + + SimpleDateFormat fmtTime = new SimpleDateFormat("HH:mm a"); + SimpleDateFormat fmtDateTele = new SimpleDateFormat("dd/MM/yyyy"); + + private AdministrationService administrationService; + + public DefaultSmsSender(AdministrationService administrationService) { + this.administrationService = administrationService; + } + + @Override + public void send(String recipientMobileNumber, String patientName, String smsType, String serviceName, Date startDateTime, String teleLink) { + HttpURLConnection uc = null; + try { + getSession(); + + String password = ""; + String addOnParams = ""; + String entityId = ""; + String templateId = ""; + String msgText = ""; + + recipientMobileNumber = recipientMobileNumber.replace("+", ""); + int recipientPhoneLengthRequired = Integer.parseInt(smsSessionProperties.getProperty("sms.phonenumber.length")); + if (recipientMobileNumber.length()>recipientPhoneLengthRequired) { + int indexStart = recipientMobileNumber.length() - recipientPhoneLengthRequired; + recipientMobileNumber = recipientMobileNumber.substring(indexStart); + } + String dateSuffixText = getDateWithSuffix(startDateTime.getDate()); + String dateApptPattern = String.format("MMMM '%s', yyyy", dateSuffixText); + SimpleDateFormat fmtDateAppt = new SimpleDateFormat(dateApptPattern); + + String requestUrl = smsSessionProperties.getProperty("sms.url"); + String apiKey = smsSessionProperties.getProperty("sms.key.username") + "=" + URLEncoder.encode(smsSessionProperties.getProperty("sms.username"), "UTF-8"); + String sender = "&" + smsSessionProperties.getProperty("sms.key.sender") + "=" + URLEncoder.encode(smsSessionProperties.getProperty("sms.sender"), "UTF-8"); + String number = "&" + smsSessionProperties.getProperty("sms.key.phonenumber") + "=" + URLEncoder.encode(recipientMobileNumber, "UTF-8"); + + if (StringUtils.isNotBlank(smsSessionProperties.getProperty("sms.key.password"))) { + password = "&" + smsSessionProperties.getProperty("sms.key.password") + "=" + URLEncoder.encode(smsSessionProperties.getProperty("sms.password") , "UTF-8"); + } + if (StringUtils.isNotBlank(smsSessionProperties.getProperty("sms.key.entity.identifier"))) { + entityId = "&" + smsSessionProperties.getProperty("sms.key.entity.identifier") + "=" + URLEncoder.encode(smsSessionProperties.getProperty("sms.entity.identifier"), "UTF-8"); + } + if (StringUtils.isNotBlank(smsSessionProperties.getProperty("sms.key.template.identifier")) && "Virtual".equalsIgnoreCase(smsType)) { + templateId = "&" + smsSessionProperties.getProperty("sms.key.template.identifier") + "=" + + URLEncoder.encode(smsSessionProperties.getProperty("sms.appointment.teleconsultation.template.identifier"), "UTF-8"); + // Dear %s,\nTele-Consultation booked for %s %s\nVideo link: %s Bahmni Hospital(IPLit) + msgText = String.format(smsSessionProperties.getProperty("sms.appointment.teleconsultation.message.template"), patientName, + fmtDateTele.format(startDateTime), fmtTime.format(startDateTime), teleLink); + } + if (StringUtils.isNotBlank(smsSessionProperties.getProperty("sms.key.template.identifier")) && "WalkIn".equalsIgnoreCase(smsType)) { + templateId = "&" + smsSessionProperties.getProperty("sms.key.template.identifier") + "=" + + URLEncoder.encode(smsSessionProperties.getProperty("sms.appointment.walkin.template.identifier"), "UTF-8"); + // Dear %s,\n Your appointment is booked for %s on %s at %s by Bahmni Hospital (powered by IPLit) + msgText = String.format(smsSessionProperties.getProperty("sms.appointment.walkin.message.template"), patientName, + serviceName, fmtDateAppt.format(startDateTime), fmtTime.format(startDateTime)); + } + if (StringUtils.isNotBlank(smsSessionProperties.getProperty("sms.params.addons"))) { + addOnParams = "&" + smsSessionProperties.getProperty("sms.params.addons"); + } + String message = "&" + smsSessionProperties.getProperty("sms.key.message") + "=" + URLEncoder.encode(msgText, "UTF-8"); + requestUrl += apiKey + message + sender + number + password + entityId + templateId + addOnParams; + + URL url = new URL(requestUrl); + uc = (HttpURLConnection)url.openConnection(); + String smsResponse = uc.getResponseMessage(); + } catch (Exception e) { + throw new RuntimeException("Error occurred while sending sms", e); + } finally { + if (uc!=null) { + uc.disconnect(); + uc = null; + } + } + } + + private Properties getSession() { + if (smsSessionProperties == null) { + Properties sessionProperties = smsSessionPropertiesFromPath(); + if (sessionProperties == null) { + log.warn("Could not load sms properties from application data directory file. Loading from OMRS settings."); + sessionProperties = smsSessionPropertiesFromOMRS(); + } + smsSessionProperties = sessionProperties; + } + return smsSessionProperties; + } + + /** + * To be used as fallback. SMS properties are visible in openmrs settings. + * @param as + * @return + */ + private Properties smsSessionPropertiesFromOMRS() { + Properties p = new Properties(); + p.put("sms.key.username", administrationService.getGlobalProperty("sms.key.username", "")); + p.put("sms.key.password", administrationService.getGlobalProperty("sms.key.password", "")); + p.put("sms.key.phonenumber", administrationService.getGlobalProperty("sms.key.phonenumber", "")); + p.put("sms.key.message", administrationService.getGlobalProperty("sms.key.message", "")); + p.put("sms.key.sender", administrationService.getGlobalProperty("sms.key.sender", "")); + p.put("sms.key.entity.identifier", administrationService.getGlobalProperty("sms.key.entity.identifier", "")); + p.put("sms.key.template.identifier", administrationService.getGlobalProperty("sms.key.template.identifier", "")); + + p.put("sms.url", administrationService.getGlobalProperty("sms.url", "")); + p.put("sms.username", administrationService.getGlobalProperty("sms.username", "")); + p.put("sms.password", administrationService.getGlobalProperty("sms.password", "")); + p.put("sms.sender", administrationService.getGlobalProperty("sms.sender", "")); + p.put("sms.entity.identifier", administrationService.getGlobalProperty("sms.entity.identifier", "")); + p.put("sms.appointment.walkin.template.identifier", administrationService.getGlobalProperty("sms.appointment.walkin.template.identifier", "")); + p.put("sms.appointment.teleconsultation.template.identifier", administrationService.getGlobalProperty("sms.appointment.teleconsultation.template.identifier", "")); + p.put("sms.appointment.walkin.message.template", administrationService.getGlobalProperty("sms.appointment.walkin.message.template", "")); + p.put("sms.appointment.teleconsultation.message.template", administrationService.getGlobalProperty("sms.appointment.teleconsultation.message.template", "")); + p.put("sms.params.addons", administrationService.getGlobalProperty("sms.params.addons", "")); + return p; + } + + private Properties smsSessionPropertiesFromPath() { + Path propertyFilePath = Paths.get(OpenmrsUtil.getApplicationDataDirectory(), SMS_PROPERTIES_FILENAME); + if (Files.exists(propertyFilePath)) { + Properties properties = new Properties(); + try { + log.info("Reading properties from: " + propertyFilePath); + properties.load(Files.newInputStream(propertyFilePath)); + return properties; + } catch (IOException e) { + log.error("Could not load sms properties from: " + propertyFilePath, e); + } + } else { + log.warn("No sms configuration defined at " + propertyFilePath); + } + return null; + } + + private String getDateWithSuffix(int date) { + switch (date) { + case 1: + case 21: + case 31: + return "" + date + "st"; + + case 2: + case 22: + return "" + date + "nd"; + + case 3: + case 23: + return "" + date + "rd"; + + default: + return "" + date + "th"; + } + } + +} diff --git a/api/src/main/resources/moduleApplicationContext.xml b/api/src/main/resources/moduleApplicationContext.xml index 3a40e543f..5e9bb8b3d 100644 --- a/api/src/main/resources/moduleApplicationContext.xml +++ b/api/src/main/resources/moduleApplicationContext.xml @@ -32,12 +32,20 @@ + + + + + + + +