diff --git a/nbri_ehr/resources/queries/ehr_lookups/buildings.js b/nbri_ehr/resources/queries/ehr_lookups/buildings.js
index 3fca085..b5e9890 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/buildings.js
+++ b/nbri_ehr/resources/queries/ehr_lookups/buildings.js
@@ -7,6 +7,19 @@ var LABKEY = require("labkey");
var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
+// Width of ehr_lookups.buildings.name. The description it is derived from is a wider column, so it can overrun the
+// key; reject it here rather than letting the database raise an unreadable error.
+var MAX_NAME_LENGTH = 100;
+
+// 'name' is not user editable, so it is absent from the incoming row map and the value this script derives has
+// nowhere to land. Declaring it managed reserves a slot so the derived key is persisted.
+function managedColumns() {
+ return {
+ insert: ["name"],
+ update: ["name"],
+ };
+}
+
function onUpsert(row, oldRow, errors){
if (extraContext.dataSource != "etl") {
if (!row.description) {
@@ -25,7 +38,19 @@ function onUpsert(row, oldRow, errors){
return;
}
- row.name = row.description + '-' + row.area;
+ if (row.description.length > MAX_NAME_LENGTH) {
+ errors['description'] = 'Description is too long: it becomes the building key, which cannot exceed ' + MAX_NAME_LENGTH + ' characters.';
+ return;
+ }
+
+ // The description alone identifies the building now that the area is no longer folded in, so a duplicate
+ // would collide on the key. Say so here instead of surfacing a constraint violation on a hidden column.
+ if (triggerHelper.totalRecords("ehr_lookups", "buildings", "name", row.description) > 0) {
+ errors['description'] = 'A building described as ' + row.description + ' already exists. Building descriptions must be unique.';
+ return;
+ }
+
+ row.name = row.description;
}
}
}
diff --git a/nbri_ehr/resources/queries/ehr_lookups/cage.js b/nbri_ehr/resources/queries/ehr_lookups/cage.js
index e19a2b7..0e71832 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/cage.js
+++ b/nbri_ehr/resources/queries/ehr_lookups/cage.js
@@ -7,6 +7,19 @@ var LABKEY = require("labkey");
var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
+// Width of ehr_lookups.cage.location. The derived key builds on the room key, which is itself derived, so it can
+// overrun the column; reject it here rather than letting the database raise an unreadable error.
+var MAX_LOCATION_LENGTH = 100;
+
+// 'location' is not user editable, so it is absent from the incoming row map and the value this script
+// derives has nowhere to land. Declaring it managed reserves a slot so the derived key is persisted.
+function managedColumns() {
+ return {
+ insert: ["location"],
+ update: ["location"],
+ };
+}
+
function onUpsert(row, oldRow, errors){
if (extraContext.dataSource != "etl") {
if (!row.location) {
@@ -20,9 +33,16 @@ function onUpsert(row, oldRow, errors){
return;
}
- row.location = row.room;
+ let location = row.room;
if (row.cage)
- row.location += '-' + row.cage;
+ location += '-' + row.cage;
+
+ if (location.length > MAX_LOCATION_LENGTH) {
+ errors['cage'] = 'Room and cage are too long: they combine to a ' + location.length + ' character location key, which cannot exceed ' + MAX_LOCATION_LENGTH + '.';
+ return;
+ }
+
+ row.location = location;
}
}
}
diff --git a/nbri_ehr/resources/queries/ehr_lookups/cage.query.xml b/nbri_ehr/resources/queries/ehr_lookups/cage.query.xml
index 97b1549..3d15c46 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/cage.query.xml
+++ b/nbri_ehr/resources/queries/ehr_lookups/cage.query.xml
@@ -9,6 +9,11 @@
Room
+
+ ehr_lookups
+ rooms
+ room
+
true
diff --git a/nbri_ehr/resources/queries/ehr_lookups/rooms.js b/nbri_ehr/resources/queries/ehr_lookups/rooms.js
index caea28b..18a10a2 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/rooms.js
+++ b/nbri_ehr/resources/queries/ehr_lookups/rooms.js
@@ -8,6 +8,19 @@ var console = require("console");
var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
+// Width of ehr_lookups.rooms.room. The derived key is built from values the user supplies, so it can overrun the
+// column; reject it here rather than letting the database raise an unreadable error.
+var MAX_ROOM_LENGTH = 100;
+
+// 'room' is not user editable, so it is absent from the incoming row map and the value this script derives has
+// nowhere to land. Declaring it managed reserves a slot so the derived key is persisted.
+function managedColumns() {
+ return {
+ insert: ["room"],
+ update: ["room"],
+ };
+}
+
function onUpsert(row, oldRow, errors){
if (extraContext.dataSource != "etl") {
if (!row.name) {
@@ -15,8 +28,8 @@ function onUpsert(row, oldRow, errors){
return;
}
- if (!row.floor) {
- errors['floor'] = 'Floor is required.';
+ if (!row.building) {
+ errors['building'] = 'Building is required.';
return;
}
@@ -26,7 +39,13 @@ function onUpsert(row, oldRow, errors){
return;
}
- row.room = row.name + '-' + row.floor;
+ let room = row.building + '-' + row.name;
+ if (room.length > MAX_ROOM_LENGTH) {
+ errors['name'] = 'Building and room name are too long: they combine to a ' + room.length + ' character room key, which cannot exceed ' + MAX_ROOM_LENGTH + '.';
+ return;
+ }
+
+ row.room = room;
}
}
}
diff --git a/nbri_ehr/resources/queries/ehr_lookups/rooms.query.xml b/nbri_ehr/resources/queries/ehr_lookups/rooms.query.xml
index dd7b89f..0b522c5 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/rooms.query.xml
+++ b/nbri_ehr/resources/queries/ehr_lookups/rooms.query.xml
@@ -6,8 +6,10 @@
Rooms
-
+
+
+ /nbri_ehr/cageDetails.view?room=${room}
true
false
false
@@ -17,6 +19,14 @@
false
false
+
+
+ true
+ false
+ false
+ false
+
diff --git a/nbri_ehr/resources/queries/ehr_lookups/rooms/.qview.xml b/nbri_ehr/resources/queries/ehr_lookups/rooms/.qview.xml
index 0bfbf5b..f8c6e5e 100644
--- a/nbri_ehr/resources/queries/ehr_lookups/rooms/.qview.xml
+++ b/nbri_ehr/resources/queries/ehr_lookups/rooms/.qview.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrder.query.xml b/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrder.query.xml
index 5c829e1..e282d4d 100644
--- a/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrder.query.xml
+++ b/nbri_ehr/resources/queries/nbri_ehr/AnimalReqOrder.query.xml
@@ -96,7 +96,7 @@
ehr_lookups
rooms
room
- name
+ room
diff --git a/nbri_ehr/resources/queries/study/BehaviorClinRemarks/.qview.xml b/nbri_ehr/resources/queries/study/BehaviorClinRemarks/.qview.xml
index 4e381e4..243ffad 100644
--- a/nbri_ehr/resources/queries/study/BehaviorClinRemarks/.qview.xml
+++ b/nbri_ehr/resources/queries/study/BehaviorClinRemarks/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/ClinicalClinRemarks/.qview.xml b/nbri_ehr/resources/queries/study/ClinicalClinRemarks/.qview.xml
index c3ca1ea..86bc194 100644
--- a/nbri_ehr/resources/queries/study/ClinicalClinRemarks/.qview.xml
+++ b/nbri_ehr/resources/queries/study/ClinicalClinRemarks/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/activeBehaviorCases/.qview.xml b/nbri_ehr/resources/queries/study/activeBehaviorCases/.qview.xml
index b08797c..6026d97 100644
--- a/nbri_ehr/resources/queries/study/activeBehaviorCases/.qview.xml
+++ b/nbri_ehr/resources/queries/study/activeBehaviorCases/.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/activeClinicalCases/.qview.xml b/nbri_ehr/resources/queries/study/activeClinicalCases/.qview.xml
index b08797c..6026d97 100644
--- a/nbri_ehr/resources/queries/study/activeClinicalCases/.qview.xml
+++ b/nbri_ehr/resources/queries/study/activeClinicalCases/.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/alopecia/.qview.xml b/nbri_ehr/resources/queries/study/alopecia/.qview.xml
index f889f92..b514468 100644
--- a/nbri_ehr/resources/queries/study/alopecia/.qview.xml
+++ b/nbri_ehr/resources/queries/study/alopecia/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/behaviorCases/.qview.xml b/nbri_ehr/resources/queries/study/behaviorCases/.qview.xml
index b08797c..6026d97 100644
--- a/nbri_ehr/resources/queries/study/behaviorCases/.qview.xml
+++ b/nbri_ehr/resources/queries/study/behaviorCases/.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/behaviorObservations/.qview.xml b/nbri_ehr/resources/queries/study/behaviorObservations/.qview.xml
index a48d5a5..5a68b93 100644
--- a/nbri_ehr/resources/queries/study/behaviorObservations/.qview.xml
+++ b/nbri_ehr/resources/queries/study/behaviorObservations/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/blood/.qview.xml b/nbri_ehr/resources/queries/study/blood/.qview.xml
index 831d6b5..c382d83 100644
--- a/nbri_ehr/resources/queries/study/blood/.qview.xml
+++ b/nbri_ehr/resources/queries/study/blood/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/breeder/.qview.xml b/nbri_ehr/resources/queries/study/breeder/.qview.xml
index 448bd0f..bba85e1 100644
--- a/nbri_ehr/resources/queries/study/breeder/.qview.xml
+++ b/nbri_ehr/resources/queries/study/breeder/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/cases/.qview.xml b/nbri_ehr/resources/queries/study/cases/.qview.xml
index 340d770..a2f1fbc 100644
--- a/nbri_ehr/resources/queries/study/cases/.qview.xml
+++ b/nbri_ehr/resources/queries/study/cases/.qview.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/cases/Active Behavior Cases.qview.xml b/nbri_ehr/resources/queries/study/cases/Active Behavior Cases.qview.xml
index 8333562..7f83fe9 100644
--- a/nbri_ehr/resources/queries/study/cases/Active Behavior Cases.qview.xml
+++ b/nbri_ehr/resources/queries/study/cases/Active Behavior Cases.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/cases/Active Clinical Cases.qview.xml b/nbri_ehr/resources/queries/study/cases/Active Clinical Cases.qview.xml
index 6081418..7554046 100644
--- a/nbri_ehr/resources/queries/study/cases/Active Clinical Cases.qview.xml
+++ b/nbri_ehr/resources/queries/study/cases/Active Clinical Cases.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/cases/All Behavior Cases.qview.xml b/nbri_ehr/resources/queries/study/cases/All Behavior Cases.qview.xml
index 261b1bb..25baf96 100644
--- a/nbri_ehr/resources/queries/study/cases/All Behavior Cases.qview.xml
+++ b/nbri_ehr/resources/queries/study/cases/All Behavior Cases.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/cases/All Clinical Cases.qview.xml b/nbri_ehr/resources/queries/study/cases/All Clinical Cases.qview.xml
index 73fb0b2..e0dd66f 100644
--- a/nbri_ehr/resources/queries/study/cases/All Clinical Cases.qview.xml
+++ b/nbri_ehr/resources/queries/study/cases/All Clinical Cases.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/chemistryResults/.qview.xml b/nbri_ehr/resources/queries/study/chemistryResults/.qview.xml
index 02467cb..3cc536b 100644
--- a/nbri_ehr/resources/queries/study/chemistryResults/.qview.xml
+++ b/nbri_ehr/resources/queries/study/chemistryResults/.qview.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinicalCases/.qview.xml b/nbri_ehr/resources/queries/study/clinicalCases/.qview.xml
index b08797c..6026d97 100644
--- a/nbri_ehr/resources/queries/study/clinicalCases/.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinicalCases/.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinicalObservations/.qview.xml b/nbri_ehr/resources/queries/study/clinicalObservations/.qview.xml
index 7b1fc1a..0fe5c32 100644
--- a/nbri_ehr/resources/queries/study/clinicalObservations/.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinicalObservations/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinical_observations/.qview.xml b/nbri_ehr/resources/queries/study/clinical_observations/.qview.xml
index daaa82d..8d99f0c 100644
--- a/nbri_ehr/resources/queries/study/clinical_observations/.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinical_observations/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinical_observations/Alopecia Scores.qview.xml b/nbri_ehr/resources/queries/study/clinical_observations/Alopecia Scores.qview.xml
index 0a05b82..589eba9 100644
--- a/nbri_ehr/resources/queries/study/clinical_observations/Alopecia Scores.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinical_observations/Alopecia Scores.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinical_observations/Behavior.qview.xml b/nbri_ehr/resources/queries/study/clinical_observations/Behavior.qview.xml
index f7566f3..6c017a8 100644
--- a/nbri_ehr/resources/queries/study/clinical_observations/Behavior.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinical_observations/Behavior.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinical_observations/Clinical.qview.xml b/nbri_ehr/resources/queries/study/clinical_observations/Clinical.qview.xml
index adf8cf8..e8cff6b 100644
--- a/nbri_ehr/resources/queries/study/clinical_observations/Clinical.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinical_observations/Clinical.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinical_observationsSummary/.qview.xml b/nbri_ehr/resources/queries/study/clinical_observationsSummary/.qview.xml
index a48d5a5..5a68b93 100644
--- a/nbri_ehr/resources/queries/study/clinical_observationsSummary/.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinical_observationsSummary/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinremarks/.qview.xml b/nbri_ehr/resources/queries/study/clinremarks/.qview.xml
index 903f88d..d96725e 100644
--- a/nbri_ehr/resources/queries/study/clinremarks/.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinremarks/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinremarks/Behavior.qview.xml b/nbri_ehr/resources/queries/study/clinremarks/Behavior.qview.xml
index 24f5a40..007434b 100644
--- a/nbri_ehr/resources/queries/study/clinremarks/Behavior.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinremarks/Behavior.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/clinremarks/Clinical.qview.xml b/nbri_ehr/resources/queries/study/clinremarks/Clinical.qview.xml
index 6cb3771..3123f8c 100644
--- a/nbri_ehr/resources/queries/study/clinremarks/Clinical.qview.xml
+++ b/nbri_ehr/resources/queries/study/clinremarks/Clinical.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/demographics/.qview.xml b/nbri_ehr/resources/queries/study/demographics/.qview.xml
index 439cd0a..d232062 100644
--- a/nbri_ehr/resources/queries/study/demographics/.qview.xml
+++ b/nbri_ehr/resources/queries/study/demographics/.qview.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/demographicsCagemates.sql b/nbri_ehr/resources/queries/study/demographicsCagemates.sql
index b3464b7..4219925 100644
--- a/nbri_ehr/resources/queries/study/demographicsCagemates.sql
+++ b/nbri_ehr/resources/queries/study/demographicsCagemates.sql
@@ -22,10 +22,15 @@ SELECT
FROM study.housing h
JOIN study.housing h2
-ON (h2.Id.demographics.calculated_status = 'Alive'
- AND (h.cage = h2.cage))
+-- cage holds a location key that already encodes the room, so caged animals match on cage alone. Group/pen rooms have
+-- no cage, so those fall back to the room, which is only consulted when neither side has a cage.
+ON ((h.cage = h2.cage OR (h.cage IS NULL AND h2.cage IS NULL AND h.room = h2.room))
+ AND h2.Id.demographics.calculated_status = 'Alive'
+ AND h2.enddateTimeCoalesced >= now()
+ AND h2.qcstate.publicdata = true)
WHERE h.enddateTimeCoalesced >= now()
+AND h.qcstate.publicdata = true
GROUP BY h.id, h.room, h.cage
) t ON (t.id = d.id)
diff --git a/nbri_ehr/resources/queries/study/drug/.qview.xml b/nbri_ehr/resources/queries/study/drug/.qview.xml
index 3b72268..8489e4d 100644
--- a/nbri_ehr/resources/queries/study/drug/.qview.xml
+++ b/nbri_ehr/resources/queries/study/drug/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/drug/Behavior.qview.xml b/nbri_ehr/resources/queries/study/drug/Behavior.qview.xml
index a9769cb..1688ad3 100644
--- a/nbri_ehr/resources/queries/study/drug/Behavior.qview.xml
+++ b/nbri_ehr/resources/queries/study/drug/Behavior.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/exemptions/.qview.xml b/nbri_ehr/resources/queries/study/exemptions/.qview.xml
index 7e4278b..cdd0721 100644
--- a/nbri_ehr/resources/queries/study/exemptions/.qview.xml
+++ b/nbri_ehr/resources/queries/study/exemptions/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/flags/.qview.xml b/nbri_ehr/resources/queries/study/flags/.qview.xml
index bf12e57..05657ca 100644
--- a/nbri_ehr/resources/queries/study/flags/.qview.xml
+++ b/nbri_ehr/resources/queries/study/flags/.qview.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/historicalOther.query.xml b/nbri_ehr/resources/queries/study/historicalOther.query.xml
deleted file mode 100644
index 37e2754..0000000
--- a/nbri_ehr/resources/queries/study/historicalOther.query.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/housing.query.xml b/nbri_ehr/resources/queries/study/housing.query.xml
index eeb5e0d..b08ae42 100644
--- a/nbri_ehr/resources/queries/study/housing.query.xml
+++ b/nbri_ehr/resources/queries/study/housing.query.xml
@@ -21,7 +21,6 @@
ehr_lookups
cage
location
- cage
@@ -31,7 +30,7 @@
ehr_lookups
rooms
room
- name
+ room
diff --git a/nbri_ehr/resources/queries/study/housing/.qview.xml b/nbri_ehr/resources/queries/study/housing/.qview.xml
index bc780a5..5f01093 100644
--- a/nbri_ehr/resources/queries/study/housing/.qview.xml
+++ b/nbri_ehr/resources/queries/study/housing/.qview.xml
@@ -7,7 +7,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/housing/Active Housing.qview.xml b/nbri_ehr/resources/queries/study/housing/Active Housing.qview.xml
index 4fc3eeb..54e07b9 100644
--- a/nbri_ehr/resources/queries/study/housing/Active Housing.qview.xml
+++ b/nbri_ehr/resources/queries/study/housing/Active Housing.qview.xml
@@ -4,6 +4,7 @@
+
diff --git a/nbri_ehr/resources/queries/study/notes/.qview.xml b/nbri_ehr/resources/queries/study/notes/.qview.xml
index 6cd9bab..0f3a556 100644
--- a/nbri_ehr/resources/queries/study/notes/.qview.xml
+++ b/nbri_ehr/resources/queries/study/notes/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/observationSchedule/.qview.xml b/nbri_ehr/resources/queries/study/observationSchedule/.qview.xml
index b91d5d4..628bcd9 100644
--- a/nbri_ehr/resources/queries/study/observationSchedule/.qview.xml
+++ b/nbri_ehr/resources/queries/study/observationSchedule/.qview.xml
@@ -3,7 +3,7 @@
-
+
@@ -11,7 +11,7 @@
-
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/observation_order/.qview.xml b/nbri_ehr/resources/queries/study/observation_order/.qview.xml
index c02d2fb..40bb429 100644
--- a/nbri_ehr/resources/queries/study/observation_order/.qview.xml
+++ b/nbri_ehr/resources/queries/study/observation_order/.qview.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/observation_order/Active Behavior Orders.qview.xml b/nbri_ehr/resources/queries/study/observation_order/Active Behavior Orders.qview.xml
index d4c5c11..c2c3d7e 100644
--- a/nbri_ehr/resources/queries/study/observation_order/Active Behavior Orders.qview.xml
+++ b/nbri_ehr/resources/queries/study/observation_order/Active Behavior Orders.qview.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/observation_order/Active Clinical Orders.qview.xml b/nbri_ehr/resources/queries/study/observation_order/Active Clinical Orders.qview.xml
index 1638c87..f8bc797 100644
--- a/nbri_ehr/resources/queries/study/observation_order/Active Clinical Orders.qview.xml
+++ b/nbri_ehr/resources/queries/study/observation_order/Active Clinical Orders.qview.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/observation_order/Behavior Orders.qview.xml b/nbri_ehr/resources/queries/study/observation_order/Behavior Orders.qview.xml
index 343d9b8..ad56df0 100644
--- a/nbri_ehr/resources/queries/study/observation_order/Behavior Orders.qview.xml
+++ b/nbri_ehr/resources/queries/study/observation_order/Behavior Orders.qview.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/observation_order/Clinical Orders.qview.xml b/nbri_ehr/resources/queries/study/observation_order/Clinical Orders.qview.xml
index 5e1e749..24a0828 100644
--- a/nbri_ehr/resources/queries/study/observation_order/Clinical Orders.qview.xml
+++ b/nbri_ehr/resources/queries/study/observation_order/Clinical Orders.qview.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/pairingSummary/.qview.xml b/nbri_ehr/resources/queries/study/pairingSummary/.qview.xml
index f0fdcf4..48d82d6 100644
--- a/nbri_ehr/resources/queries/study/pairingSummary/.qview.xml
+++ b/nbri_ehr/resources/queries/study/pairingSummary/.qview.xml
@@ -6,7 +6,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/pairingSummary/Active Pairing.qview.xml b/nbri_ehr/resources/queries/study/pairingSummary/Active Pairing.qview.xml
index 14e0dfe..689f99f 100644
--- a/nbri_ehr/resources/queries/study/pairingSummary/Active Pairing.qview.xml
+++ b/nbri_ehr/resources/queries/study/pairingSummary/Active Pairing.qview.xml
@@ -9,7 +9,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/pairingSummary/Pairing History.qview.xml b/nbri_ehr/resources/queries/study/pairingSummary/Pairing History.qview.xml
index 1a11617..81ee864 100644
--- a/nbri_ehr/resources/queries/study/pairingSummary/Pairing History.qview.xml
+++ b/nbri_ehr/resources/queries/study/pairingSummary/Pairing History.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/pairings/.qview.xml b/nbri_ehr/resources/queries/study/pairings/.qview.xml
index 26a8731..1a55dfa 100644
--- a/nbri_ehr/resources/queries/study/pairings/.qview.xml
+++ b/nbri_ehr/resources/queries/study/pairings/.qview.xml
@@ -6,7 +6,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/physicalExam/.qview.xml b/nbri_ehr/resources/queries/study/physicalExam/.qview.xml
index caf3fb0..3fb7d6a 100644
--- a/nbri_ehr/resources/queries/study/physicalExam/.qview.xml
+++ b/nbri_ehr/resources/queries/study/physicalExam/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/prc/.qview.xml b/nbri_ehr/resources/queries/study/prc/.qview.xml
index f5ed643..089bd36 100644
--- a/nbri_ehr/resources/queries/study/prc/.qview.xml
+++ b/nbri_ehr/resources/queries/study/prc/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/prcOverdue/.qview.xml b/nbri_ehr/resources/queries/study/prcOverdue/.qview.xml
index b8bf120..7957d9f 100644
--- a/nbri_ehr/resources/queries/study/prcOverdue/.qview.xml
+++ b/nbri_ehr/resources/queries/study/prcOverdue/.qview.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/prcSchedule/.qview.xml b/nbri_ehr/resources/queries/study/prcSchedule/.qview.xml
index 4604b77..227ae69 100644
--- a/nbri_ehr/resources/queries/study/prcSchedule/.qview.xml
+++ b/nbri_ehr/resources/queries/study/prcSchedule/.qview.xml
@@ -3,7 +3,7 @@
-
+
@@ -17,7 +17,7 @@
-
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/prc_order/.qview.xml b/nbri_ehr/resources/queries/study/prc_order/.qview.xml
index b9de6c1..8da38d4 100644
--- a/nbri_ehr/resources/queries/study/prc_order/.qview.xml
+++ b/nbri_ehr/resources/queries/study/prc_order/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/prc_order_report/.qview.xml b/nbri_ehr/resources/queries/study/prc_order_report/.qview.xml
index 62d20f0..3402400 100644
--- a/nbri_ehr/resources/queries/study/prc_order_report/.qview.xml
+++ b/nbri_ehr/resources/queries/study/prc_order_report/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/treatmentSchedule/.qview.xml b/nbri_ehr/resources/queries/study/treatmentSchedule/.qview.xml
index 4c57f0b..e773c14 100644
--- a/nbri_ehr/resources/queries/study/treatmentSchedule/.qview.xml
+++ b/nbri_ehr/resources/queries/study/treatmentSchedule/.qview.xml
@@ -3,7 +3,7 @@
-
+
@@ -22,7 +22,7 @@
-
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/treatment_order/.qview.xml b/nbri_ehr/resources/queries/study/treatment_order/.qview.xml
index 27608af..2b14b7b 100644
--- a/nbri_ehr/resources/queries/study/treatment_order/.qview.xml
+++ b/nbri_ehr/resources/queries/study/treatment_order/.qview.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/vitals/.qview.xml b/nbri_ehr/resources/queries/study/vitals/.qview.xml
index f3b831c..b6bc349 100644
--- a/nbri_ehr/resources/queries/study/vitals/.qview.xml
+++ b/nbri_ehr/resources/queries/study/vitals/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/queries/study/weight/.qview.xml b/nbri_ehr/resources/queries/study/weight/.qview.xml
index b27ffe4..b6d1a95 100644
--- a/nbri_ehr/resources/queries/study/weight/.qview.xml
+++ b/nbri_ehr/resources/queries/study/weight/.qview.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/nbri_ehr/resources/referenceStudy/study/datasets/datasets_manifest.xml b/nbri_ehr/resources/referenceStudy/study/datasets/datasets_manifest.xml
index 1d03f01..a4cd2a4 100644
--- a/nbri_ehr/resources/referenceStudy/study/datasets/datasets_manifest.xml
+++ b/nbri_ehr/resources/referenceStudy/study/datasets/datasets_manifest.xml
@@ -20,7 +20,6 @@
-
diff --git a/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml b/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml
index 42317f7..47283a3 100644
--- a/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml
+++ b/nbri_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml
@@ -623,30 +623,6 @@
-
-
-
- varchar
- http://cpas.labkey.com/Study#ParticipantId
-
- ptid
-
-
-
- timestamp
- http://cpas.labkey.com/Study#VisitDate
- http://cpas.labkey.com/Study#VisitDate
-
-
- varchar
-
-
- varchar
-
-
- Historical Other
- Captures miscellaneous historical health and research records for primates that do not fit other specific categories.
-
Housing
Tracks housing assignments and location transfers for primates within the research facility.
diff --git a/nbri_ehr/resources/reports/additionalReports.tsv b/nbri_ehr/resources/reports/additionalReports.tsv
index 8eddd91..efaa2ad 100644
--- a/nbri_ehr/resources/reports/additionalReports.tsv
+++ b/nbri_ehr/resources/reports/additionalReports.tsv
@@ -49,5 +49,4 @@ behaviorRemarks Behavior query Behavior Remarks true study BehaviorClinRemarks
clinObsBehavior Behavior query Observations true study behaviorObservations date false false qcstate/publicdata This report contains one record for each encounter with each animal, including surergies, exams, procedures, etc.
clinremarks Clinical query Clinical Remarks true study ClinicalClinRemarks date false false qcstate/publicdata This report contains the clinical remarks entered about each animal
physicalExam Clinical query Exam History True study physicalExam date false false qcstate/publicdata This report displays physical exam data for the selected animal
-historicalOther General query Historical True study historicalOther date false false qcstate/publicdata This report displays historical events from legacy systems
conceptionsByDam Reproductive Management query Conceptions by Dam true nbri_ehr ConceptionsByDam ConceptDate false false qcstate/publicdata This report displays conception records where the selected animal is the dam
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/cageDetails.html b/nbri_ehr/resources/views/cageDetails.html
index a0c5da2..fde98de 100644
--- a/nbri_ehr/resources/views/cageDetails.html
+++ b/nbri_ehr/resources/views/cageDetails.html
@@ -42,7 +42,7 @@
schemaName: 'ehr_lookups',
queryName: 'cage',
filterArray: detailFilterArray,
- columns: 'cage,room,room/floor,room/floor/building,room/floor/building/area,outdoor',
+ columns: 'cage,room,room/building,room/building/area,outdoor',
},
title: 'Cage Details',
renderTo: 'cageDetails_' + webpart.wrapperDivId,
@@ -56,7 +56,7 @@
queryName: 'cage',
filterArray: detailFilterArray,
sort: 'cage',
- columns: 'cage,room,room/floor,room/floor/building,room/floor/building/area,outdoor',
+ columns: 'cage,room,room/building,room/building/area,outdoor',
}).render('cageDetails_' + webpart.wrapperDivId);
}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/history/NBRIHousingDataSource.java b/nbri_ehr/src/org/labkey/nbri_ehr/history/NBRIHousingDataSource.java
index 91fdf41..0749f01 100644
--- a/nbri_ehr/src/org/labkey/nbri_ehr/history/NBRIHousingDataSource.java
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/history/NBRIHousingDataSource.java
@@ -36,7 +36,7 @@ public NBRIHousingDataSource(Module module)
@Override
protected Set getColumnNames()
{
- return PageFlowUtil.set("Id", "date", "cage/cage", "room/fullRoom", "reason", "remark");
+ return PageFlowUtil.set("Id", "date", "cage/cage", "room/room", "reason", "remark");
}
@Override
@@ -44,7 +44,7 @@ protected String getHtml(Container c, Results rs, boolean redacted) throws SQLEx
{
StringBuilder sb = new StringBuilder();
- FieldKey room = FieldKey.fromString("room/fullRoom");
+ FieldKey room = FieldKey.fromString("room/room");
FieldKey cage = FieldKey.fromString("cage/cage");
String value = "Unknown";
if (rs.hasColumn(cage) && rs.getObject(cage) != null)
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/table/NBRI_EHRCustomizer.java b/nbri_ehr/src/org/labkey/nbri_ehr/table/NBRI_EHRCustomizer.java
index 38fd24d..b365680 100644
--- a/nbri_ehr/src/org/labkey/nbri_ehr/table/NBRI_EHRCustomizer.java
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/table/NBRI_EHRCustomizer.java
@@ -93,16 +93,6 @@ public void customize(TableInfo table)
customizeTasks(ti);
}
- if (matches(ti, "ehr_lookups", "rooms"))
- {
- customizeRooms(ti);
- }
-
- if (matches(ti, "ehr_lookups", "floors"))
- {
- customizeFloors(ti);
- }
-
if (matches(ti, "nbri_ehr", "necropsyTasks"))
{
addNecropsyReportLink(ti);
@@ -581,60 +571,6 @@ public boolean isEditable()
}
}
- private void customizeRooms(AbstractTableInfo ti)
- {
- ColumnInfo roomCol = ti.getColumn("name");
- ColumnInfo floorCol = ti.getColumn("floor");
- if (roomCol != null && floorCol != null && ti.getColumn("fullRoom") == null)
- {
- ExprColumn col = new ExprColumn(ti, new FieldKey(null, "fullRoom"), new SQLFragment("##ERROR"), JdbcType.VARCHAR, roomCol, floorCol) {
- @Override
- public SQLFragment getValueSql(String tableAlias)
- {
- // Need to subclass and override this function due to issue using ExprColumn.STR_TABLE_ALIAS with extensible columns
- SQLFragment sql = new SQLFragment("(SELECT COALESCE(r.room, 'Room N/A') || ', ' || COALESCE(r.floor, 'Floor N/A') || ', ' || COALESCE(r.building, 'Building N/A') \n" +
- " FROM (\n" +
- " SELECT \n").append(roomCol.getValueSql(tableAlias));
- sql.append(" AS room,\n" +
- " ff.name as floor,\n" +
- " bb.description as building\n" +
- " FROM ehr_lookups.floors ff \n" +
- " JOIN ehr_lookups.buildings bb ON ff.building = bb.name\n" +
- " WHERE ff.floor =\n").append(floorCol.getValueSql(tableAlias));
- sql.append(" ) r\n" +
- " )");
-
- return sql;
- }
- };
- col.setName("fullRoom");
- col.setLabel("Full Room");
- ti.addColumn(col);
- }
- }
-
- private void customizeFloors(AbstractTableInfo ti)
- {
- ColumnInfo floorCol = ti.getColumn("name");
- ColumnInfo bldgCol = ti.getColumn("building");
- if (floorCol != null && bldgCol != null && ti.getColumn("fullFloor") == null)
- {
- SQLFragment sql = new SQLFragment("(SELECT COALESCE(r.floor, 'Floor N/A') || ', ' || COALESCE(r.building, 'Building N/A') \n" +
- " FROM (\n" +
- " SELECT " + ExprColumn.STR_TABLE_ALIAS + ".name\n");
- sql.append(" AS floor,\n" +
- " bb.description as building\n" +
- " FROM ehr_lookups.buildings bb \n" +
- " WHERE bb.name = " + ExprColumn.STR_TABLE_ALIAS + ".building\n");
- sql.append(" ) r\n" +
- " )");
-
- ExprColumn col = new ExprColumn(ti, "fullFloor", sql, JdbcType.VARCHAR, floorCol, bldgCol);
- col.setLabel("Full Floor");
- ti.addColumn(col);
- }
- }
-
private void customizeTasks(AbstractTableInfo ti)
{
DetailsURL detailsURL = DetailsURL.fromString("/ehr/dataEntryFormDetails.view?formType=${formtype}&taskid=${taskid}");
@@ -777,7 +713,7 @@ public void doSharedCustomization(AbstractTableInfo ti)
{
UserSchema us = getEHRUserSchema(ti, "ehr_lookups");
col.setLabel("Room");
- col.setFk(new QueryForeignKey(ti.getUserSchema(), ti.getContainerFilter(), us, null, "rooms", "room", "fullRoom"));
+ col.setFk(new QueryForeignKey(ti.getUserSchema(), ti.getContainerFilter(), us, null, "rooms", "room", "room"));
col.setURL(StringExpressionFactory.createURL("/nbri_ehr/cageDetails.view?room=${room}"));
}
if ("building".equalsIgnoreCase(col.getName()) && !ti.getName().equalsIgnoreCase("buildings"))
@@ -790,7 +726,7 @@ public void doSharedCustomization(AbstractTableInfo ti)
{
UserSchema us = getEHRUserSchema(ti, "ehr_lookups");
col.setLabel("Floor");
- col.setFk(new QueryForeignKey(ti.getUserSchema(), ti.getContainerFilter(), us, null, "floors", "floor", "fullFloor"));
+ col.setFk(new QueryForeignKey(ti.getUserSchema(), ti.getContainerFilter(), us, null, "floors", "floor", "name"));
}
if ("area".equalsIgnoreCase(col.getName()) && !ti.getName().equalsIgnoreCase("areas"))
{
diff --git a/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetHousing.tsv b/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetHousing.tsv
index 23969c0..487f797 100644
--- a/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetHousing.tsv
+++ b/nbri_ehr/test/sampledata/nbri_ehr/study/study/datasets/datasetHousing.tsv
@@ -1,9 +1,9 @@
-objectid Id QCStateLabel date enddate cage performedby
-1 44444 Completed -1381d -1321d 11 - Rm 202B - ZZA 1004
-2 44444 Completed -1321d 11 - Rm 202B - ZZB 1004
-3 44446 Completed -1406d -1316d 11 - Rm 202B - ZZA 1004
-4 44446 Completed -1316d 11 - Rm 202B - ZZC 1004
-5 TEST4551032 Completed -6362d 11 - Rm 202B - ZZD 1004
-6 44442 Completed -6100d -5920d 11 - Rm 202B - ZZA 1004
-7 44442 Completed -5920d 11 - Rm 202B - ZZC 1004
-8 44443 Completed -6100d 11 - Rm 202B - ZZF 1004
+objectid Id QCStateLabel date enddate room cage performedby
+1 44444 Completed -1381d -1321d TestBuilding-R1 TestBuilding-R1-C1 1004
+2 44444 Completed -1321d TestBuilding-R1 TestBuilding-R1-C2 1004
+3 44446 Completed -1406d -1316d TestBuilding-R1 TestBuilding-R1-C1 1004
+4 44446 Completed -1316d TestBuilding-R2 TestBuilding-R2-C3 1004
+5 TEST4551032 Completed -6362d TestBuilding-R3 TestBuilding-R3-C4 1004
+6 44442 Completed -6100d -5920d TestBuilding-R1 TestBuilding-R1-C1 1004
+7 44442 Completed -5920d TestBuilding-R2 TestBuilding-R2-C3 1004
+8 44443 Completed -6100d TestBuilding-R1 TestBuilding-R1-C2 1004
diff --git a/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java b/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java
index a06c389..487d1f2 100644
--- a/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java
+++ b/nbri_ehr/test/src/org.labkey.test/tests.nbri_ehr/NBRI_EHRTest.java
@@ -34,6 +34,8 @@
import org.labkey.remoteapi.query.ImportDataCommand;
import org.labkey.remoteapi.query.InsertRowsCommand;
import org.labkey.remoteapi.query.RowsResponse;
+import org.labkey.remoteapi.query.SelectRowsCommand;
+import org.labkey.remoteapi.query.SelectRowsResponse;
import org.labkey.remoteapi.security.CreateUserResponse;
import org.labkey.test.Locator;
import org.labkey.test.TestFileUtils;
@@ -107,6 +109,28 @@ public class NBRI_EHRTest extends AbstractGenericEHRTest implements PostgresOnly
// createTestSubjects so the clinical case form raises no warnings that would keep the validation banner up.
private static final String taskGroupAnimalId = "TESTGRP9090";
+ // Rooms are keyed by building and name, so every room fixture needs a building to hang off of.
+ // 'buildings' derives its key from the description, and 'SPF' is one of the areas seeded with the ehr_lookups schema.
+ private static final String BUILDING_ID = "TestBuilding";
+ private static final String BUILDING_AREA = "SPF";
+
+ // Cage locations seeded by populateLocations, named for the room each one sits in. The cage trigger derives these
+ // from the room and cage, and housing records key off the location, so these are what belongs in a housing row's
+ // 'cage' field. datasetHousing.tsv spells the same values out, since a TSV cannot call cageLocation.
+ private static final String CAGE_IN_R1 = cageLocation("R1", "C1");
+ private static final String CAGE_IN_R3 = cageLocation("R3", "C4");
+
+ // A group pen has no cage, so its location is the room key alone. Created by testGroupPenCagemates.
+ private static final String PEN_ROOM_NAME = "PEN1";
+ private static final String[] PEN_ANIMALS = {"PEN0001", "PEN0002"};
+
+ // Housed with a cage but no room, which is how a record entered against a cage alone lands. Every cage seeded by
+ // populateLocations already has occupants from datasetHousing.tsv, so testCagematesWithoutRoom creates its own to
+ // keep the expected cagemate count exact.
+ private static final String ROOMLESS_CAGE_NAME = "C9";
+ private static final String ROOMLESS_CAGE = cageLocation("R1", ROOMLESS_CAGE_NAME);
+ private static final String[] ROOMLESS_ANIMALS = {"CAGE0001", "CAGE0002"};
+
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};
@@ -244,20 +268,62 @@ private void populateFormulary() throws IOException, CommandException
RowsResponse saveRowsResponse = insertRowsCommand.execute(getApiHelper().getConnection(), getContainerPath());
}
+ /**
+ * The key a room trigger derives for the given room name. Mirrors ehr_lookups/rooms.js.
+ */
+ private static String roomKey(String roomName)
+ {
+ return BUILDING_ID + "-" + roomName;
+ }
+
+ /**
+ * The location a cage trigger derives for the given room and cage. Mirrors ehr_lookups/cage.js.
+ */
+ private static String cageLocation(String roomName, String cageName)
+ {
+ return roomKey(roomName) + "-" + cageName;
+ }
+
+ /**
+ * Housing records store the derived room key, so the fixture rooms have to be named by key for the room
+ * lookups to resolve. The base implementation returns names that match no room in this study.
+ */
+ @Override
+ protected String[] getRooms()
+ {
+ return new String[]{roomKey("R1"), roomKey("R2"), roomKey("R3")};
+ }
+
+ @LogMethod
+ private void populateBuildingRecords() throws Exception
+ {
+ InsertRowsCommand insertCmd = new InsertRowsCommand("ehr_lookups", "buildings");
+ Map rowMap = new HashMap<>();
+ // Supply the derived key rather than relying on the trigger to fill it, matching how the base class seeds rooms.
+ rowMap.put("name", BUILDING_ID);
+ rowMap.put("description", BUILDING_ID);
+ rowMap.put("area", BUILDING_AREA);
+ insertCmd.addRow(rowMap);
+
+ insertCmd.execute(createDefaultConnection(), getContainerPath());
+ }
+
@Override
protected void populateRoomRecords() throws Exception
{
+ populateBuildingRecords();
+
InsertRowsCommand insertCmd = new InsertRowsCommand("ehr_lookups", "rooms");
Map rowMap = new HashMap<>();
rowMap.put("name", ROOM_ID);
- rowMap.put("floor", "floor1");
+ rowMap.put("building", BUILDING_ID);
rowMap.put("housingType", 1);
rowMap.put("housingCondition", 1);
insertCmd.addRow(rowMap);
rowMap = new HashMap<>();
rowMap.put("name", ROOM_ID2);
- rowMap.put("floor", "floor2");
+ rowMap.put("building", BUILDING_ID);
rowMap.put("housingType", 1);
rowMap.put("housingCondition", 1);
insertCmd.addRow(rowMap);
@@ -321,19 +387,21 @@ private void enableNotification(String notification)
private void populateLocations() throws IOException, CommandException
{
goToEHRFolder();
+ // BUILDING_ID is created by populateRoomRecords, which runs earlier as part of initProject.
log("Inserting values in rooms");
InsertRowsCommand roomCmd = new InsertRowsCommand("ehr_lookups", "rooms");
- roomCmd.addRow(Map.of("name", "R1", "floor", "F1"));
- roomCmd.addRow(Map.of("name", "R2", "floor", "F2"));
- roomCmd.addRow(Map.of("name", "R3", "floor", "F3"));
+ roomCmd.addRow(Map.of("name", "R1", "building", BUILDING_ID));
+ roomCmd.addRow(Map.of("name", "R2", "building", BUILDING_ID));
+ roomCmd.addRow(Map.of("name", "R3", "building", BUILDING_ID));
roomCmd.execute(getApiHelper().getConnection(), getContainerPath());
+ // 'location' is left out so the cage trigger derives it, exercising the same path production entry takes.
log("Inserting values in cage");
InsertRowsCommand cageCmd = new InsertRowsCommand("ehr_lookups", "cage");
- cageCmd.addRow(Map.of("location", "L1", "cage", "C1", "room", "R1"));
- cageCmd.addRow(Map.of("location", "L2", "cage", "C2", "room", "R1"));
- cageCmd.addRow(Map.of("location", "L3", "cage", "C3", "room", "R2"));
- cageCmd.addRow(Map.of("location", "L4", "cage", "C4", "room", "R3"));
+ cageCmd.addRow(Map.of("cage", "C1", "room", roomKey("R1")));
+ cageCmd.addRow(Map.of("cage", "C2", "room", roomKey("R1")));
+ cageCmd.addRow(Map.of("cage", "C3", "room", roomKey("R2")));
+ cageCmd.addRow(Map.of("cage", "C4", "room", roomKey("R3")));
cageCmd.execute(getApiHelper().getConnection(), getContainerPath());
}
@@ -439,10 +507,10 @@ protected void createTestSubjects() throws Exception
log("Creating initial housing records");
fields = new String[]{"Id", "date", "enddate", "room", "cage", "performedby"};
data = new Object[][]{
- {SUBJECTS[0], pastDate1, pastDate2, getRooms()[0], CAGES[0], 1004},
- {SUBJECTS[0], pastDate2, null, getRooms()[0], CAGES[0], 1004},
- {SUBJECTS[1], pastDate1, pastDate2, getRooms()[0], CAGES[0], 1004},
- {SUBJECTS[1], pastDate2, null, getRooms()[2], CAGES[2], 1004}
+ {SUBJECTS[0], pastDate1, pastDate2, getRooms()[0], CAGE_IN_R1, 1004},
+ {SUBJECTS[0], pastDate2, null, getRooms()[0], CAGE_IN_R1, 1004},
+ {SUBJECTS[1], pastDate1, pastDate2, getRooms()[0], CAGE_IN_R1, 1004},
+ {SUBJECTS[1], pastDate2, null, getRooms()[2], CAGE_IN_R3, 1004}
};
insertCommand = getApiHelper().prepareInsertCommand("study", "Housing", "lsid", fields, data);
getApiHelper().deleteAllRecords("study", "Housing", new Filter("Id", StringUtils.join(SUBJECTS, ";"), Filter.Operator.IN));
@@ -486,7 +554,7 @@ protected void createTestSubjects() throws Exception
fields = new String[]{"Id", "date", "enddate", "room", "cage", "performedby"};
data = new Object[][]{
- {taskGroupAnimalId, pastDate1, null, getRooms()[0], CAGES[0], 1004}
+ {taskGroupAnimalId, pastDate1, null, getRooms()[0], CAGE_IN_R1, 1004}
};
insertCommand = getApiHelper().prepareInsertCommand("study", "Housing", "lsid", fields, data);
getApiHelper().deleteAllRecords("study", "Housing", new Filter("Id", taskGroupAnimalId));
@@ -1233,7 +1301,7 @@ public void createSubjectsForDeathForm() throws IOException, CommandException
project.execute(getApiHelper().getConnection(), getContainerPath());
InsertRowsCommand housing = new InsertRowsCommand("study", "housing");
- housing.addRow(Map.of("Id", aliveAnimalId, "date", LocalDateTime.now().minusDays(10), "cage", "C4", "QCStateLabel", "Completed", "performedby", 1004));
+ housing.addRow(Map.of("Id", aliveAnimalId, "date", LocalDateTime.now().minusDays(10), "room", getRooms()[2], "cage", CAGE_IN_R3, "QCStateLabel", "Completed", "performedby", 1004));
housing.execute(getApiHelper().getConnection(), getContainerPath());
log("Marking an animal dead");
@@ -1536,6 +1604,159 @@ public void testCalculatedAgeColumns()
assertEquals("Calculated ages are incorrect", Arrays.asList("4.8", "4.0", "58.0"), row.subList(columnCount - 3, columnCount));
}
+ @Test
+ public void testRoomKeyDerivation() throws Exception
+ {
+ log("Verifying a room derives its key from its building and name");
+ SelectRowsCommand selectCmd = new SelectRowsCommand("ehr_lookups", "rooms");
+ selectCmd.setColumns(List.of("room", "name", "building"));
+ selectCmd.addFilter(new Filter("name", "R1"));
+ SelectRowsResponse response = selectCmd.execute(getApiHelper().getConnection(), getContainerPath());
+
+ Assert.assertEquals("Expected exactly one room named R1", 1, response.getRows().size());
+ Map room = response.getRows().get(0);
+ Assert.assertEquals("Room key should combine the building and the name", roomKey("R1"), room.get("room"));
+ Assert.assertEquals("Room should belong to the seeded building", BUILDING_ID, room.get("building"));
+ }
+
+ @Test
+ public void testRoomRequiresBuilding()
+ {
+ log("Verifying a room cannot be created without a building");
+ InsertRowsCommand insertCmd = new InsertRowsCommand("ehr_lookups", "rooms");
+ insertCmd.addRow(Map.of("name", "NOBUILDING"));
+
+ try
+ {
+ insertCmd.execute(getApiHelper().getConnection(), getContainerPath());
+ Assert.fail("Room insert should have been rejected when no building was supplied");
+ }
+ catch (IOException | CommandException e)
+ {
+ Assert.assertTrue("Unexpected failure inserting a room without a building: " + e.getMessage(),
+ e.getMessage() != null && e.getMessage().contains("Building is required"));
+ }
+ }
+
+ @Test
+ public void testDuplicateBuildingRejected()
+ {
+ log("Verifying a second building cannot reuse an existing description");
+ // The description alone is the building key now, so reusing the seeded building's description would collide.
+ InsertRowsCommand insertCmd = new InsertRowsCommand("ehr_lookups", "buildings");
+ insertCmd.addRow(Map.of("description", BUILDING_ID, "area", BUILDING_AREA));
+
+ try
+ {
+ insertCmd.execute(getApiHelper().getConnection(), getContainerPath());
+ Assert.fail("Building insert should have been rejected when the description was already in use");
+ }
+ catch (IOException | CommandException e)
+ {
+ Assert.assertTrue("Unexpected failure inserting a duplicate building: " + e.getMessage(),
+ e.getMessage() != null && e.getMessage().contains("already exists"));
+ }
+ }
+
+ @Test
+ public void testGroupPenCagemates() throws Exception
+ {
+ String penRoom = roomKey(PEN_ROOM_NAME);
+
+ log("Creating a group pen, whose location is the room with no cage");
+ InsertRowsCommand roomCmd = new InsertRowsCommand("ehr_lookups", "rooms");
+ roomCmd.addRow(Map.of("name", PEN_ROOM_NAME, "building", BUILDING_ID));
+ roomCmd.execute(getApiHelper().getConnection(), getContainerPath());
+
+ // With no cage supplied the trigger derives the location as the room key alone.
+ InsertRowsCommand penCmd = new InsertRowsCommand("ehr_lookups", "cage");
+ penCmd.addRow(Map.of("room", penRoom));
+ penCmd.execute(getApiHelper().getConnection(), getContainerPath());
+
+ createAliveAnimals(PEN_ANIMALS);
+
+ // The cage is deliberately left null: a penned animal is housed against the room, which is the case the
+ // cagemates query has to bound by room rather than by cage.
+ log("Housing two animals in the pen, with no cage");
+ houseAnimals(PEN_ANIMALS, penRoom, null);
+
+ log("Verifying penned animals resolve as each other's cagemates");
+ assertCagemates(PEN_ANIMALS[0], 2, PEN_ANIMALS[1]);
+ }
+
+ @Test
+ public void testCagematesWithoutRoom() throws Exception
+ {
+ log("Creating an unoccupied cage");
+ InsertRowsCommand cageCmd = new InsertRowsCommand("ehr_lookups", "cage");
+ cageCmd.addRow(Map.of("cage", ROOMLESS_CAGE_NAME, "room", roomKey("R1")));
+ cageCmd.execute(getApiHelper().getConnection(), getContainerPath());
+
+ createAliveAnimals(ROOMLESS_ANIMALS);
+
+ // The cage is a location key that already names its room, so the room is redundant here and nothing requires
+ // it. Cagemates must still resolve when it is absent.
+ log("Housing two animals in the same cage, with no room");
+ houseAnimals(ROOMLESS_ANIMALS, null, ROOMLESS_CAGE);
+
+ log("Verifying caged animals resolve as each other's cagemates without a room");
+ assertCagemates(ROOMLESS_ANIMALS[0], 2, ROOMLESS_ANIMALS[1]);
+ }
+
+ /**
+ * Creates living demographics records for the given animals, replacing any left behind by an earlier run.
+ */
+ private void createAliveAnimals(String[] animalIds) throws Exception
+ {
+ log("Creating animals " + StringUtils.join(animalIds, ", "));
+ String[] fields = new String[]{"Id", "Species", "Birth", "Gender", "date", "calculated_status", "objectid", "performedby"};
+ Object[][] data = new Object[animalIds.length][];
+ for (int i = 0; i < animalIds.length; i++)
+ {
+ data[i] = new Object[]{animalIds[i], "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", StringUtils.join(animalIds, ";"), Filter.Operator.IN));
+ getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), insertCommand, getExtraContext());
+ }
+
+ /**
+ * Opens a completed housing record for each animal at the given location, replacing any left behind by an earlier
+ * run. Either the room or the cage may be null, which is how records entered against one alone land.
+ */
+ private void houseAnimals(String[] animalIds, String room, String cage) throws Exception
+ {
+ String[] fields = new String[]{"Id", "date", "enddate", "room", "cage", "QCStateLabel", "performedby"};
+ Object[][] data = new Object[animalIds.length][];
+ for (int i = 0; i < animalIds.length; i++)
+ {
+ data[i] = new Object[]{animalIds[i], new Date(), null, room, cage, EHRQCState.COMPLETED.label, 1004};
+ }
+
+ SimplePostCommand insertCommand = getApiHelper().prepareInsertCommand("study", "Housing", "lsid", fields, data);
+ getApiHelper().deleteAllRecords("study", "Housing", new Filter("Id", StringUtils.join(animalIds, ";"), Filter.Operator.IN));
+ getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), insertCommand, getExtraContext());
+ }
+
+ /**
+ * Asserts the cagemates report resolves the expected companions for an animal. A null total means the join matched
+ * nothing at all, which is reported as its own failure rather than as an unexpected count.
+ */
+ private void assertCagemates(String animalId, int expectedTotal, String expectedCompanion) throws Exception
+ {
+ SelectRowsCommand selectCmd = new SelectRowsCommand("study", "demographicsCagemates");
+ selectCmd.addFilter(new Filter("Id", animalId));
+ SelectRowsResponse response = selectCmd.execute(getApiHelper().getConnection(), getContainerPath());
+
+ Assert.assertEquals("Expected one cagemates row for " + animalId, 1, response.getRows().size());
+ Map cagemates = response.getRows().get(0);
+ Assert.assertNotNull("Cagemates resolved no location for " + animalId, cagemates.get("total"));
+ Assert.assertEquals("Unexpected cagemate count for " + animalId, expectedTotal, ((Number) cagemates.get("total")).intValue());
+ Assert.assertTrue("Cagemate list should name " + expectedCompanion + ", was: " + cagemates.get("animals"),
+ String.valueOf(cagemates.get("animals")).contains(expectedCompanion));
+ }
+
@Test
public void testLookupPage() throws Exception
{