Skip to content

Commit

Permalink
Blood draw scheduling improvement (#724)
Browse files Browse the repository at this point in the history
* working with selected records

* Check DB for scheduled/approved blood draws

* half working test

* finish test

* remove unused import

* add error details

* change wording

* add timestamp

* auto adjust height to fit message

* more appropriate naming
  • Loading branch information
csebranek authored Jan 9, 2025
1 parent c7188f8 commit f965e78
Show file tree
Hide file tree
Showing 3 changed files with 364 additions and 3 deletions.
68 changes: 65 additions & 3 deletions WNPRC_EHR/resources/web/wnprc_ehr/datasetButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,25 @@ WNPRC_EHR.DatasetButtons = new function(){
}
}
},

checkBloodSchedule: function(records) {

return new Promise((resolve, reject) => {
LABKEY.Ajax.request({
url: LABKEY.ActionURL.buildURL('WNPRC_EHR', 'CompareBloodSchedules'),
jsonData: { records: records },
callback: function (config, success, xhr) {
if (success) {
resolve(xhr.responseText);
} else {
reject('Couldn\'t compare blood schedule, internal error.');
}
}
})
});

},

/**
* This add a handler to a dataset that allows the user to change the QCState of the records, designed to approve or deny blood requests.
* It also captures values for 'billedBy' and 'instructions'.
Expand Down Expand Up @@ -710,14 +729,26 @@ WNPRC_EHR.DatasetButtons = new function(){
title: 'Change Request Status',
width: 430,
autoHeight: true,
id: 'change-request-window',
items: [{
xtype: 'form',
height: '100%',
ref: 'theForm',
id: 'change-request-form',
autoHeight: true,
bodyStyle: 'padding: 5px;',
defaults: {
border: false
},
items: [{
items: [

{
id:'bloodCompareResponseWrapper',
height: '100%',
html: '<div id="bloodCompareResponse"><i class="fa fa-spinner fa-pulse"></i> loading..</div>',
tag: 'div'
},
{
html: 'Total Records: '+checked.length+'<br><br>',
tag: 'div'
},{
Expand Down Expand Up @@ -767,10 +798,11 @@ WNPRC_EHR.DatasetButtons = new function(){
}],
buttons: [{
text:'Submit',
disabled:false,
disabled:true,
formBind: true,
ref: '../submit',
scope: this,
id: 'submitButton',
handler: function(o){
var win = o.up('window');
var form = win.down('form');
Expand Down Expand Up @@ -848,7 +880,37 @@ WNPRC_EHR.DatasetButtons = new function(){
handler: function(o){
o.ownerCt.ownerCt.close();
}
}]
}],
listeners: {
afterrender: () => {
this.checkBloodSchedule(records).then(response => {
let resp = document.getElementById('bloodCompareResponse');
let rsp = JSON.parse(response).message;
let txt = '';
if (rsp) {
for (let item of rsp) {
txt += '<li>' + item.message + ' (Project(s): ' + item.projects + '; Contacts: <a href="mailto:' + item.emails + '">' + item.emails + '</a>' + ')</li>';
}
}
if (txt.length > 0){
txt = '<ul>' + txt + '</ul>'
resp.innerHTML = '<span style="background:#acac16"> Warning:</span> ' + txt;
} else {
resp.innerHTML = '';
}
Ext4.getCmp('submitButton').enable()
// Have to reset the height since the form has a set height after initial rendering,
// but when text is dynamically added the height does not change.
Ext4.getCmp('change-request-form').setHeight('100%')
}).catch(error => {
Ext4.getCmp('submitButton').enable()
console.error(error);
let resp = document.getElementById('bloodCompareResponse');
resp.innerHTML = '<p>Error checking blood schedule, you are still able to submit. Please contact EHR admins with details: ' + new Date() + ' ' + JSON.parse(error) + '</p>'
Ext4.getCmp('change-request-form').setHeight('100%')
});
}
}
}).show();
}
},
Expand Down
200 changes: 200 additions & 0 deletions WNPRC_EHR/src/org/labkey/wnprc_ehr/WNPRC_EHRController.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.labkey.api.action.SimpleRedirectAction;
import org.labkey.api.action.SpringActionController;
import org.labkey.api.data.ColumnInfo;
import org.labkey.api.data.CompareType;
import org.labkey.api.data.Container;
import org.labkey.api.data.CoreSchema;
import org.labkey.api.data.DbSchema;
Expand Down Expand Up @@ -76,11 +77,13 @@
import org.labkey.api.security.RequiresPermission;
import org.labkey.api.security.RequiresSiteAdmin;
import org.labkey.api.security.User;
import org.labkey.api.security.UserManager;
import org.labkey.api.security.permissions.AdminPermission;
import org.labkey.api.security.permissions.ReadPermission;
import org.labkey.api.study.Dataset;
import org.labkey.api.study.StudyService;
import org.labkey.api.util.ExceptionUtil;
import org.labkey.api.util.PageFlowUtil;
import org.labkey.api.util.Path;
import org.labkey.api.util.ResultSetUtil;
import org.labkey.api.util.URLHelper;
Expand Down Expand Up @@ -122,13 +125,16 @@
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -2105,4 +2111,198 @@ public Object execute(Object o, BindException errors) throws Exception
return response;
}
}

public static class CompareBloodSchedulesForm
{
private List<Map<String,Object>> _records;
public List<Map<String,Object>> getRecords()
{
return _records;
}

public void setRecords(List<Map<String,Object>> records)
{
_records = records;
}
}

public static String convertSetToString(Set<?> set, String delimiter) {
StringBuilder sb = new StringBuilder();
Iterator<?> iterator = set.iterator();

while (iterator.hasNext()) {
Object element = iterator.next();
sb.append(element);
if (iterator.hasNext()) {
sb.append(delimiter + " ");
}
}

return sb.toString();
}

@ActionNames("CompareBloodSchedules")
@RequiresNoPermission()
public class comparebloodSchedulesAction extends ReadOnlyApiAction<CompareBloodSchedulesForm>
{

@Override
public ApiResponse execute(CompareBloodSchedulesForm form, BindException errors) throws IOException, InvalidFormatException
{

ApiSimpleResponse response = new ApiSimpleResponse();
Map<String, List<Map<String, Object>>> groupedById = new HashMap<>();
Map<Timestamp, List<Map<String, Object>>> groupedByDate = new HashMap<>();
List<String> lsids = new ArrayList<>();

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Date parsedDate;

for (Map<String,Object> mp : form.getRecords())
{
String id = (String) mp.get("Id");
try
{
//convert the date to Timestamp since this is what comes from the db later
parsedDate = dateFormat.parse((String) mp.get("date"));
Timestamp date = new Timestamp(parsedDate.getTime());
mp.put("date", date);
groupedById.computeIfAbsent(id, k -> new ArrayList<>()).add(mp);
groupedByDate.computeIfAbsent(date, k -> new ArrayList<>()).add(mp);
}
catch (ParseException e)
{
throw new RuntimeException(e);
}
}


Set<String> ids = groupedById.keySet();
Set<Timestamp> dates = groupedByDate.keySet();

//get min date to query DB later
java.time.LocalDate minDate= java.time.LocalDate.now().plusYears(100);
for (Timestamp dateTime : dates)
{
java.time.LocalDate date1 = dateTime.toLocalDateTime().toLocalDate();
minDate = date1.isBefore(minDate) ? date1 : minDate;
}

//get max date to query DB later
java.time.LocalDate maxDate = java.time.LocalDate.now().minusYears(100);
for (Timestamp dateTime : dates)
{
java.time.LocalDate date1 = dateTime.toLocalDateTime().toLocalDate();
maxDate = date1.isAfter(maxDate) ? date1 : maxDate;
}

//get any blood draws that have potentially the same date & id
Set<Integer> qcStates = new HashSet<>();
qcStates.add(EHRService.get().getQCStates(getContainer()).get(EHRService.QCSTATES.RequestApproved.getLabel()).getRowId());
qcStates.add(EHRService.get().getQCStates(getContainer()).get(EHRService.QCSTATES.Scheduled.getLabel()).getRowId());

SimpleFilter bloodFilter = new SimpleFilter(FieldKey.fromString("lsid"), String.join("; ",ids), CompareType.CONTAINS_ONE_OF);
bloodFilter.addCondition(FieldKey.fromString("date"), minDate, CompareType.DATE_GTE);
bloodFilter.addCondition(FieldKey.fromString("date"), maxDate, CompareType.DATE_LTE);
bloodFilter.addCondition(FieldKey.fromString("qcstate"), qcStates, CompareType.IN);
//Runs query with updated info.
TableInfo bloodTi = QueryService.get().getUserSchema(getUser(), getContainer(), "study").getTable("Blood Draws");
TableSelector bloodTable = new TableSelector(bloodTi, PageFlowUtil.set("lsid", "date", "project", "Id", "requestor", "createdby"), bloodFilter, null);
Map<String, Object>[] bloodRows = bloodTable.getMapArray();

//add the records from the db to our groupedById array
for (Map<String, Object> row : bloodRows)
{
for (Map.Entry<String, List<Map<String, Object>>> entry: groupedById.entrySet())
{
if (entry.getKey().equals(row.get("Id")))
{
entry.getValue().add(row);
}
}
}


List<Map<String,Object>> messageList = new ArrayList<>();

for (Map.Entry<String, List<Map<String, Object>>> entry : groupedById.entrySet())
{
Map<String, Object> theMessage = new HashMap<>();

List<Map<String, Object>> records = entry.getValue();

Set<java.time.LocalTime> uniqueTimes = new HashSet<>();
Set<String> uniqueRequestors = new HashSet<>();
Set<Integer> uniqueProjects = new HashSet<>();
Set<Integer> uniqueCreatedBy = new HashSet<>();


// Create a Set to track unique dates
for (int i = 0; i < records.size() - 1; i++)
{
Map<String, Object> record1 = records.get(i);
Timestamp dateTime1 = (Timestamp) record1.get("date");

lsids.add((String) record1.get("lsid"));

for (int j = i + 1; j < records.size(); j++)
{
Map<String, Object> record2 = records.get(j);
Timestamp dateTime2 = (Timestamp) record2.get("date");
lsids.add((String) record1.get("lsid"));


java.time.LocalDate date1 = dateTime1.toLocalDateTime().toLocalDate();
java.time.LocalTime time1 = dateTime1.toLocalDateTime().toLocalTime();

java.time.LocalDate date2 = dateTime2.toLocalDateTime().toLocalDate();
java.time.LocalTime time2 = dateTime2.toLocalDateTime().toLocalTime();

if (date1.isEqual(date2) && !time1.equals(time2))
{
uniqueTimes.add(time1);
uniqueTimes.add(time2);
}
}
}
if (uniqueTimes.size() > 0)
{
//get requestor and project information

SimpleFilter myFilter = new SimpleFilter(FieldKey.fromString("lsid"), String.join("; ", lsids), CompareType.CONTAINS_ONE_OF);
//Runs query with updated info.
TableInfo ti = QueryService.get().getUserSchema(getUser(), getContainer(), "study").getTable("Blood Draws");
TableSelector myTable = new TableSelector(ti, PageFlowUtil.set("lsid", "project", "Id", "requestor", "createdby"), myFilter, null);
Map<String, Object>[] rows = myTable.getMapArray();
for (Map<String, Object> row : rows)
{
uniqueRequestors.add((String) row.get("requestor"));
uniqueProjects.add((Integer) row.get("project"));
uniqueCreatedBy.add((Integer) row.get("createdBy"));
}

List<String> emails = new ArrayList<>();
for (var userId : uniqueCreatedBy)
{
var user = UserManager.getUser(userId);

if (user != null)
emails.add(user.getEmail());
}

//pull out requestors email/userid
theMessage.put("emails", String.join(",", emails));
theMessage.put("projects", convertSetToString(uniqueProjects, ","));
theMessage.put("message",entry.getKey() + " has "+ uniqueTimes.size() + " draws on the same day but at different times.");

messageList.add(theMessage);
}
}
response.put("message", messageList);

return response;

}
}

}
Loading

0 comments on commit f965e78

Please sign in to comment.