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 @@
+
+
+
+
+
+
+
+