From c4728ba4773e0a7152c8ebae0da2593e4018fadb Mon Sep 17 00:00:00 2001 From: aschmidt34 <124093649+aschmidt34@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:24:29 -0600 Subject: [PATCH] 24.3 fb notification revamps (#685) * - In Blood Draws Today (All, Animal Care, and Vet Staff), I changed 'Assignment Status' column header to 'Unassigned' and 'Completion Status' to 'Incomplete'. I also sorted all results so Incomplete draws show up first. - In BloodDrawReviewTriggerNotification, I made it so warnings only send when the draw has a date of today. - In BloodDrawReviewDailyNotification, I added a check to verify that there are no upcoming blood draws that will overdraw. - TODO: After testing this on test servers, make sure to remove test dates in BloodDrawsTodayAll and BloodDrawReviewDailyNotification. * Migrated automated test changes from 23.11 to 24.3. 23.11_notificationRevamps is now ready to be deleted. * Added blood overdraw trigger notification. * Registered new notification. * Updated code so message sends when testing/running in browser. Updated log for wnprc_triggers.js for when function is run to help debug until issue with webpack generation is fixed. * Updated overdraw notification. Notification is now created everytime blood is updated. Logic is run inside notification to determine if current draw is an overdraw. If it is an overdraw, the message is sent. * Fixed issue where 'Animal Replacement Fee' showed incorrectly in the revamped Death Notification. This occurred when the Type of Death resulted in a fee in the table 'ehr_lookups' > 'death_cause', but the 'prepaid' field was empty in the table 'study' > 'demographics'. This was due to labkeys table lookup resulting in a string 'null' instead of a real null when using the TableSelector. My new notifications use a new query function i wrote, but this Death Notification used the old TableSelector method. * Reordered messages in BloodDrawReviewDailyNotification.java so overdraws are listed first. * -NotificationToolkit.java: Updated getWeightFromAnimalID() and getSexFromAnimalID() so they use new query function getTableMultiRowMultiColumnWithFieldKeys() instead of old function getTableRowAsList(). Also moved DeathNecropsyObject and DeathDemographicObject to DeathNotificationRevamp.java. - DeathNotificationRevamp.java: Moved DeathNecropsyObject and DeathDemographicObject here from NotificationToolkit.java. Also cleaned up these objects and updated these them to use the new query getTableMultiRowMultiColumnWithFieldKeys. * Removed automated tests that don't aren't finished. * Added 2 new functions to NotificationToolkit for creating URL's. This is to avoid hardcoding URL's per new LabKey policy. Implimented these 2 new functions in DeathNotificationRevamp.java for getting the necropsy and animal abstract URL's. * Adding automated tests for notifications. * - WNPRC_EHRTest.java: Added notification setup function, multiple notification check functions, and a toolkit object containing reusable test functions that are commonly used when writing different tests. - BloodOverdrawTriggerNotification.java & BloodDrawReviewTriggerNotification.java: Added a reset function to clear the data after notification is triggered. This is because there are null checks that don't work when artifacts are left over from previous instances. - NotificationToolkit.java: Added null check to checkIfBloodDrawIsOverdraw() because empty data was crashing the function. * - DeathNotificationRevamp.java: Added a null check to make sure class doesn't cause a failure if there's no taskID in the row returned from the query. - WNPRC_EHRTest.java: Added a check for death notification and prenatal death notification. * Added null check before sending blood overdraw trigger manually. The built in labkey notification setup just doesn't send a notification if there's a null body, but for my trigger notifications where I send it manually, I should check for null. * Moved the automated tests to the end of the list and removed the @Test flag. * Disabled site-wide notifications. Also disable individual notifications. * Re-added 'test' tag before function. Originally removed this because Marty said it was not necessary if I called the function in doSetup(), but it's not showing up in the console log anymore. Trying to re-add this. * Added try/catch for insertValueIntoBloodBilledByDataset due to duplicate data being uploaded. Looks like labkey's loadBloodBilledByLookup() already uploads the same values. * Added try/catch for remaining functions that insert into datasets. * Changed a query function in checkIfAnimalIsAlive() so it no longer references my functions that use QviewObject. Need to evenutally phase out all these functions as my new query functions work much better. * Removing migrated alerts from ehrcron to Java based notifications * Added comment to leave commented-out code block alone. This will be used for future notifications, and it's very tough coding this correctly in the right order. Please do not delete. * Removed function call for notification tests in doSetup(). This was recommended by Binal via a LabKey ticket; it caused my test to run twice since the @test annotation already exists. * - TriggerScriptHelper.java: Added trigger call for new AnimalRequestUpdateNotificationRevamp notification. Also moved call for old notification inside if/else statement so 'on/off' status is checked before sending. Will delete the old call after vefifying new version works well. - WNPRC_EHRModule.java: Registered new EmptyNotificationRevamp and AnimalRequestUpdateNotificationRevamp notifications. - AdminAlertsNotificationRevamp.java: Updated this notification so the days of the week are displayed in the correct order. Also fixed an error with the wrong results being queried (needed to update the filter). - AnimalRequestNotificationRevamp.java: Added test data to be set when notification is triggered from 'Run Report in Browser'. Also added a resetClass function. - AnimalRequestUpdateNotificationRevamp.java: Created this new revamped notification. - ColonyAlertsLiteNotificationRevamp.java: Updated this notification to use the new EmptyNotificationRevamp notification. This is sent instead of the original notification if there is no data to be sent. - EmptyNotificationRevamp.java: Created this new revamped notification. This is sent when certain notifications have no data to send. This is to prevent users from receiving empty emails, but allows Daniel and I to see that the notifications are still being sent. - NotificationToolkit.java: Added function to send the new EmptyNotificationRevamp.java notification. * - TriggerScriptHelper.java: Added 'sendManually' function call that I had forgotten. - BloodDrawReviewDailyNotification.java: Added 2 extra checks requested by blood draw team. - BloodDrawsTodayAnimalCare.java: Updated this to use the new dummy notification when there is no data to be sent. * - animal_requests.js: Added fix for fatal issue with qcstatus being different in dataset and form. - ColonyAlertsNotificationRevamp.java: Added extra query requested by Kim. This query checks all animals in the 'Assignments' dataset and returns any where the project has expired or the protocol has deactivated. * - WNPRC_EHRModule.java: Registered new notification. - TreatmentAlertsNotificationRevamp.java: Created new notification. - BloodDrawsTodayAll.java: Added 'incomplete count' to the notification (as requested by blood draw team). - BloodDrawsTodayVetStaff.java: Added functionality so message does not send when there is no data (as requested by blood draw team). * Removed joda time import statement because it was unused. --------- Co-authored-by: F. Daniel Nicolalde --- .../queries/wnprc/animal_requests.js | 20 +- .../org/labkey/wnprc_ehr/WNPRC_EHRModule.java | 3 +- .../notification/BloodDrawsTodayAll.java | 54 ++- .../notification/BloodDrawsTodayVetStaff.java | 10 +- .../ColonyAlertsNotificationRevamp.java | 102 ++++ .../TreatmentAlertsNotificationRevamp.java | 438 ++++++++++++++++++ 6 files changed, 598 insertions(+), 29 deletions(-) create mode 100644 WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/TreatmentAlertsNotificationRevamp.java diff --git a/WNPRC_EHR/resources/queries/wnprc/animal_requests.js b/WNPRC_EHR/resources/queries/wnprc/animal_requests.js index 2c9cfa381..81cac97da 100644 --- a/WNPRC_EHR/resources/queries/wnprc/animal_requests.js +++ b/WNPRC_EHR/resources/queries/wnprc/animal_requests.js @@ -90,6 +90,24 @@ function onAfterInsert(helper,errors,row){ function onAfterUpdate(helper,errors,row,oldRow){ var rowid = row.rowId; var hostName = 'https://' + LABKEY.serverName; - console.log ("animal_requests.js: New request updated, rowid: "+ rowid); + console.log("animal_requests.js: New request updated, rowid: "+ rowid); + + if ("QCStateLabel" in row) { + delete row.QCState; + row["qcstate"] = row["QCStateLabel"]; + delete row.QCStateLabel; + } + if ("QCStateLabel" in oldRow) { + oldRow["qcstate"] = oldRow["QCStateLabel"]; + delete oldRow.QCStateLabel; + } + + if ("_publicData" in row) { + delete row._publicData; + } + if ("_publicData" in oldRow) { + delete oldRow._publicData; + } + WNPRC.Utils.getJavaHelper().sendAnimalRequestNotificationUpdate(rowid, row, oldRow, hostName); } diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRModule.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRModule.java index 7c9832694..abdebd68e 100644 --- a/WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRModule.java +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRModule.java @@ -392,7 +392,8 @@ public void registerNotifications() { new ColonyAlertsLiteNotificationRevamp(this), new BloodOverdrawTriggerNotification(this), new EmptyNotificationRevamp(this), - new AnimalRequestUpdateNotificationRevamp(this) + new AnimalRequestUpdateNotificationRevamp(this), + new TreatmentAlertsNotificationRevamp(this) ); for (Notification notification : notifications) diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayAll.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayAll.java index 8030b9204..e143099c2 100644 --- a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayAll.java +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayAll.java @@ -81,18 +81,17 @@ public String getMessageBodyHTML(Container c, User u) { // Prints all tables. if (myBloodDrawNotificationObject.resultsByArea.isEmpty()) { - messageBody.append("There are no scheduled blood draws for this group today."); + messageBody.append("There are no scheduled blood draws today."); } else { + messageBody.append("

REMAINING INCOMPLETE TOTALS:
\n"); + messageBody.append("Animal Care: " + myBloodDrawNotificationObject.numIncompleteAnimalCare + "
\n"); + messageBody.append("Research Staff: " + myBloodDrawNotificationObject.numIncompleteResearchStaff + "
\n"); + messageBody.append("SPI: " + myBloodDrawNotificationObject.numIncompleteSPI + "
\n"); + messageBody.append("Vet Staff: " + myBloodDrawNotificationObject.numIncompleteVetStaff + "

"); messageBody.append(myBloodDrawNotificationObject.printTablesAsHTML()); } -// // Creates table. -// String[] myTableColumns = new String[]{"Id", "Blood Remaining", "Project Assignment", "Completion Status", "Group", "Other Groups Drawing Blood Today"}; -// NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(myTableColumns, myBloodDrawNotificationObject.myTableData); -// myTable.rowColors = myBloodDrawNotificationObject.myTableRowColors; -// messageBody.append(myTable.createBasicHTMLTable()); - return messageBody.toString(); } @@ -105,8 +104,11 @@ public static class BloodDrawsTodayObject { NotificationToolkit.DateToolkit dateToolkit = new NotificationToolkit.DateToolkit(); ArrayList myTableData = new ArrayList<>(); // List of all blood draws as [[id, blood remaining, project assignment, completion status, assigned to]] - // ArrayList myTableRowColors = new ArrayList<>(); // List of all row colors (same length as myTableData). HashMap>> resultsByArea = new HashMap<>(); // Area(Room(List of draws)) + Integer numIncompleteSPI = 0; + Integer numIncompleteAnimalCare = 0; + Integer numIncompleteVetStaff = 0; + Integer numIncompleteResearchStaff = 0; //Gets all info for the BloodDrawNotificationObject. @@ -145,8 +147,10 @@ public static class BloodDrawsTodayObject { // Updates id. myCurrentRow[0] = result.get("id"); + // Updates blood remaining. myCurrentRow[1] = result.get("BloodRemaining/AvailBlood"); + // Updates project status (this checks if animal is assigned to a project). if (!result.get("qcstate/label").equals("Request: Denied") && !result.get("projectStatus").isEmpty()) { myCurrentRow[2] = "UNASSIGNED"; @@ -154,6 +158,7 @@ public static class BloodDrawsTodayObject { else { myCurrentRow[2] = ""; } + // Updates completion status (this checks if blood draw has been completed). if (!result.get("qcstate/label").equals("Completed")) { myCurrentRow[3] = "INCOMPLETE"; @@ -161,8 +166,10 @@ public static class BloodDrawsTodayObject { else { myCurrentRow[3] = ""; } + // Updates the current group assigned to this animal. myCurrentRow[4] = result.get("billedby/title"); + // Updates the current area. if (!result.get("Id/curLocation/area").isEmpty()) { myCurrentRow[6] = result.get("Id/curLocation/area"); @@ -170,6 +177,7 @@ public static class BloodDrawsTodayObject { else { myCurrentRow[6] = "Unknown Area"; } + // Updates the current room. if (!result.get("Id/curLocation/room").isEmpty()) { myCurrentRow[7] = result.get("Id/curLocation/room"); @@ -191,18 +199,6 @@ else if (availBlood <= bloodThreshold) { myCurrentRow[8] = "orange"; } } -// String currentRowColor = "white"; -// if (!result.get("BloodRemaining/AvailBlood").isEmpty()) { -// Float availBlood = Float.parseFloat(result.get("BloodRemaining/AvailBlood")); -// if (availBlood <= 0) { -// // If blood draw is over limit, color it red. -// currentRowColor = "red"; -// } -// else if (availBlood <= bloodThreshold) { -// // If blood draw is over threshold limit, color it orange. -// currentRowColor = "orange"; -// } -// } // Adds the current row to myTableData (based on group being queried). if (assignmentGroup.equals("animalCare")) { @@ -234,6 +230,24 @@ else if (assignmentGroup.equals("vetStaff")) { myTableData.add(myCurrentRow); } } + + // Updates number of incomplete draws. + if (assignmentGroup.equals("all")) { + if (myCurrentRow[3].equals("INCOMPLETE")) { + if (result.get("billedby/title").equals("SPI")) { + numIncompleteSPI++; + } + else if (result.get("billedby/title").equals("Animal Care")) { + numIncompleteAnimalCare++; + } + else if (result.get("billedby/title").equals("Research Staff")) { + numIncompleteResearchStaff++; + } + else if (result.get("billedby/title").equals("Vet Staff")) { + numIncompleteVetStaff++; + } + } + } } //Goes through each draw to find draws scheduled for more than one group, then updates myTableData with information. diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayVetStaff.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayVetStaff.java index 35de535fd..a4ebcd972 100644 --- a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayVetStaff.java +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/BloodDrawsTodayVetStaff.java @@ -69,17 +69,13 @@ public String getMessageBodyHTML(Container c, User u) { // Creates table. if (myBloodDrawNotificationObject.resultsByArea.isEmpty()) { - messageBody.append("There are no scheduled blood draws for this group today."); + notificationToolkit.sendEmptyNotificationRevamp(c, u, "Blood Draws Today (Vet Staff)"); + return null; } else { messageBody.append(myBloodDrawNotificationObject.printTablesAsHTML()); + return messageBody.toString(); } -// String[] myTableColumns = new String[]{"Id", "Blood Remaining", "Project Assignment", "Completion Status", "Group", "Other Groups Drawing Blood Today"}; -// NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(myTableColumns, myBloodDrawNotificationObject.myTableData); -// myTable.rowColors = myBloodDrawNotificationObject.myTableRowColors; -// messageBody.append(myTable.createBasicHTMLTable()); - - return messageBody.toString(); } } diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/ColonyAlertsNotificationRevamp.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/ColonyAlertsNotificationRevamp.java index bda4861a3..872a428e8 100644 --- a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/ColonyAlertsNotificationRevamp.java +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/ColonyAlertsNotificationRevamp.java @@ -27,6 +27,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -41,6 +43,8 @@ import javax.script.ScriptEngine; +import static org.labkey.api.search.SearchService._log; + /** * Created by Alex Schmidt on 12/27/23. @@ -97,6 +101,12 @@ public String getMessageBodyHTML(Container c, User u) { final StringBuilder messageBody = new StringBuilder(); ColonyInformationObject myColonyAlertObject = new ColonyInformationObject(c, u, "colonyAlert"); + // Creates CSS. + messageBody.append(styleToolkit.beginStyle()); + messageBody.append(styleToolkit.setBasicTableStyle()); + messageBody.append(styleToolkit.setHeaderRowBackgroundColor("#d9d9d9")); + messageBody.append(styleToolkit.endStyle()); + //Begins message info. messageBody.append("

This email contains a series of automatic alerts about the colony. It was run on: " + dateToolkit.getCurrentTime() + ".

"); @@ -373,6 +383,19 @@ public String getMessageBodyHTML(Container c, User u) { messageBody.append(notificationToolkit.createHyperlink("

Click here to view them
\n", myColonyAlertObject.totalFinalizedRecordsWithFutureDatesURLView)); messageBody.append("


\n"); } + //38. Find any animals assigned to an inactivated project or deactivated protocol. + if (!myColonyAlertObject.animalsWithInvalidProjectOrProtocol.isEmpty()) { + // Creates HTML table to return. + String[] myTableColumns = new String[]{"Id", "Project", "Project End Date", "Protocol", "Protocol End Date"}; + NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(myTableColumns, myColonyAlertObject.animalsWithInvalidProjectOrProtocol); + + // Displays message. + messageBody.append("WARNING: There are " + myColonyAlertObject.animalsWithInvalidProjectOrProtocol.size() + " living animals with inactivated projects or deactivated protocols.
"); + messageBody.append(myTable.createBasicHTMLTable()); + messageBody.append(notificationToolkit.createHyperlink("

Click here to view all Assignments.
\n", myColonyAlertObject.animalsWithInvalidProjectOrProtocolURLView)); + messageBody.append("


\n"); + + } //Returns string. return messageBody.toString(); @@ -469,6 +492,8 @@ public ColonyInformationObject(Container currentContainer, User currentUser, Str getPrenatalDeathsInLastFiveDays(); //37. Find the total finalized records with future dates. getTotalFinalizedRecordsWithFutureDates(); + //38. Find any animals assigned to an inactivated project or deactivated protocol. + getAnimalsWithInvalidProjectOrProtocol(); } else if (alertType.equals("colonyManagement")) { // 1. Find all living animals without a weight. @@ -1356,6 +1381,83 @@ private void getProtocolsNearingAnimalLimitPercentage() { this.protocolsNearingAnimalLimitPercentage = returnArray; this.protocolsNearingAnimalLimitPercentageURLView = viewQueryURL; } + + // Find any animals assigned to an inactivated project or deactivated protocol. + ArrayList animalsWithInvalidProjectOrProtocol = new ArrayList<>(); // + String animalsWithInvalidProjectOrProtocolURLView; + private void getAnimalsWithInvalidProjectOrProtocol() { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/Dataset/Demographics/calculated_status", "Alive", CompareType.EQUAL); +// myFilter.addCondition("project/protocol", "", CompareType.NONBLANK); + // Gets columns to retrieve. + String[] targetColumns = new String[]{"id", "project", "project/protocol", "project/enddate", "project/protocol/enddate"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "Assignment", myFilter, null, targetColumns); + + // Sets up a Try/Catch block to catch date parsing errors. + try { + // Sets variables. + SimpleDateFormat myFormat = new SimpleDateFormat("yyyy-MM-dd"); + Date formattedCurrentDate = myFormat.parse(dateToolkit.getCurrentTime()); + + for (HashMap result : returnArray) { + // 0: ID + // 1: Project + // 2: Project End Date + // 3: Protocol + // 4: Protocol End Date + String[] myCurrentRow = new String[5]; + myCurrentRow[1] = ""; + myCurrentRow[2] = ""; + myCurrentRow[3] = ""; + myCurrentRow[4] = ""; + + // Retrieves row data. + String currentID = result.get("id"); + String currentProject = result.get("project"); + String currentProtocol = result.get("project/protocol"); + String currentProjectEnd = result.get("project/enddate"); + String currentProtocolEnd = result.get("project/protocol/enddate"); + Boolean projectOrProtocolExpired = false; + + // Adds id. + myCurrentRow[0] = currentID; + // Checks project. + if (currentProject != null && currentProjectEnd != null) { + if (!currentProject.isEmpty() && !currentProjectEnd.isEmpty()) { + Date formattedProjectEnd = myFormat.parse(currentProjectEnd); + if (formattedCurrentDate.compareTo(formattedProjectEnd) > 0) { + myCurrentRow[1] = currentProject; + myCurrentRow[2] = currentProjectEnd; + projectOrProtocolExpired = true; + } + } + } + // Checks protocol. + if (currentProtocol != null && currentProtocolEnd != null) { + if (!currentProtocol.isEmpty() && !currentProtocolEnd.isEmpty()) { + Date formattedProtocolEnd = myFormat.parse(currentProtocolEnd); + if (formattedCurrentDate.compareTo(formattedProtocolEnd) > 0) { + myCurrentRow[3] = currentProtocol; + myCurrentRow[4] = currentProtocolEnd; + projectOrProtocolExpired = true; + } + } + } + + // Adds row to return list if there is an expired project or protocol. + if (projectOrProtocolExpired) { + animalsWithInvalidProjectOrProtocol.add(myCurrentRow); + } + } + } + catch (ParseException e) { + _log.error("There was a parsing exception for: ColonyAlertsNotificationRevamp->getAnimalsWithInvalidProjectOrProtocol", e); + } + + // Creates url link to the assignments table. + this.animalsWithInvalidProjectOrProtocolURLView = notificationToolkit.createQueryURL(c, "execute", "study", "Assignment", null); + } } } diff --git a/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/TreatmentAlertsNotificationRevamp.java b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/TreatmentAlertsNotificationRevamp.java new file mode 100644 index 000000000..13412570a --- /dev/null +++ b/WNPRC_EHR/src/org/labkey/wnprc_ehr/notification/TreatmentAlertsNotificationRevamp.java @@ -0,0 +1,438 @@ +package org.labkey.wnprc_ehr.notification; + +import org.labkey.api.data.CompareType; +import org.labkey.api.data.Container; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.Sort; +import org.labkey.api.module.Module; +import org.labkey.api.security.User; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +public class TreatmentAlertsNotificationRevamp extends AbstractEHRNotification { + //Class Variables + NotificationToolkit notificationToolkit = new NotificationToolkit(); + NotificationToolkit.DateToolkit dateToolkit = new NotificationToolkit.DateToolkit(); + NotificationToolkit.StyleToolkit styleToolkit = new NotificationToolkit.StyleToolkit(); + + + + + + // Constructors + /** + * This constructor is used to register the notification in WNPRC_EHRModule.java. + * @param owner + */ + public TreatmentAlertsNotificationRevamp(Module owner) {super(owner);} + + + + + + // Notification Details + @Override + public String getName() { + return "Treatment Alerts Notification Revamp"; + } + @Override + public String getDescription() { + return "This email contains any scheduled treatments not marked as completed."; + } + @Override + public String getEmailSubject(Container c) { + return "Treatment Alerts: " + dateToolkit.getCurrentTime(); + } + @Override + public String getScheduleDescription() { + return "Daily at 6:00AM, 10:00AM, 1:00PM, 3:00PM, 5:00PM, 7:00PM"; + } + @Override + public String getCronString() { + return notificationToolkit.createCronString("0", "6,10,13,15,17,19", "*"); + } + @Override + public String getCategory() { + return "Revamped Notifications"; + } + + + + + + //Message Creation + @Override + public String getMessageBodyHTML(Container c, User u) { + + // Creates variables & gets data. + final StringBuilder messageBody = new StringBuilder(); + TreatmentAlertsObject myTreatmentAlertsObject = new TreatmentAlertsObject(c, u); + + // Creates CSS. + messageBody.append(styleToolkit.beginStyle()); + messageBody.append(styleToolkit.setBasicTableStyle()); + messageBody.append(styleToolkit.setHeaderRowBackgroundColor("#d9d9d9")); + messageBody.append(styleToolkit.endStyle()); + + // Begins message info. + messageBody.append("This email contains any scheduled treatments not marked as completed. It was run on: " + dateToolkit.getCurrentTime() + ".

"); + + // Creates message. + // 1. Shows all rooms lacking observations today. + if (!myTreatmentAlertsObject.roomsLackingObservationsToday.isEmpty()) { + messageBody.append("WARNING: The following rooms do not have any obs for today as of: " + dateToolkit.getCurrentTime() + "."); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view them.

\n", myTreatmentAlertsObject.roomsLackingObservationsTodayUrlView)); + for (HashMap result : myTreatmentAlertsObject.roomsLackingObservationsToday) { + messageBody.append(result.get("room") + "
"); + } + messageBody.append("


\n"); + } + // 2. Shows all treatments where the animal is not assigned to that project. + if (!myTreatmentAlertsObject.treatmentsWithAnimalNotAssignedToProject.isEmpty()) { + messageBody.append("WARNING: There are " + myTreatmentAlertsObject.treatmentsWithAnimalNotAssignedToProject.size() + " scheduled treatments where the animal is not assigned to the project.

"); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view them.
\n", myTreatmentAlertsObject.treatmentsWithAnimalNotAssignedToProjectUrlView)); + messageBody.append("
\n"); + } + // 3. Shows treatments for each time of day. + String[] timesOfDay = {"AM", "Noon", "PM", "Any Time", "Night"}; + String[] treatmentColumns = new String[]{"ID", "Treatment", "Route", "Concentration", "Amount To Give", "Volume", "Instructions", "Ordered By"}; + + for (String timeOfDay : timesOfDay) { + // Verifies there are treatments scheduled. + Integer totalTreatments = myTreatmentAlertsObject.incompleteTreatmentsForEachTimeOfDay.get(timeOfDay).size() + myTreatmentAlertsObject.completedTreatmentCountsForEachTimeOfDay.get(timeOfDay); + if (totalTreatments > 0) { + messageBody.append("There are " + totalTreatments + " scheduled " + timeOfDay + " treatments. " + myTreatmentAlertsObject.completedTreatmentCountsForEachTimeOfDay.get(timeOfDay) + " have been completed. "); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view them.

\n", myTreatmentAlertsObject.treatmentsForEachTimeOfDayUrlView.get(timeOfDay))); + + // Creates the current timeOfDay results sorted by (area --> room --> result). + HashMap>>> resultsByArea = new HashMap<>(); + + for (HashMap result : myTreatmentAlertsObject.incompleteTreatmentsForEachTimeOfDay.get(timeOfDay)) { + String currentArea = result.get("CurrentArea"); + String currentRoom = result.get("CurrentRoom"); + // Adds result if area does not yet exist. + if (!resultsByArea.containsKey(currentArea)) { + // Creates new treatments list. + ArrayList> roomTreatments = new ArrayList<>(); + roomTreatments.add(result); + // Adds new treatments list to new room. + HashMap>> newRoom = new HashMap<>(); + newRoom.put(currentRoom, roomTreatments); + // Adds new room to new area. + resultsByArea.put(currentArea, newRoom); + } + // Adds result if area exists but room does not yet exist. + else if (!resultsByArea.get(currentArea).containsKey(currentRoom)) { + // Creates new treatments list. + ArrayList> roomTreatments = new ArrayList<>(); + roomTreatments.add(result); + // Adds new room to new area. + resultsByArea.get(currentArea).put(currentRoom, roomTreatments); + } + // Adds result if area and room both exist. + else { + resultsByArea.get(currentArea).get(currentRoom).add(result); + } + } + + // Iterates through each area (sorted alphabetically). + for (String currentArea : notificationToolkit.sortSetWithNulls(resultsByArea.keySet())) { + messageBody.append("" + currentArea + ":
\n"); + // Iterates through each room (sorted alphabetically) + for (String currentRoom : notificationToolkit.sortSetWithNulls(resultsByArea.get(currentArea).keySet())) { + messageBody.append(currentRoom + ": " + resultsByArea.get(currentArea).get(currentRoom).size() + "
\n"); + // Reformats the treatment hashmap into a String[] List (to be compatible with the table creation function). + ArrayList formattedResults = new ArrayList<>(); + for (HashMap currentTreatment : resultsByArea.get(currentArea).get(currentRoom)) { + String[] newTableRow = new String[]{ + currentTreatment.get("Id"), + currentTreatment.get("meaning"), + currentTreatment.get("route"), + currentTreatment.get("conc2"), + currentTreatment.get("amount2"), + currentTreatment.get("volume2"), + currentTreatment.get("remark"), + currentTreatment.get("performedby") + }; + formattedResults.add(newTableRow); + } + // Displays table with results. + NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(treatmentColumns, formattedResults); + messageBody.append(myTable.createBasicHTMLTable()); + } + } + + } + else { + messageBody.append("There are no scheduled " + timeOfDay + " treatments as of " + dateToolkit.getCurrentTime() + ". Treatments could be added after this email was sent, so please check online closer to the time."); + } + messageBody.append("
\n"); + } + // 4. Shows any treatments from today that differ from the order. + if (!myTreatmentAlertsObject.differentOrderTreatments.isEmpty()) { + // Reformats the String List into a String[] List (to be compatible with the table creation function). + ArrayList formattedResults = new ArrayList<>(); + for (String result : myTreatmentAlertsObject.differentOrderTreatments) { + formattedResults.add(new String[]{result}); + } + // Creates the necessary table. + String[] diffTableColumns = new String[]{"DIFFERING TREATMENTS"}; + NotificationToolkit.NotificationRevampTable myTable = new NotificationToolkit.NotificationRevampTable(diffTableColumns, formattedResults); + messageBody.append(myTable.createBasicHTMLTable()); + messageBody.append("
\n"); + } + // 5. Shows any treatments where the animal is not alive. + if (!myTreatmentAlertsObject.treatmentsWhereAnimalIsNotAlive.isEmpty()) { + messageBody.append("WARNING: There are " + myTreatmentAlertsObject.treatmentsWhereAnimalIsNotAlive.size() + " active treatments for animals not currently at WNPRC."); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view and update them.
\n", myTreatmentAlertsObject.treatmentsWhereAnimalIsNotAliveURLView)); + messageBody.append("
\n"); + } + // 6. Find any problems where the animal is not alive. + if (!myTreatmentAlertsObject.problemsWhereAnimalIsNotAlive.isEmpty()) { + messageBody.append("WARNING: There are " + myTreatmentAlertsObject.problemsWhereAnimalIsNotAlive.size() + " unresolved problems for animals not currently at WNPRC."); + messageBody.append("" + notificationToolkit.createHyperlink("Click here to view and update them.
\n", myTreatmentAlertsObject.problemsWhereAnimalIsNotAliveUrlView)); + messageBody.append("
\n"); + } + // 7. Checks for missing In Rooms after 2:30pm, as specified in the SOP. + if (!myTreatmentAlertsObject.missingInRoomsAfterTwoThirty.isEmpty()) { + messageBody.append("WARNING: There are " + myTreatmentAlertsObject.missingInRoomsAfterTwoThirty.size() + " " + notificationToolkit.createHyperlink("animals without In Rooms", myTreatmentAlertsObject.missingInRoomsAfterTwoThirtyUrlView) + "."); + } + + + return messageBody.toString(); + } + + // Gets all info for Treatment Alerts. + public static class TreatmentAlertsObject { + //Set up. + NotificationToolkit notificationToolkit = new NotificationToolkit(); + NotificationToolkit.DateToolkit dateToolkit = new NotificationToolkit.DateToolkit(); + Date todayDate = dateToolkit.getDateToday(); + + TreatmentAlertsObject(Container c, User u) { + getRoomsLackingObservationsToday(c, u); + getTreatmentsWithAnimalNotAssignedToProject(c, u); + getIncompleteTreatmentsForEachTimeOfDay(c, u); + getDifferentOrderTreatments(c, u); + getTreatmentsWhereAnimalIsNotAlive(c, u); + getProblemsWhereAnimalIsNotAlive(c, u); + getMissingInRoomsAfterTwoThirty(c, u); + } + + // Gets any rooms lacking observations for today. + ArrayList> roomsLackingObservationsToday; + String roomsLackingObservationsTodayUrlView; + private void getRoomsLackingObservationsToday(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("hasObs", "N", CompareType.EQUAL); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"room"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "ehr", "RoomsWithoutObsToday", myFilter, null, targetColumns); + + // Assigns data. + this.roomsLackingObservationsToday = returnArray; + this.roomsLackingObservationsTodayUrlView = notificationToolkit.createQueryURL(c, "execute", "ehr", "RoomsWithoutObsToday", myFilter); + } + + // Gets all treatments (today) where the animal is not assigned to that project. + ArrayList> treatmentsWithAnimalNotAssignedToProject; + String treatmentsWithAnimalNotAssignedToProjectUrlView; + private void getTreatmentsWithAnimalNotAssignedToProject(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/DataSet/Demographics/calculated_status", "Alive", CompareType.EQUAL); + myFilter.addCondition("projectStatus", "", CompareType.NONBLANK); + myFilter.addCondition("date", todayDate, CompareType.DATE_EQUAL); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"Id"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "treatmentSchedule", myFilter, null, targetColumns); + + // Assigns data. + this.treatmentsWithAnimalNotAssignedToProject = returnArray; + this.treatmentsWithAnimalNotAssignedToProjectUrlView = notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", myFilter); + } + + // Gets all treatments (today) for each time of the day (AM, Noon, PM, Any Time, Night). + HashMap>> incompleteTreatmentsForEachTimeOfDay = new HashMap<>() {{ + put("AM", new ArrayList()); + put("Noon", new ArrayList()); + put("PM", new ArrayList()); + put("Any Time", new ArrayList()); + put("Night", new ArrayList()); + }}; + HashMap completedTreatmentCountsForEachTimeOfDay = new HashMap<>() {{ + put("AM", 0); + put("Noon", 0); + put("PM", 0); + put("Any Time", 0); + put("Night", 0); + }}; + HashMap treatmentsForEachTimeOfDayUrlView = new HashMap<>(); + private void getIncompleteTreatmentsForEachTimeOfDay(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/DataSet/Demographics/calculated_status", "Alive", CompareType.EQUAL); + myFilter.addCondition("date", todayDate, CompareType.DATE_EQUAL); + // Creates sort. + Sort mySort = new Sort("CurrentArea,CurrentRoom"); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"timeofday", "Id", "CurrentArea", "CurrentRoom", "CurrentCage", "projectStatus", "treatmentStatus", "treatmentStatus/Label", "meaning", "code", "volume2", "conc2", "route", "amount2", "remark", "performedby"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "treatmentSchedule", myFilter, mySort, targetColumns); + + if (!returnArray.isEmpty()) { + // Sorts data for each time of day. + for (HashMap currentTreatment : returnArray) { + String treatmentTime = currentTreatment.get("timeofday"); + // Verifies time of day is one of the 5 supported times. + if (treatmentTime != null) { + if (treatmentTime.equals("AM") || treatmentTime.equals("Noon") || treatmentTime.equals("PM") || treatmentTime.equals("Any Time") || treatmentTime.equals("Night")) { + if (currentTreatment.get("treatmentStatus/Label").equals("Completed")) { + Integer currentCount = completedTreatmentCountsForEachTimeOfDay.get(treatmentTime); + completedTreatmentCountsForEachTimeOfDay.put(treatmentTime, currentCount + 1); + } + else { + incompleteTreatmentsForEachTimeOfDay.get(treatmentTime).add(currentTreatment); + } + } + } + } + // Updates official class variable for each time of day's URL view. + SimpleFilter amFilter = new SimpleFilter(myFilter); + SimpleFilter noonFilter = new SimpleFilter(myFilter); + SimpleFilter pmFilter = new SimpleFilter(myFilter); + SimpleFilter anytimeFilter = new SimpleFilter(myFilter); + SimpleFilter nightFilter = new SimpleFilter(myFilter); + amFilter.addCondition("timeofday", "AM", CompareType.EQUAL); + noonFilter.addCondition("timeofday", "Noon", CompareType.EQUAL); + pmFilter.addCondition("timeofday", "PM", CompareType.EQUAL); + anytimeFilter.addCondition("timeofday", "Any Time", CompareType.EQUAL); + nightFilter.addCondition("timeofday", "Night", CompareType.EQUAL); + this.treatmentsForEachTimeOfDayUrlView.put("AM", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", amFilter)); + this.treatmentsForEachTimeOfDayUrlView.put("Noon", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", noonFilter)); + this.treatmentsForEachTimeOfDayUrlView.put("PM", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", pmFilter)); + this.treatmentsForEachTimeOfDayUrlView.put("Any Time", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", anytimeFilter)); + this.treatmentsForEachTimeOfDayUrlView.put("Night", notificationToolkit.createQueryURL(c, "execute", "study", "treatmentSchedule", nightFilter)); + } + } + + // Gets all treatments (today) that differ from the order. + List differentOrderTreatments; + String differentOrderTreatmentsUrlView; + private void getDifferentOrderTreatments(Container c, User u) { + // Creates variable. + List resultsAsTableEntries = new ArrayList<>(); + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("date", todayDate, CompareType.DATE_EQUAL); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"CurrentArea", "CurrentRoom", "id", "date", "meaning", "performedby", "drug_performedby", "route", "drug_route", "concentration", "drug_concentration", "conc_units", "drug_conc_units", "dosage", "drug_dosage", "dosage_units", "drug_dosage_units", "amount", "drug_amount", "amount_units", "drug_amount_units", "volume", "drug_volume", "vol_units", "drug_vol_units"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "TreatmentsThatDiffer", myFilter, null, targetColumns); + + for (HashMap currentTreatment : returnArray) { + // Creates variable. + StringBuilder currentTableEntry = new StringBuilder(); + + // Displays the basic info for the current treatment. + currentTableEntry.append("Id: " + currentTreatment.get("id") + "
\n"); + currentTableEntry.append("Date: " + currentTreatment.get("date") + "
\n"); + currentTableEntry.append("Treatment: " + currentTreatment.get("meaning") + "
\n"); + currentTableEntry.append("Ordered By: " + currentTreatment.get("performedby") + "
\n"); + currentTableEntry.append("Performed By: " + currentTreatment.get("drug_performedby") + "
\n"); + + // Displays which order/entry field in the treatment does not match. + // Compares the route. + if (!currentTreatment.get("route").equals(currentTreatment.get("drug_route"))) { + currentTableEntry.append("Route Ordered: " + currentTreatment.get("route") + "
\n"); + currentTableEntry.append("Route Entered: " + currentTreatment.get("drug_route") + "
\n"); + } + // Compares the concentration & concentration units. + if (!currentTreatment.get("concentration").equals(currentTreatment.get("drug_concentration")) || !currentTreatment.get("conc_units").equals(currentTreatment.get("drug_conc_units"))) { + currentTableEntry.append("Concentration Ordered: " + currentTreatment.get("concentration") + " " + currentTreatment.get("conc_units") + "
\n"); + currentTableEntry.append("Concentration Entered: " + currentTreatment.get("drug_concentration") + " " + currentTreatment.get("drug_conc_units") + "
\n"); + } + // Compares the dosage & dosage units. + if (!currentTreatment.get("dosage").equals(currentTreatment.get("drug_dosage")) || !currentTreatment.get("dosage_units").equals(currentTreatment.get("drug_dosage_units"))) { + currentTableEntry.append("Dosage Ordered: " + currentTreatment.get("dosage") + " " + currentTreatment.get("dosage_units") + "
\n"); + currentTableEntry.append("Dosage Entered: " + currentTreatment.get("drug_dosage") + " " + currentTreatment.get("drug_dosage_units") + "
\n"); + } + // Compares the amount & amount units. + if (!currentTreatment.get("amount").equals(currentTreatment.get("drug_amount")) || !currentTreatment.get("amount_units").equals(currentTreatment.get("drug_amount_units"))) { + currentTableEntry.append("Amount Ordered: " + currentTreatment.get("amount") + " " + currentTreatment.get("amount_units") + "
\n"); + currentTableEntry.append("Amount Entered: " + currentTreatment.get("drug_amount") + " " + currentTreatment.get("drug_amount_units") + "
\n"); + } + // Compares the volume & volume units. + if (!currentTreatment.get("volume").equals(currentTreatment.get("drug_volume")) || !currentTreatment.get("vol_units").equals(currentTreatment.get("drug_vol_units"))) { + currentTableEntry.append("Volume Ordered: " + currentTreatment.get("volume") + " " + currentTreatment.get("vol_units") + "
\n"); + currentTableEntry.append("Volume Entered: " + currentTreatment.get("drug_volume") + " " + currentTreatment.get("drug_vol_units") + "
\n"); + } + + // Adds treatment to table row list. + resultsAsTableEntries.add(currentTableEntry.toString()); + } + + // Assigns data. + this.differentOrderTreatments = resultsAsTableEntries; + this.differentOrderTreatmentsUrlView = notificationToolkit.createQueryURL(c, "execute", "study", "TreatmentsThatDiffer", myFilter); + } + + // Gets all treatments (today & future) where the animal is not alive. + ArrayList> treatmentsWhereAnimalIsNotAlive; + String treatmentsWhereAnimalIsNotAliveURLView; + private void getTreatmentsWhereAnimalIsNotAlive(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/DataSet/Demographics/calculated_status", "Alive", CompareType.NEQ_OR_NULL); + myFilter.addCondition("enddate", "", CompareType.ISBLANK); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"Id"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "Treatment Orders", myFilter, null, targetColumns); + + // Assigns data. + this.treatmentsWhereAnimalIsNotAlive = returnArray; + this.treatmentsWhereAnimalIsNotAliveURLView = notificationToolkit.createQueryURL(c, "execute", "study", "Treatment Orders", myFilter); + } + + // Gets any problems (today & future) where the animal is not alive. + ArrayList> problemsWhereAnimalIsNotAlive; + String problemsWhereAnimalIsNotAliveUrlView; + private void getProblemsWhereAnimalIsNotAlive(Container c, User u) { + // Creates filter. + SimpleFilter myFilter = new SimpleFilter("Id/DataSet/Demographics/calculated_status", "Alive", CompareType.NEQ_OR_NULL); + myFilter.addCondition("enddate", "", CompareType.ISBLANK); + // Creates columns to retrieve. + String[] targetColumns = new String[]{"Id"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "Problem List", myFilter, null, targetColumns); + + // Assigns data. + this.problemsWhereAnimalIsNotAlive = returnArray; + this.problemsWhereAnimalIsNotAliveUrlView = notificationToolkit.createQueryURL(c, "execute", "study", "Problem List", myFilter); + } + + // Checks for missing-in-rooms after 2:30pm, as specified in the SOP. + ArrayList> missingInRoomsAfterTwoThirty; + String missingInRoomsAfterTwoThirtyUrlView; + private void getMissingInRoomsAfterTwoThirty(Container c, User u) { + // Creates columns to retrieve. + String[] targetColumns = new String[]{"Id"}; + // Runs query. + ArrayList> returnArray = notificationToolkit.getTableMultiRowMultiColumnWithFieldKeys(c, u, "study", "inRoomNotSubmitted", null, null, targetColumns); + + // Assigns data. + this.missingInRoomsAfterTwoThirty = returnArray; + this.missingInRoomsAfterTwoThirtyUrlView = notificationToolkit.createQueryURL(c, "execute", "study", "inRoomNotSubmitted", null); + } + + + + + + + } + +}