diff --git a/iTrust/WebRoot/auth/admin/menu.jsp b/iTrust/WebRoot/auth/admin/menu.jsp index 8ceae79a1302faa8fccc2e741e3f7826896e26a0..cdfeab9ebcff917fee0794622deacf7328308849 100644 --- a/iTrust/WebRoot/auth/admin/menu.jsp +++ b/iTrust/WebRoot/auth/admin/menu.jsp @@ -31,6 +31,18 @@ </ul> </div> </div> + +<div class="panel panel-default"> + <div class="panel-heading" anim-type="collapse" anim-target="#messaging-menu"> + <h2 class="panel-title">Messaging</h2> + </div> + <div class="panel-body" id="messaging-menu"> + <ul class="nav nav-sidebar"> + <li><a href="/iTrust/auth/admin/viewReminderOutbox.jsp">Reminder Message Outbox</a></li> + </ul> + </div> +</div> + <div class="panel panel-default"> <div class="panel-heading" anim-type="collapse" anim-target="#other-menu"> <h2 class="panel-title">Other</h2> @@ -44,6 +56,7 @@ <li><a href="/iTrust/auth/admin/sessionTimeout.jsp">Change Global Session Timeout </a></li> <li><a href="/iTrust/auth/surveyResults.jsp">Satisfaction Survey Results</a></li> <li><a href="/iTrust/auth/admin/activatePatient.jsp">Activate Patient</a></li> + <li><a href="/iTrust/auth/admin/sendAppointmentReminders.jsp">Send Appointment Reminders</a></li> </ul> </div> </div> \ No newline at end of file diff --git a/iTrust/WebRoot/auth/admin/reminderMessage.jsp b/iTrust/WebRoot/auth/admin/reminderMessage.jsp new file mode 100644 index 0000000000000000000000000000000000000000..a9f7df9ab5a859a62572af8d38293722c7f4d7ea --- /dev/null +++ b/iTrust/WebRoot/auth/admin/reminderMessage.jsp @@ -0,0 +1,74 @@ +<%@page errorPage="/auth/exceptionHandler.jsp"%> + +<%@page import="edu.ncsu.csc.itrust.action.ViewMyMessagesAction"%> +<%@page import="edu.ncsu.csc.itrust.beans.MessageBean"%> +<%@page import="java.util.List"%> + +<%@include file="/global.jsp" %> + +<% +pageTitle = "iTrust - View Message"; +%> + +<%@include file="/header.jsp" %> + +<% + ViewMyMessagesAction action = new ViewMyMessagesAction(prodDAO, loggedInMID.longValue()); + MessageBean original = null; + + loggingAction.logEvent(TransactionType.OUTBOX_VIEW, loggedInMID.longValue(), 0, ""); + + if (request.getParameter("msg") != null) { + String msgParameter = request.getParameter("msg"); + int msgIndex = 0; + try { + msgIndex = Integer.parseInt(msgParameter); + } catch (NumberFormatException nfe) { + response.sendRedirect("viewReminderOutbox.jsp"); + } + List<MessageBean> messages = null; + if (session.getAttribute("messages") != null) { + messages = (List<MessageBean>) session.getAttribute("messages"); + if(msgIndex > messages.size() || msgIndex < 0) { + msgIndex = 0; + response.sendRedirect("oops.jsp"); + } + } else { + response.sendRedirect("viewReminderOutbox.jsp"); + } + original = (MessageBean)messages.get(msgIndex); + session.setAttribute("message", original); + } + else { + response.sendRedirect("viewReminderOutbox.jsp"); + } + +%> + <div> + <table width="99%"> + <tr> + <td><b>To:</b> <%= StringEscapeUtils.escapeHtml("" + ( action.getName(original.getTo()) )) %></td> + </tr> + <tr> + <td><b>Subject:</b> <%= StringEscapeUtils.escapeHtml("" + ( original.getSubject() )) %></td> + </tr> + <tr> + <td><b>Date & Time:</b> <%= StringEscapeUtils.escapeHtml("" + ( original.getSentDate() )) %></td> + </tr> + </table> + </div> + + <table> + <tr> + <td colspan="2"><b>Message:</b></td> + </tr> + <tr> + <td colspan="2"><%= StringEscapeUtils.escapeHtml("" + ( original.getBody() )).replace("\n","<br/>") %></td> + </tr> + <tr> + <td colspan="2"><a href="viewReminderOutbox.jsp">Back</a></td> + </tr> + </table> + + +<%@include file="/footer.jsp" %> \ No newline at end of file diff --git a/iTrust/WebRoot/auth/admin/sendAppointmentReminders.jsp b/iTrust/WebRoot/auth/admin/sendAppointmentReminders.jsp new file mode 100644 index 0000000000000000000000000000000000000000..711032f0c4cf478d0a95bbc755bf61860e04bec0 --- /dev/null +++ b/iTrust/WebRoot/auth/admin/sendAppointmentReminders.jsp @@ -0,0 +1,50 @@ + +<%@taglib prefix="itrust" uri="/WEB-INF/tags.tld"%> +<%@page errorPage="/auth/exceptionHandler.jsp"%> +<%@page import="edu.ncsu.csc.itrust.action.SendRemindersAction" %> +<%@page import="edu.ncsu.csc.itrust.exception.ITrustException" %> +<%@page import="java.lang.NumberFormatException" %> + +<%@include file="/global.jsp" %> + +<% +pageTitle = "iTrust - Send Reminder Message"; +%> + +<%@include file="/header.jsp" %> + +<% + String input = request.getParameter("withinDays"); + if (input != null && !input.equals("")) { + try { + int days = Integer.valueOf(input); + if (days <= 0) { +%> + <span class="iTrustError"><%=StringEscapeUtils.escapeHtml("Provide a positive number") %></span> +<% + } + else { + SendRemindersAction action = new SendRemindersAction(prodDAO, loggedInMID.longValue()); + action.sendReminderForAppointments(days); +%> + <span class="iTrustMessage"><%=StringEscapeUtils.escapeHtml("Reminders were successfully sent") %></span> +<% + } + + } catch (NumberFormatException | ITrustException e) { +%> + <span class="iTrustError"><%=StringEscapeUtils.escapeHtml("Reminders failed to send. Please provide a positive number") %></span> +<% + } + } +%> + +<div class="page-header"><h1>Send Reminder Message</h1></div> + +<form method="post"> +<h4> Send reminders to all patients with an appointment within the next n days. Provide n: </h4> +<input type="text" name="withinDays"></td> +<input type="submit" style="font-size: 12pt; font-weight: bold;" value="Send Appointment Reminders"> +</form> + +<%@include file="/footer.jsp" %> \ No newline at end of file diff --git a/iTrust/WebRoot/auth/admin/viewReminderOutbox.jsp b/iTrust/WebRoot/auth/admin/viewReminderOutbox.jsp new file mode 100644 index 0000000000000000000000000000000000000000..d2f2618da93dbc02f634568a57b81f0081a924f4 --- /dev/null +++ b/iTrust/WebRoot/auth/admin/viewReminderOutbox.jsp @@ -0,0 +1,71 @@ +<%@page errorPage="/auth/exceptionHandler.jsp"%> + +<%@page import="java.util.List"%> + +<%@page import="edu.ncsu.csc.itrust.action.ViewMyMessagesAction"%> +<%@page import="edu.ncsu.csc.itrust.beans.MessageBean"%> +<%@page import="edu.ncsu.csc.itrust.dao.DAOFactory"%> + +<%@include file="/global.jsp" %> + +<% +pageTitle = "iTrust - View My Sent Messages"; +%> + +<%@include file="/header.jsp" %> + +<div align=center> + <h2>Sent Reminders</h2> + <% + loggingAction.logEvent(TransactionType.OUTBOX_VIEW, loggedInMID.longValue(), loggedInMID.longValue(), ""); + + ViewMyMessagesAction action = new ViewMyMessagesAction(prodDAO, 9000000009L); + List<MessageBean> messages = null; + if(request.getParameter("sortby") != null) { + if(request.getParameter("sortby").equals("name")) { + if(request.getParameter("sorthow").equals("asce")) { + messages = action.getAllMySentMessagesNameAscending(); + } else { + messages = action.getAllMySentMessagesNameDescending(); + } + } else if(request.getParameter("sortby").equals("time")) { + if(request.getParameter("sorthow").equals("asce")) { + messages = action.getAllMySentMessagesTimeAscending(); + } else { + messages = action.getAllMySentMessages(); + } + } + } + else { + messages = action.getAllMySentMessages(); + } + session.setAttribute("messages", messages); + if (messages.size() > 0) { %> + <br /> + <table class="fancyTable"> + <tr> + <th>To</th> + <th>Subject</th> + <th>Sent</th> + <th></th> + </tr> + <% int index = 0; %> + <% for(MessageBean message : messages) { %> + <tr <%=(index%2 == 1)?"class=\"alt\"":"" %>> + <td><%= StringEscapeUtils.escapeHtml("" + ( action.getName(message.getTo()) )) %></td> + <td><%= StringEscapeUtils.escapeHtml("" + ( message.getSubject() )) %></td> + <td><%= StringEscapeUtils.escapeHtml("" + ( message.getSentDate() )) %></td> + <td><a href="reminderMessage.jsp?msg=<%= StringEscapeUtils.escapeHtml("" + ( index )) %>">Read</a></td> + </tr> + <% index ++; %> + <% } %> + </table> + <% } else { %> + <div> + <i>No reminders sent</i> + </div> + <% } %> + <br /> +</div> + +<%@include file="/footer.jsp" %> \ No newline at end of file diff --git a/iTrust/src/edu/ncsu/csc/itrust/action/SendRemindersAction.java b/iTrust/src/edu/ncsu/csc/itrust/action/SendRemindersAction.java new file mode 100644 index 0000000000000000000000000000000000000000..ef8b0597ed3a9bfcdec4d7c3a3c92257ba418464 --- /dev/null +++ b/iTrust/src/edu/ncsu/csc/itrust/action/SendRemindersAction.java @@ -0,0 +1,67 @@ +package edu.ncsu.csc.itrust.action; + +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + +import edu.ncsu.csc.itrust.beans.ApptBean; +import edu.ncsu.csc.itrust.beans.MessageBean; +import edu.ncsu.csc.itrust.beans.PersonnelBean; +import edu.ncsu.csc.itrust.dao.DAOFactory; +import edu.ncsu.csc.itrust.dao.mysql.ApptDAO; +import edu.ncsu.csc.itrust.dao.mysql.PersonnelDAO; +import edu.ncsu.csc.itrust.exception.DBException; +import edu.ncsu.csc.itrust.exception.FormValidationException; +import edu.ncsu.csc.itrust.exception.ITrustException; +import java.util.*; + +public class SendRemindersAction { + public final long systemReminderMID; + private long loggedInMID; + private ApptDAO apptDAO; + private SendMessageAction smAction; + + public SendRemindersAction(DAOFactory factory, long loggedInMID) throws DBException { + PersonnelDAO personnelDAO = factory.getPersonnelDAO(); + List<PersonnelBean> personnels = personnelDAO.searchForPersonnelWithName("System", "Reminder"); + this.systemReminderMID = personnels.get(0).getMID(); + this.loggedInMID = loggedInMID; + this.apptDAO = factory.getApptDAO(); + this.smAction = new SendMessageAction(factory, systemReminderMID); + } + + public void sendReminder(ApptBean aBean) throws ITrustException, SQLException, FormValidationException { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime date = aBean.getDate().toLocalDateTime(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm, MMM d"); + + MessageBean message = new MessageBean(); + message.setTo(aBean.getPatient()); + message.setFrom(systemReminderMID); + message.setSubject(String.format("Reminder: upcoming appointment in %d day(s)", now.truncatedTo(ChronoUnit.DAYS).until(date.truncatedTo(ChronoUnit.DAYS), ChronoUnit.DAYS))); + message.setBody(String.format("You have an appointment on %s with Dr. %s", date.format(formatter), smAction.getPersonnelName(aBean.getHcp()))); + message.setSentDate(Timestamp.valueOf(now)); + + smAction.sendMessage(message); + } + + public int sendReminderForAppointments(int numDays) throws ITrustException { + List<ApptBean> appointments = null; + try { + appointments = apptDAO.getUpcomingAppts(numDays); + for (ApptBean appt : appointments) { + sendReminder(appt); + } + return appointments.size(); + } catch (DBException e) { + throw new ITrustException("DB Error in sending reminders."); + } catch (SQLException e) { + throw new ITrustException("SQL Error in sending reminders."); + } catch (FormValidationException e) { + throw new ITrustException("Form Validation Error in sending reminders."); + } + + } +} \ No newline at end of file diff --git a/iTrust/src/edu/ncsu/csc/itrust/dao/mysql/ApptDAO.java b/iTrust/src/edu/ncsu/csc/itrust/dao/mysql/ApptDAO.java index 056528376184ac2e813fc4b9af7f2361862398c6..7b91089e577109761c010f43d9c6ddbb65f0becf 100644 --- a/iTrust/src/edu/ncsu/csc/itrust/dao/mysql/ApptDAO.java +++ b/iTrust/src/edu/ncsu/csc/itrust/dao/mysql/ApptDAO.java @@ -328,5 +328,35 @@ public class ApptDAO { } + } + + /** + * Get all upcoming appointments within n days + * @param numDays Number of days after current date within which to find all appointments + * @return ApptBean List of upcoming appointments + */ + public List<ApptBean> getUpcomingAppts(int numDays) throws SQLException, DBException { + Connection conn = null; + PreparedStatement pstring = null; + try { + conn = factory.getConnection(); + + pstring = conn.prepareStatement( + "SELECT * FROM appointment WHERE " + /*" sched_date.after(?)=TRUE AND sched_date.before(?)=TRUE");*/ + "DATE(sched_date)<=DATE_ADD(CURRENT_DATE, INTERVAL ? DAY) AND " + //sched_date day is before or at (numDays) days from now + "sched_date>=CURRENT_DATE"); // sched_date is after today + + pstring.setInt(1, numDays); + + final ResultSet results = pstring.executeQuery(); + final List<ApptBean> abList = this.abloader.loadList(results); + results.close(); + pstring.close(); + return abList; + } catch (SQLException e) { + throw new DBException(e); + } finally { + DBUtil.closeConnection(conn, pstring); + } } } diff --git a/iTrust/test/edu/ncsu/csc/itrust/selenium/SendRemindersTest.java b/iTrust/test/edu/ncsu/csc/itrust/selenium/SendRemindersTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6dfa3c967de80820686df6ee01c8aa548772a4af --- /dev/null +++ b/iTrust/test/edu/ncsu/csc/itrust/selenium/SendRemindersTest.java @@ -0,0 +1,97 @@ +package edu.ncsu.csc.itrust.selenium; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public class SendRemindersTest extends iTrustSeleniumTest{ + + protected WebDriver driver; + + @Override + protected void setUp() throws Exception { + super.setUp(); + gen.clearAllTables(); + gen.standardData(); + } + + // Test sending reminders + public void testSendReminder() throws Exception { + // Login as admin + driver = login("9000000001", "pw"); + assertEquals("iTrust - Admin Home", driver.getTitle()); + driver.findElement(By.linkText("Send Appointment Reminders")).click(); + + // Send reminders + driver.findElement(By.name("withinDays")).sendKeys("10"); + driver.findElement(By.name("withinDays")).submit(); + assertEquals("Reminders were successfully sent", + driver.findElement(By.className("iTrustMessage")).getText()); + + // Create timestamp + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + Date date = new Date(); + String stamp = dateFormat.format(date); + + // Check admin reminders outbox + driver.findElement(By.linkText("Reminder Message Outbox")).click(); + assertNotNull(driver.findElement(By.className("fancyTable"))); + assertTrue(driver.getPageSource().contains(stamp)); + + // Check a reminder message + driver.findElement(By.linkText("Read")).click(); + assertTrue(driver.getPageSource().contains(stamp)); + + // Logout admin and login as patient + List<WebElement> links = driver.findElements(By.tagName("a")); + int count = 0; + for(int i = 0; i < links.size(); i++) { + if(links.get(i).getAttribute("href").contains("logout")) + { + count = i; + break; + } + } + links.get(count).click(); + driver = login("5", "pw"); + + // Check patient inbox + driver.findElement(By.linkText("Message Inbox")).click(); + int index = 1; + WebElement baseTable = driver.findElement(By.cssSelector(".display.fTable")); + List<WebElement> tableRows = baseTable.findElements(By.tagName("tr")); + assertTrue(tableRows.get(index).getText().contains("System Reminder")); + assertTrue(tableRows.get(index).getText().contains("Reminder: upcoming appointment")); + index++; + assertTrue(tableRows.get(index).getText().contains("Reminder: upcoming appointment")); + assertTrue(tableRows.get(index).getText().contains(stamp)); + } + + // Test invalid number of days input for sending reminders + public void testInvalidSendReminder() throws Exception { + + // Login as admin + driver = login("9000000001", "pw"); + assertEquals("iTrust - Admin Home", driver.getTitle()); + driver.findElement(By.linkText("Send Appointment Reminders")).click(); + + // Send reminder with negative days + driver.findElement(By.name("withinDays")).sendKeys("-4"); + driver.findElement(By.name("withinDays")).submit(); + assertEquals("Provide a positive number", + driver.findElement(By.className("iTrustError")).getText()); + + // Send reminder with non-numberic days + driver.findElement(By.name("withinDays")).sendKeys("Hello"); + driver.findElement(By.name("withinDays")).submit(); + assertEquals("Reminders failed to send. Please provide a positive number", + driver.findElement(By.className("iTrustError")).getText()); + } + + + + +} \ No newline at end of file diff --git a/iTrust/test/edu/ncsu/csc/itrust/unit/action/SendRemindersActionTest.java b/iTrust/test/edu/ncsu/csc/itrust/unit/action/SendRemindersActionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a91f84a430de111b88d1f79c3fb46a51fb3b509c --- /dev/null +++ b/iTrust/test/edu/ncsu/csc/itrust/unit/action/SendRemindersActionTest.java @@ -0,0 +1,68 @@ +package edu.ncsu.csc.itrust.unit.action; + +import edu.ncsu.csc.itrust.action.SendRemindersAction; +import edu.ncsu.csc.itrust.beans.ApptBean; +import edu.ncsu.csc.itrust.beans.MessageBean; +import edu.ncsu.csc.itrust.dao.DAOFactory; +import edu.ncsu.csc.itrust.dao.mysql.MessageDAO; +import edu.ncsu.csc.itrust.exception.FormValidationException; +import edu.ncsu.csc.itrust.exception.ITrustException; +import edu.ncsu.csc.itrust.unit.datagenerators.TestDataGenerator; +import edu.ncsu.csc.itrust.unit.testutils.TestDAOFactory; +import junit.framework.TestCase; + +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; + + +public class SendRemindersActionTest extends TestCase { + + private DAOFactory factory; + private MessageDAO messageDAO; + private SendRemindersAction srAction; + private TestDataGenerator gen; + private long patientId; + private long hcpId; + + @Override + protected void setUp() throws Exception { + super.setUp(); + gen = new TestDataGenerator(); + gen.clearAllTables(); + gen.standardData(); + + this.patientId = 2L; + this.hcpId = 9000000000L; + this.factory = TestDAOFactory.getTestInstance(); + this.messageDAO = new MessageDAO(this.factory); + this.srAction = new SendRemindersAction(this.factory, this.hcpId); + } + + public void testSendRemindersAction() throws ITrustException + { + int numberOfAppts = srAction.sendReminderForAppointments(10); + assertTrue(numberOfAppts >= 5); + } + + public void testSendReminders() throws ITrustException, SQLException, FormValidationException { + ApptBean aBean = new ApptBean(); + + aBean.setApptType("TEST"); + aBean.setPatient(patientId); + aBean.setHcp(hcpId); + aBean.setDate(Timestamp.valueOf(LocalDateTime.now().plusDays(4))); + + List<MessageBean> mbListBefore = messageDAO.getMessagesFor(patientId); + + srAction.sendReminder(aBean); + + List<MessageBean> mbList = messageDAO.getMessagesFor(patientId); + + assertEquals(mbList.size(), mbListBefore.size() + 1); + MessageBean mBeanDB = mbList.get(0); + assertEquals("Reminder: upcoming appointment in 4 day(s)", mBeanDB.getSubject()); + } + +} \ No newline at end of file diff --git a/iTrust/test/edu/ncsu/csc/itrust/unit/dao/appointment/ApptDAOTest.java b/iTrust/test/edu/ncsu/csc/itrust/unit/dao/appointment/ApptDAOTest.java index f20dd33a2d9a35329be32bef858fcd52fa2cca3c..77a4579abd8c080646cb858ae14b8fbbcf9903fc 100644 --- a/iTrust/test/edu/ncsu/csc/itrust/unit/dao/appointment/ApptDAOTest.java +++ b/iTrust/test/edu/ncsu/csc/itrust/unit/dao/appointment/ApptDAOTest.java @@ -17,6 +17,8 @@ public class ApptDAOTest extends TestCase { private DAOFactory factory = TestDAOFactory.getTestInstance(); private ApptDAO apptDAO = factory.getApptDAO(); + private ApptBean[] appts = null; + private ApptBean a1; private ApptBean a2; private ApptBean a3; @@ -30,8 +32,6 @@ public class ApptDAOTest extends TestCase { gen.clearAllTables(); gen.appointmentType(); - - a1 = new ApptBean(); a1.setDate(new Timestamp(new Date().getTime())); a1.setApptType("Ultrasound"); @@ -49,6 +49,7 @@ public class ApptDAOTest extends TestCase { a3.setApptType("Ultrasound"); a3.setHcp(doctorMID); a3.setPatient(patientMID); + } public void testAppointment() throws Exception { @@ -148,5 +149,20 @@ public class ApptDAOTest extends TestCase { assertEquals(30, type.getDuration()); assertEquals("Ultrasound", type.getName()); } + + // Test adding and retreiving upcoming appointments within n days + public void testGetUpcomingAppts() throws Exception { + // Edge case: empty database + List<ApptBean> upcomingAppts = apptDAO.getUpcomingAppts(30); + assertEquals(0, upcomingAppts.size()); + + // Test returning upcoming appts B + apptDAO.scheduleAppt(a1); + apptDAO.scheduleAppt(a2); + apptDAO.scheduleAppt(a3); + + upcomingAppts = apptDAO.getUpcomingAppts(1); + assertEquals(3, upcomingAppts.size()); + } }