Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -840,12 +840,15 @@ public Map<String, Object> handleScheduledObservations(Map<String, Object> row,
String observation = ConvertHelper.convert(row.get("observation"), String.class);
String performedBy = ConvertHelper.convert(row.get("performedBy"), String.class);
String taskid = ConvertHelper.convert(row.get("taskid"), String.class);
String id = ConvertHelper.convert(row.get("Id"), String.class);

// Get observation orders for these tasks
// Get this animal's observation orders for these tasks. An order taskid is shared by every animal
// ordered in the same session, so without the animal filter the entry spills onto those animals too.
TableInfo ti = getTableInfo("study", "observationOrdersByDate");
SimpleFilter filter = new SimpleFilter(FieldKey.fromString("taskid"), orderTasks, CompareType.IN);
filter.addCondition(FieldKey.fromString("category"), category);
filter.addCondition(FieldKey.fromString("date"), scheduledDate);
filter.addCondition(FieldKey.fromString("animalId"), id);
TableSelector ts = new TableSelector(ti, PageFlowUtil.set("category","caseId","animalId","area","objectid","type","taskid"), filter, null);
ts.setNamedParameters(Map.of("StartDate", scheduledDate, "NumDays", "1"));

Expand Down
114 changes: 114 additions & 0 deletions nirc_ehr/test/src/org.labkey.test/tests.nirc_ehr/NIRC_EHRTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ public class NIRC_EHRTest extends AbstractGenericEHRTest implements PostgresOnly
// Dedicated animal for testObservationTypeDerivedFromCategory; provisioned the same way so the Observations
// form can be submitted final in one step.
private static final String obsTypeAnimalId = "TESTOBSTYPE9191";
// Dedicated pair for testScheduledObservationStaysWithItsAnimal; both get their observation order from the
// same entry form, so the two orders share a taskid. Provisioned the same way as the animals above.
private static final String sharedOrderAnimalId = "TESTSHARED8181";
private static final String sharedOrderOtherAnimalId = "TESTSHARED8282";

private final String[] weightFields = {"Id", "date", "enddate", "project", "weight", FIELD_QCSTATELABEL, FIELD_OBJECTID, FIELD_LSID, "_recordid", "performedby"};
private final Object[] weightData1 = {getExpectedAnimalIDCasing("TESTSUBJECT1"), EHRClientAPIHelper.DATE_SUBSTITUTION, null, null, "12", EHRQCState.IN_PROGRESS.label, null, null, "_recordID", 1004};
Expand Down Expand Up @@ -537,9 +541,42 @@ protected void createTestSubjects() throws Exception
getApiHelper().deleteAllRecords("study", "Assignment", new Filter("Id", obsTypeAnimalId));
getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), insertCommand, getExtraContext());

provisionTestSubject(sharedOrderAnimalId, CAGES[2], pastDate1);
provisionTestSubject(sharedOrderOtherAnimalId, CAGES[2], pastDate1);

primeCaches();
}

// Fully provisions an animal (alive demographics, current housing, active assignment) so the clinical data
// entry forms raise no unknown-animal warnings that would keep the validation banner up.
private void provisionTestSubject(String animalId, String cage, Date housedSince) throws Exception
{
log("Creating test subject " + animalId);
String[] fields = new String[]{"Id", "Species", "Birth", "Gender", "date", "calculated_status", "objectid", "performedby"};
Object[][] data = new Object[][]{
{animalId, "Rhesus", (new Date()).toString(), getMale(), new Date(), "Alive", UUID.randomUUID().toString(), 1004}
};
SimplePostCommand insertCommand = getApiHelper().prepareInsertCommand("study", "demographics", "lsid", fields, data);
getApiHelper().deleteAllRecords("study", "demographics", new Filter("Id", animalId));
getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), insertCommand, getExtraContext());

fields = new String[]{"Id", "date", "enddate", "room", "cage", "performedby"};
data = new Object[][]{
{animalId, housedSince, null, getRooms()[0], cage, 1004}
};
insertCommand = getApiHelper().prepareInsertCommand("study", "Housing", "lsid", fields, data);
getApiHelper().deleteAllRecords("study", "Housing", new Filter("Id", animalId));
getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), insertCommand, getExtraContext());

fields = new String[]{"Id", "date", "enddate", "project", "performedby"};
data = new Object[][]{
{animalId, housedSince, null, PROJECTS[0], 1004}
};
insertCommand = getApiHelper().prepareInsertCommand("study", "Assignment", "lsid", fields, data);
getApiHelper().deleteAllRecords("study", "Assignment", new Filter("Id", animalId));
getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), insertCommand, getExtraContext());
}

@Override
protected String[] getWeightFields()
{
Expand Down Expand Up @@ -966,6 +1003,83 @@ public void testScheduledObservationTaskGrouping()
Assert.assertEquals("Expected two entries (one per matching order) for category " + category, Integer.valueOf(2), count));
}

// The observation ordered for both animals of the shared-order pair, and a valid Observation/Score for it.
private static final String SHARED_ORDER_CATEGORY = "Dental/Oral Observations";
private static final String SHARED_ORDER_OBSERVATION = "Fractured Tooth";

@Test
public void testScheduledObservationStaysWithItsAnimal()
{
// An observation order's taskid identifies the ordering session, not the animal, so ordering for
// several animals at once leaves them all with the same order taskid. Recording from the schedule
// opens the form for one animal and passes that taskid to the server, which must still confine the
// entry to the animal the form was opened for.
log("Ordering the same observation for two animals in one submission");
gotoEnterData();
waitAndClickAndWait(Locator.linkWithText("Bulk Clinical Entry"));

Ext4GridRef observationOrders = _helper.getExt4GridForFormSection("Observation Orders");
addObservationOrderRow(observationOrders, sharedOrderAnimalId);
addObservationOrderRow(observationOrders, sharedOrderOtherAnimalId);
submitForm("Submit Final", "Finalize");

List<Map<String, Object>> orders = getObservationOrders(sharedOrderAnimalId, sharedOrderOtherAnimalId);
Assert.assertEquals("Expected one observation order per animal", 2, orders.size());

Map<String, String> orderIdByAnimal = new HashMap<>();
Set<String> orderTaskIds = new HashSet<>();
for (Map<String, Object> order : orders)
{
orderIdByAnimal.put(String.valueOf(order.get("Id")), String.valueOf(order.get("objectid")));
orderTaskIds.add(String.valueOf(order.get("taskid")));
}
Assert.assertEquals("The two orders should share the submitting form's taskid, which is what puts the "
+ "entry at risk of crossing animals", 1, orderTaskIds.size());

log("Recording the scheduled observation for " + sharedOrderAnimalId + " only");
goToEHRFolder();
waitAndClickAndWait(Locator.linkWithText("Today's Observation Schedule"));
DataRegionTable table = new AnimalHistoryPage<>(getDriver()).getActiveReportDataRegion();
table.setFilter("Id", "Equals", sharedOrderAnimalId);
Assert.assertEquals("Expected a single scheduled slot for " + sharedOrderAnimalId, 1, table.getDataRowCount());

table.link(0, "observationRecord").click();
switchToWindow(1);
waitForText(sharedOrderAnimalId);
Ext4GridRef observation = _helper.getExt4GridForFormSection("Observations");
Assert.assertEquals("The form should open with only the animal it was launched for", 1, observation.getRowCount());
observation.setGridCell(1, "observation", SHARED_ORDER_OBSERVATION);
observation.setGridCellJS(1, "remark", "remark for " + sharedOrderAnimalId);
submitForm("Submit Final", "Finalize");

List<Map<String, Object>> entered = getClinicalObservations(sharedOrderAnimalId);
Assert.assertEquals("Expected the single entered observation for " + sharedOrderAnimalId, 1, entered.size());
Assert.assertEquals("Entered observation has the wrong category", SHARED_ORDER_CATEGORY, String.valueOf(entered.get(0).get("category")));
Assert.assertEquals("The entry should be filed against the animal's own observation order",
orderIdByAnimal.get(sharedOrderAnimalId), String.valueOf(entered.get(0).get("orderId")));

Assert.assertTrue("Recording an observation for " + sharedOrderAnimalId + " must not create one for "
+ sharedOrderOtherAnimalId + ", whose order only shares the same taskid",
getClinicalObservations(sharedOrderOtherAnimalId).isEmpty());
}

// Appends an observation order row for the animal. SID puts the order on a single 8:00 AM slot, so each
// animal gets exactly one row on today's observation schedule.
private void addObservationOrderRow(Ext4GridRef observationOrders, String animalId)
{
_helper.addRecordToGrid(observationOrders);
int row = observationOrders.getRowCount();
observationOrders.setGridCell(row, "Id", animalId);
observationOrders.setGridCell(row, "category", SHARED_ORDER_CATEGORY);
observationOrders.setGridCell(row, "frequency", "SID");
}

private List<Map<String, Object>> getObservationOrders(String... animalIds)
{
return executeSelectRowCommand("study", "observation_order", ContainerFilter.Current, "/" + getContainerPath(),
List.of(new Filter("Id", String.join(";", animalIds), Filter.Operator.IN))).getRows();
}

// Creates and finalizes a minimal clinical case for the animal. The case's open date is set to
// yesterday so the auto-generated daily observation orders land on today's observation schedule.
// confirmWindowTitle is the finalize-confirmation dialog expected on submit: "Finalize" for a brand
Expand Down