+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/folderTypes/NBRI_EHR.folderType.xml b/nbri_ehr/resources/folderTypes/NBRI_EHR.folderType.xml
new file mode 100644
index 0000000..2dfc4a4
--- /dev/null
+++ b/nbri_ehr/resources/folderTypes/NBRI_EHR.folderType.xml
@@ -0,0 +1,70 @@
+
+ NBRI EHR
+ Folder type used for the National Biomedical Research Institute's EHR
+
+
+
+
+
+
+ Electronic Health Record
+ menu
+
+
+ Quick Search
+ menu
+
+
+
+
+
+ overview
+
Overview
+
+
+ EHR Front Page
+ body
+
+
+
+
+ animalHistory
+
+
+
+ animalSearch
+
+
+
+
+ Animal Search
+ body
+
+
+
+
+
+ Core
+ Study
+ DataIntegration
+ EHR
+ NBRI_EHR
+
+ Core
+ false
+
diff --git a/nbri_ehr/resources/module.xml b/nbri_ehr/resources/module.xml
new file mode 100644
index 0000000..03d5fae
--- /dev/null
+++ b/nbri_ehr/resources/module.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/ehr/activeProtocols.sql b/nbri_ehr/resources/queries/ehr/activeProtocols.sql
new file mode 100644
index 0000000..a33358e
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/activeProtocols.sql
@@ -0,0 +1,7 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT protocol, title FROM ehr.protocol pr
+WHERE pr.inactiveDate IS NULL OR pr.inactiveDate > now()
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/ehr/my_tasks.query.xml b/nbri_ehr/resources/queries/ehr/my_tasks.query.xml
new file mode 100644
index 0000000..2a6ca88
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/my_tasks.query.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ My Tasks
+
+
+
+ RowId
+
+
+ Task Id
+ true
+ true
+
+
+ Task Type
+
+
+ Assigned To
+ true
+
+ core
+ PrincipalsWithoutAdmin
+ userid
+
+
+
+ Due Date
+ true
+ DateTime
+
+
+ Created By
+
+
+ Created
+
+
+ Modified By
+
+
+ Modified
+
+
+
+ ehr.context
+ /ehr/studyButtons.js
+
+ Grid Views
+
+
+ Charts
+
+
+ Export
+
+
+ Print
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/ehr/my_tasks.sql b/nbri_ehr/resources/queries/ehr/my_tasks.sql
new file mode 100644
index 0000000..dae6220
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/my_tasks.sql
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+
+taskid,
+rowid,
+updateTitle,
+category,
+title,
+formtype,
+qcstate,
+assignedto,
+duedate,
+requestid,
+datecompleted,
+modifiedby,
+modified,
+createdby,
+created,
+description
+
+
+FROM ehr.tasks t
+
+WHERE ISMEMBEROF(t.assignedto)
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/ehr/my_tasks/.qview.xml b/nbri_ehr/resources/queries/ehr/my_tasks/.qview.xml
new file mode 100644
index 0000000..8388595
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/my_tasks/.qview.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/ehr/my_tasks/Active Tasks.qview.xml b/nbri_ehr/resources/queries/ehr/my_tasks/Active Tasks.qview.xml
new file mode 100644
index 0000000..069ab01
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/my_tasks/Active Tasks.qview.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/ehr/my_tasks/Review Required.qview.xml b/nbri_ehr/resources/queries/ehr/my_tasks/Review Required.qview.xml
new file mode 100644
index 0000000..d02c415
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/my_tasks/Review Required.qview.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/ehr/observation_types.query.xml b/nbri_ehr/resources/queries/ehr/observation_types.query.xml
new file mode 100644
index 0000000..16fbabe
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/observation_types.query.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Observation Types
+
+
+ false
+
+
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/ehr/project.js b/nbri_ehr/resources/queries/ehr/project.js
new file mode 100644
index 0000000..bb57d44
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/project.js
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+var nextProjectNum = -1;
+
+function onInit(event, helper){
+
+ //ETL'd data has integer values in the 'project' column that is meaningless to the client,
+ //but since 'project' is a required field, find the highest num in the existing data and add 1 for inserting new Projects.
+ LABKEY.Query.executeSql({
+ schemaName: 'ehr',
+ sql: 'SELECT MAX(project) AS latestProjectNum FROM ehr.project',
+ containerFilter: LABKEY.Query.containerFilter.allFolders,
+ success: function(data) {
+ nextProjectNum = data.rows[0].latestProjectNum + 1;
+ },
+ failure: function (error) {
+ console.log("Error getting the latest 'Project Id' in project trigger onInit()\n" + error);
+ }
+ });
+}
+function onUpsert(helper, scriptErrors, row, oldRow) {
+ if (!helper.isETL()) {
+ if (!row.project) {
+ row.project = nextProjectNum;
+ }
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/ehr/project.query.xml b/nbri_ehr/resources/queries/ehr/project.query.xml
new file mode 100644
index 0000000..d2db2ca
--- /dev/null
+++ b/nbri_ehr/resources/queries/ehr/project.query.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/nbri_ehr/dateRange.sql b/nbri_ehr/resources/queries/nbri_ehr/dateRange.sql
new file mode 100644
index 0000000..aadf062
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/dateRange.sql
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+PARAMETERS(StartDate TIMESTAMP, NumDays INTEGER DEFAULT 1)
+
+SELECT
+i.date,
+CAST(i.date as date) as dateOnly,
+cast(dayofyear(i.date) as integer) as DayOfYear,
+cast(dayofmonth(i.date) as integer) as DayOfMonth,
+cast(dayofweek(i.date) as integer) as DayOfWeek,
+ceiling(cast(dayofmonth(i.date) as float) / 7.0) as WeekOfMonth,
+cast(week(i.date) as integer) as WeekOfYear,
+CAST(StartDate AS TIMESTAMP) as startDate @hidden,
+cast(NumDays as integer) as numDays @hidden
+
+FROM (SELECT
+
+timestampadd('SQL_TSI_DAY', i.value, CAST(COALESCE(CAST(StartDate as date), curdate()) AS TIMESTAMP)) as date
+
+FROM ldk.integers i
+
+WHERE i.value < NumDays) i
diff --git a/nbri_ehr/resources/queries/nbri_ehr/locationTypes/.qview.xml b/nbri_ehr/resources/queries/nbri_ehr/locationTypes/.qview.xml
new file mode 100644
index 0000000..b1d4016
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/locationTypes/.qview.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/nbri_ehr/locations.query.xml b/nbri_ehr/resources/queries/nbri_ehr/locations.query.xml
new file mode 100644
index 0000000..3f4fe51
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/locations.query.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ Location Type
+
+ nbri_ehr
+ locationTypes
+ locationTypeId
+ name
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/nbri_ehr/locations/.qview.xml b/nbri_ehr/resources/queries/nbri_ehr/locations/.qview.xml
new file mode 100644
index 0000000..08af13d
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/locations/.qview.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/nbri_ehr/locationsMapping.query.xml b/nbri_ehr/resources/queries/nbri_ehr/locationsMapping.query.xml
new file mode 100644
index 0000000..d2357c7
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/locationsMapping.query.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/nbri_ehr/necropsyTasks.sql b/nbri_ehr/resources/queries/nbri_ehr/necropsyTasks.sql
new file mode 100644
index 0000000..d7efcf4
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/necropsyTasks.sql
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+ t.taskid,
+ d.Id AS animalId,
+ t.updateTitle AS form,
+ t.assignedTo,
+ t.created,
+ t.createdBy,
+ t.modified,
+ t.modifiedBy,
+ CASE WHEN t.qcstate.Label = 'Request: Pending' THEN 'Death Entered: Necropsy Pending'
+ WHEN t.qcstate.Label = 'Review Required' THEN 'Necropsy Entered: Review Required'
+ ELSE t.qcstate.Label
+ END AS status,
+ CASE WHEN t.qcstate.Label = 'Request: Pending' THEN 1
+ WHEN t.qcstate.Label = 'Review Required' THEN 2
+ WHEN t.qcstate.Label = 'Completed' THEN 3
+ ELSE 4
+ END AS statusOrder
+FROM ehr.tasks t
+LEFT JOIN study.deaths d ON d.taskid = t.taskid
+WHERE formType = 'Necropsy'
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/nbri_ehr/necropsyTasks/.qview.xml b/nbri_ehr/resources/queries/nbri_ehr/necropsyTasks/.qview.xml
new file mode 100644
index 0000000..908527e
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/necropsyTasks/.qview.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/nbri_ehr/staff.query.xml b/nbri_ehr/resources/queries/nbri_ehr/staff.query.xml
new file mode 100644
index 0000000..c39c37c
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/staff.query.xml
@@ -0,0 +1,128 @@
+
+
+
+
+ Staff
+
+
+ First Name
+
+
+ Last Name
+
+
+ Middle Name
+
+
+ Display Name
+
+
+ Hire Date
+
+
+ Last Employ Date
+
+
+ Office Phone
+
+
+ Office Phone Ext
+
+
+ Office Fax
+
+
+ Home Phone
+
+
+ Beeper Phone
+
+
+ Cell Phone
+
+
+ Emergency Contact
+
+
+ Emergency Phone
+
+
+ Home Address 1
+
+
+ Home Address2
+
+
+ Home City
+
+
+ Home State
+
+
+ Home Zip
+
+
+ Home Zip Ext
+
+
+ Home Country
+
+
+ Office Address 1
+
+
+ Office Address 2
+
+
+ Office City
+
+
+ Office State
+
+
+ Office Zip
+
+
+ Office Zip Ext
+
+
+ Office Country
+
+
+ Birth Date
+
+
+ High School Name
+
+
+ College Grad Date
+
+
+ College Degree
+
+
+ College Major
+
+
+ College Name
+
+
+ Supervisor
+
+ nbri_ehr
+ staff
+ staffId
+ displayName
+
+
+
+ Position Name
+
+
+ Position Description
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/nbri_ehr/stress.query.xml b/nbri_ehr/resources/queries/nbri_ehr/stress.query.xml
new file mode 100644
index 0000000..590673d
--- /dev/null
+++ b/nbri_ehr/resources/queries/nbri_ehr/stress.query.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/blood/.qview.xml b/nbri_ehr/resources/queries/study/blood/.qview.xml
new file mode 100644
index 0000000..831d6b5
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/blood/.qview.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/bloodDrawChanges.sql b/nbri_ehr/resources/queries/study/bloodDrawChanges.sql
new file mode 100644
index 0000000..6ea698a
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/bloodDrawChanges.sql
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+--this query is designed to return any dates when allowable blood draw volume changes
+--this includes dates of blood draws, plus the date those draws drop off
+PARAMETERS(DATE_INTERVAL INTEGER)
+
+SELECT
+ b2.id,
+ b2.dateOnly,
+ b2.quantity,
+ DATE_INTERVAL as blood_draw_interval,
+ TIMESTAMPADD('SQL_TSI_DAY', (-1 * DATE_INTERVAL), b2.dateOnly) as minDate,
+ TIMESTAMPADD('SQL_TSI_DAY', DATE_INTERVAL, b2.dateOnly) as maxDate,
+
+FROM (
+ SELECT
+ b.id,
+ b.dateOnly,
+ sum(b.quantity) as quantity
+
+ FROM (
+ --find all blood draws within the interval, looking backwards
+ SELECT
+ b.id,
+ b.dateOnly,
+ b.quantity,
+ FROM study.blood b
+ WHERE b.dateOnly > timestampadd('SQL_TSI_DAY', -1 * DATE_INTERVAL, curdate())
+
+ UNION ALL
+
+ --join 1 row for the current date
+ SELECT
+ d1.id,
+ curdate() as dateOnly,
+ 0 as quantity,
+ FROM study.demographics d1
+ WHERE d1.calculated_status = 'Alive'
+
+ UNION ALL
+
+ --add one row for each date when the draw drops off the record
+ SELECT
+ b.id,
+ timestampadd('SQL_TSI_DAY', DATE_INTERVAL, b.dateOnly),
+ 0 as quantity,
+ FROM study.blood b
+ WHERE timestampadd('SQL_TSI_DAY', DATE_INTERVAL, b.dateOnly) >= timestampadd('SQL_TSI_DAY', -1 * DATE_INTERVAL, curdate())
+
+ ) b
+
+ GROUP BY b.id, b.dateOnly
+ ) b2
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/breeder/.qview.xml b/nbri_ehr/resources/queries/study/breeder/.qview.xml
new file mode 100644
index 0000000..448bd0f
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/breeder/.qview.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/cases.js b/nbri_ehr/resources/queries/study/cases.js
new file mode 100644
index 0000000..d995a6a
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/cases.js
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+const console = require("console");
+require("ehr/triggers").initScript(this);
+
+var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
+
+function onInit(event, helper) {
+ helper.decodeExtraContextProperty('ordersInTransaction');
+}
+
+EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_UPSERT, 'study', 'cases', function(helper, errors, row, oldRow){
+ if (!helper.isETL()) {
+ var error = false;
+ if (row.enddate) {
+ if (!row.closeRemark) {
+ EHR.Server.Utils.addError(errors, 'closeRemark', 'Close remark required when closing a case.', 'ERROR');
+ error = true;
+ }
+
+ if(!triggerHelper.canCloseCase()) {
+ EHR.Server.Utils.addError(errors, 'enddate', 'Veterinarian permission required to close a case.', 'ERROR');
+ error = true;
+ }
+ }
+
+ if (!row.performedby) {
+ EHR.Server.Utils.addError(errors, 'performedby', 'Opened By is a required field.', 'ERROR');
+ error = true;
+ }
+
+ if (!helper.isValidateOnly() && row.caseid && row.enddate && oldRow && (row.enddate != oldRow.enddate)) {
+ triggerHelper.closeDailyClinicalObs(row.caseid, row.enddate);
+ }
+
+ if (!helper.isValidateOnly() && !error) {
+ var qc;
+ if (row.QCStateLabel) {
+ qc = EHR.Server.Security.getQCStateByLabel(row.QCStateLabel);
+ }
+ else if (row.QCState) {
+ qc = EHR.Server.Security.getQCStateByRowId(row.QCState);
+ }
+
+ if (!qc) {
+ console.error('Unable to find QCState: ' + row.QCState + '/' + row.QCStateLabel);
+ }
+ else {
+
+ // Don't allow taking an existing non-draft case back to draft
+ if (oldRow && qc.Label == 'In Progress') {
+ var oldQc = EHR.Server.Security.getQCStateByLabel(oldRow.QCStateLabel);
+ if (oldQc.Label != 'In Progress') {
+ EHR.Server.Utils.addError(errors, null, 'Cannot save a draft copy of a case already opened or in review.', 'ERROR');
+ }
+ } else if (row.category == 'Clinical' && (qc.Label == 'Completed' || qc.Label == 'Review Required') && row.caseid && row.Id && row.performedby && row.taskid && !row.enddate) {
+ var ordersInTransaction = helper.getProperty('ordersInTransaction');
+ var oit = [];
+ if (ordersInTransaction && ordersInTransaction.length) {
+ oit = ordersInTransaction;
+ }
+ triggerHelper.ensureDailyClinicalObservationOrders(row.Id, row.caseid, row.date, row.performedby, qc.RowId, row.taskid, oit);
+ }
+ }
+ }
+ }
+});
diff --git a/nbri_ehr/resources/queries/study/cases.query.xml b/nbri_ehr/resources/queries/study/cases.query.xml
new file mode 100644
index 0000000..b2b7762
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/cases.query.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Open Date
+
+
+ Close Date
+ false
+
+
+ Problem Area
+
+ ehr_lookups
+ problem_list_category
+ value
+
+
+
+ Problem Subcategory
+
+ ehr_lookups
+ problem_list_subcategory
+ value
+
+
+
+ textarea
+ 5
+
+
+ Case Plan
+ textarea
+ 5
+
+
+ textarea
+ 5
+
+
+ Opened By
+
+
+ Category
+ true
+ false
+
+ ehr_lookups
+ data_category
+ value
+
+
+
+ true
+
+
+ false
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/cases/.qview.xml b/nbri_ehr/resources/queries/study/cases/.qview.xml
new file mode 100644
index 0000000..340d770
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/cases/.qview.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000..8333562
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/cases/Active Behavior Cases.qview.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000..6081418
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/cases/Active Clinical Cases.qview.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000..261b1bb
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/cases/All Behavior Cases.qview.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000..73fb0b2
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/cases/All Clinical Cases.qview.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/chemistryResults.query.xml b/nbri_ehr/resources/queries/study/chemistryResults.query.xml
new file mode 100644
index 0000000..caa0e9e
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/chemistryResults.query.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Date
+
+
+ Type
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/chemistryResults/.qview.xml b/nbri_ehr/resources/queries/study/chemistryResults/.qview.xml
new file mode 100644
index 0000000..02467cb
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/chemistryResults/.qview.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinicalCases.sql b/nbri_ehr/resources/queries/study/clinicalCases.sql
new file mode 100644
index 0000000..5dbc64b
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinicalCases.sql
@@ -0,0 +1,7 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT * FROM study.cases
+WHERE category = 'Clinical'
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinicalCases/.qview.xml b/nbri_ehr/resources/queries/study/clinicalCases/.qview.xml
new file mode 100644
index 0000000..b08797c
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinicalCases/.qview.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinicalObservations.sql b/nbri_ehr/resources/queries/study/clinicalObservations.sql
new file mode 100644
index 0000000..4de105c
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinicalObservations.sql
@@ -0,0 +1,7 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT * FROM study.clinical_observations
+WHERE type = 'Clinical'
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinicalObservations/.qview.xml b/nbri_ehr/resources/queries/study/clinicalObservations/.qview.xml
new file mode 100644
index 0000000..7b1fc1a
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinicalObservations/.qview.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinical_observations.js b/nbri_ehr/resources/queries/study/clinical_observations.js
new file mode 100644
index 0000000..9e54948
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinical_observations.js
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+const console = require("console");
+require("ehr/triggers").initScript(this);
+
+var animalIdCasesMap = {};
+var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
+
+function onInit(event, helper) {
+ helper.decodeExtraContextProperty('orderTasksInTransaction');
+ triggerHelper.clearScheduledObsTaskMap();
+}
+
+function onUpsert(helper, scriptErrors, row, oldRow) {
+
+ if (!helper.isETL() && !helper.isGeneratedByServer()) {
+
+ if (!row.observation && !row.remark) {
+ EHR.Server.Utils.addError(scriptErrors, 'observation', 'Must enter an observation or remark', 'WARN');
+ EHR.Server.Utils.addError(scriptErrors, 'remark', 'Must enter an observation or remark', 'WARN');
+ }
+
+ if (row.category === "Verified Id?" && row.observation === "No" && !row.remark) {
+ EHR.Server.Utils.addError(scriptErrors, 'remark', "You selected 'No' for 'Verified Id?', please enter Remark", "WARN");
+ }
+
+ if (row.category === "Daily Enrichment" && row.observation === "Other" && !row.remark) {
+ EHR.Server.Utils.addError(scriptErrors, 'remark', "You selected 'Other' for 'Daily Enrichment', please enter Remark", "WARN");
+ }
+
+ if (row.category === "Alopecia Score" && !row.observation) {
+ EHR.Server.Utils.addError(scriptErrors, 'observation', "Score required for 'Alopecia Score'.", "WARN");
+ }
+
+ if (row.category === "Alopecia Score" && !row.remark) {
+ EHR.Server.Utils.addError(scriptErrors, 'remark', "Remark required for 'Alopecia Score'.", "WARN");
+ }
+
+ var yesRemarkRequired = (row.category === "Self Biting Observed" || row.category === "New Injury Observed" || row.category === "Other Stereotopy" || row.category === "Environmental Change" || row.category === "Special Enrichment");
+ if (yesRemarkRequired && row.observation === "Yes" && !row.remark) {
+ EHR.Server.Utils.addError(scriptErrors, 'remark', "You selected 'Yes' for " + row.category + ", please explain in the Remark", "WARN");
+ }
+
+ // Handle scheduled observations
+ if (!helper.isValidateOnly() && row.scheduledDate) {
+ var qc;
+ if (row.QCStateLabel) {
+ qc = EHR.Server.Security.getQCStateByLabel(row.QCStateLabel);
+ }
+ else if (row.QCState) {
+ qc = EHR.Server.Security.getQCStateByRowId(row.QCState);
+ }
+
+ if (!qc) {
+ console.error('Unable to find QCState: ' + row.QCState + '/' + row.QCStateLabel);
+ }
+ else {
+ var orderTasks = helper.getProperty('orderTasksInTransaction');
+ if (orderTasks && orderTasks.length > 0) {
+ var orderData = triggerHelper.handleScheduledObservations(row, qc.RowId, orderTasks[0]);
+
+ if (orderData) {
+ row.caseId = orderData.caseId;
+ row.orderid = orderData.orderId;
+ row.area = orderData.area;
+ row.type = orderData.type;
+ row.taskid = orderData.taskId;
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinical_observations.query.xml b/nbri_ehr/resources/queries/study/clinical_observations.query.xml
new file mode 100644
index 0000000..7b058cf
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinical_observations.query.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Category
+
+
+ Area
+
+ ehr_lookups
+ observation_areas
+ value
+
+
+
+ Observation/Score
+
+
+ Remark
+
+
+ Case
+
+
+ Performed By
+
+
+ Scheduled Date
+
+
+ Type
+ true
+
+
+ Order Id
+ true
+
+ study
+ observation_order
+ objectid
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinical_observations/.qview.xml b/nbri_ehr/resources/queries/study/clinical_observations/.qview.xml
new file mode 100644
index 0000000..daaa82d
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinical_observations/.qview.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000..0a05b82
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinical_observations/Alopecia Scores.qview.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinical_observations/Behavior.qview.xml b/nbri_ehr/resources/queries/study/clinical_observations/Behavior.qview.xml
new file mode 100644
index 0000000..f7566f3
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinical_observations/Behavior.qview.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinical_observations/Clinical.qview.xml b/nbri_ehr/resources/queries/study/clinical_observations/Clinical.qview.xml
new file mode 100644
index 0000000..adf8cf8
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinical_observations/Clinical.qview.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinical_observationsSummary.sql b/nbri_ehr/resources/queries/study/clinical_observationsSummary.sql
new file mode 100644
index 0000000..40fca9d
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinical_observationsSummary.sql
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+ Id,
+ date,
+ category,
+ area,
+ observation,
+ remark,
+ performedBy,
+ scheduledDate,
+ QCState,
+ type
+FROM clinical_observations
+WHERE type = 'Clinical'
+GROUP BY
+ Id,
+ date,
+ category,
+ area,
+ observation,
+ remark,
+ performedBy,
+ scheduledDate,
+ QCState,
+ type
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinical_observationsSummary/.qview.xml b/nbri_ehr/resources/queries/study/clinical_observationsSummary/.qview.xml
new file mode 100644
index 0000000..a48d5a5
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinical_observationsSummary/.qview.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinremarks.js b/nbri_ehr/resources/queries/study/clinremarks.js
new file mode 100644
index 0000000..6a4955a
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinremarks.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+
+const console = require("console");
+require("ehr/triggers").initScript(this);
+
+function onInit(event, helper) {
+ helper.decodeExtraContextProperty('casesInTransaction');
+}
+
+function onUpsert(helper, scriptErrors, row, oldRow) {
+ if (!helper.isETL()) {
+
+ var caseMap = helper.getProperty('casesInTransaction');
+ var remarkCase = [];
+ if (caseMap && caseMap[row.Id]){
+ remarkCase = caseMap[row.Id];
+ }
+
+ var hasCaseEnddate = remarkCase.length > 0 && remarkCase[0].enddate
+
+ if (!row.s && !row.o && !row.a && !row.p && !row.remark && !hasCaseEnddate) {
+ EHR.Server.Utils.addError(scriptErrors, 'remark', 'Must enter at least one comment', 'WARN');
+ EHR.Server.Utils.addError(scriptErrors, 's', 'Must enter at least one comment', 'WARN');
+ }
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinremarks.query.xml b/nbri_ehr/resources/queries/study/clinremarks.query.xml
new file mode 100644
index 0000000..a221d11
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinremarks.query.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinremarks/.qview.xml b/nbri_ehr/resources/queries/study/clinremarks/.qview.xml
new file mode 100644
index 0000000..903f88d
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinremarks/.qview.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinremarks/Behavior.qview.xml b/nbri_ehr/resources/queries/study/clinremarks/Behavior.qview.xml
new file mode 100644
index 0000000..24f5a40
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinremarks/Behavior.qview.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/clinremarks/Clinical.qview.xml b/nbri_ehr/resources/queries/study/clinremarks/Clinical.qview.xml
new file mode 100644
index 0000000..6cb3771
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/clinremarks/Clinical.qview.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/colonyPopulationByAgeClass.query.xml b/nbri_ehr/resources/queries/study/colonyPopulationByAgeClass.query.xml
new file mode 100644
index 0000000..27edcf5
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/colonyPopulationByAgeClass.query.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ Population Summary, By Age and Species
+
+
+ Species
+
+ ehr_lookups
+ species_codes
+ code
+ common_name
+
+
+
+ Animal Count
+ /query/executeQuery.view?schemaName=study&
+ query.queryName=demographics&
+ query.species~eq=${species}&
+ query.gender~eq=${gender}&
+ query.Id/ageClass/label~eq=${ageclass}&
+ query.viewName=Alive%2C at Center&
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/colonyPopulationChange.sql b/nbri_ehr/resources/queries/study/colonyPopulationChange.sql
new file mode 100644
index 0000000..82f9c62
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/colonyPopulationChange.sql
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+PARAMETERS(StartDate TIMESTAMP, EndDate TIMESTAMP)
+
+SELECT
+ T1.id,
+-- T1.id.dataset.demographics.species as species,
+ 'Births' AS Category,
+ T1.date,
+ convert(year(T1.date), integer) AS Year
+
+FROM study.Birth T1
+WHERE T1.date IS NOT NULL
+and cast(COALESCE(STARTDATE, '1900-01-01') as date) <= T1.date and cast(COALESCE(ENDDATE, curdate()) as date) >= cast(T1.date as date)
+
+UNION ALL
+
+SELECT
+ T2.id,
+-- T2.id.dataset.demographics.species,
+ 'Arrivals' AS Category,
+ T2.date,
+ convert(year(T2.date), INTEGER) AS Year
+
+FROM study.Arrival T2
+WHERE T2.date IS NOT NULL
+AND T2.qcstate.publicdata = true
+and cast(COALESCE(STARTDATE, '1900-01-01') as date) <= T2.date and cast(COALESCE(ENDDATE, curdate()) as date) >= cast(T2.date as date)
+
+UNION ALL
+
+SELECT
+ T3.id,
+-- T3.id.dataset.demographics.species,
+ 'Departures' AS Category,
+ T3.date,
+ convert(year(T3.date), INTEGER) AS Year
+
+FROM study.Departure T3
+WHERE T3.date IS NOT NULL
+AND T3.qcstate.publicdata = true
+and cast(COALESCE(STARTDATE, '1900-01-01') as date) <= T3.date and cast(COALESCE(ENDDATE, curdate()) as date) >= cast(T3.date as date)
+
+UNION ALL
+
+SELECT
+ T4.id,
+-- T4.id.dataset.demographics.species,
+ 'Deaths' AS Category,
+ T4.date,
+ convert(year(T4.date), INTEGER) AS Year
+
+FROM study.Deaths T4
+WHERE T4.date IS NOT NULL
+-- and (t4.notAtCenter is null or t4.notAtCenter = false)
+and cast(COALESCE(STARTDATE, '1900-01-01') as date) <= T4.date and cast(COALESCE(ENDDATE, curdate()) as date) >= cast(T4.date as date)
diff --git a/nbri_ehr/resources/queries/study/colonyPopulationSummary.query.xml b/nbri_ehr/resources/queries/study/colonyPopulationSummary.query.xml
new file mode 100644
index 0000000..1005e5d
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/colonyPopulationSummary.query.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ Population Summary, By Species
+
+
+ Species
+
+ ehr_lookups
+ species_codes
+ code
+ common_name
+
+
+
+ Animal Count
+ /query/executeQuery.view?schemaName=study&
+ query.queryName=demographics&
+ query.species~eq=${species}&
+ query.viewName=Alive%2C at Center
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/currentBloodDraws.sql b/nbri_ehr/resources/queries/study/currentBloodDraws.sql
new file mode 100644
index 0000000..0e2c056
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/currentBloodDraws.sql
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+SELECT
+ t.id,
+ t.date,
+ cast(t.quantity as double) as quantity,
+ t.species,
+ t.max_draw_pct,
+ t.blood_draw_interval,
+ t.blood_per_kg,
+ t.mostRecentWeight,
+ t.mostRecentWeightDate,
+ t.death,
+ cast(t.allowableBlood as double) as maxAllowableBlood,
+ cast(t.bloodPrevious as double) as bloodPrevious,
+ cast((t.allowableBlood - t.bloodPrevious) as double) as allowablePrevious,
+
+ cast(t.bloodFuture as double) as bloodFuture,
+ cast((t.allowableBlood - t.bloodFuture) as double) as allowableFuture,
+
+ --if the draw is historic, always consider previous draws only.
+ --otherwise, look both forward and backwards, then take the interval with the highest volume
+ cast(case
+ WHEN t.date < curdate() THEN (t.allowableBlood - t.bloodPrevious)
+ WHEN t.bloodPrevious < t.bloodFuture THEN (t.allowableBlood - t.bloodFuture)
+ ELSE (t.allowableBlood - t.bloodPrevious)
+ end as double) as allowableBlood,
+ t.minDate,
+ t.maxDate
+
+FROM (
+
+ SELECT
+ bd.id,
+ bd.dateOnly as date,
+ bd.quantity,
+ d.species,
+ d.death,
+ d.id.mostRecentWeight.MostRecentWeight,
+ d.id.mostRecentWeight.MostRecentWeightDate,
+ d.species.scientific_name.blood_per_kg,
+ d.species.scientific_name.max_draw_pct,
+ bd.blood_draw_interval,
+ (d.id.mostRecentWeight.MostRecentWeight * d.species.scientific_name.blood_per_kg * d.species.scientific_name.max_draw_pct) AS allowableBlood,
+ bd.minDate,
+ bd.maxDate,
+ COALESCE(
+ (SELECT SUM(coalesce(draws.quantity, 0)) AS _expr
+ FROM study.blood draws
+ WHERE draws.id = bd.id
+ AND draws.dateOnly > bd.minDate
+ AND draws.dateOnly <= bd.dateOnly
+ --NOTE: this has been changed to include pending/non-approved draws
+ AND draws.countsAgainstVolume = true
+ ), 0) AS BloodPrevious,
+
+ COALESCE(
+ (SELECT SUM(coalesce(draws.quantity, 0)) AS _expr
+ FROM study.blood draws
+ WHERE draws.id = bd.id
+ AND draws.dateOnly < bd.maxDate
+ AND draws.dateOnly >= bd.dateOnly
+ --NOTE: this has been changed to include pending/non-approved draws
+ AND draws.countsAgainstVolume = true
+ ), 0) AS BloodFuture
+
+ FROM study.bloodDrawChanges bd
+ JOIN study.demographics d ON (d.id = bd.id)
+
+ ) t
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/currentproject.sql b/nbri_ehr/resources/queries/study/currentproject.sql
new file mode 100644
index 0000000..3ab2cf2
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/currentproject.sql
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+-- This query is used to find the current project
+
+SELECT DISTINCT assignment.Id,
+assignment.project
+FROM assignment
+WHERE assignment.enddate is null
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/deathNotification.sql b/nbri_ehr/resources/queries/study/deathNotification.sql
new file mode 100644
index 0000000..86f8800
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/deathNotification.sql
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+SELECT
+ Id,
+ date,
+ taskid,
+ performedBy.DisplayName AS performedBy,
+ reason.title AS reason
+FROM study.deaths
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/deaths.js b/nbri_ehr/resources/queries/study/deaths.js
new file mode 100644
index 0000000..79883b4
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/deaths.js
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+require("ehr/triggers").initScript(this);
+
+var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
+var validIds = [];
+var idMap = {};
+var deathIdMap = {};
+
+function onInit(event, helper){
+
+ helper.decodeExtraContextProperty('deathsInTransaction');
+
+ // Cache valid Ids for check on each row
+ LABKEY.Query.selectRows({
+ requiredVersion: 9.1,
+ schemaName: 'study',
+ queryName: 'demographics',
+ columns: ['Id', 'calculated_status', 'QCState/Label'],
+ scope: this,
+ success: function (results) {
+ if (!results || !results.rows || results.rows.length < 1)
+ return;
+
+ for(var i=0; i < results.rows.length; i++) {
+ validIds.push(results.rows[i]["Id"]["value"])
+ idMap[results.rows[i]["Id"]["value"]] = {calculated_status: results.rows[i]["calculated_status"]["value"], QCStateLabel: results.rows[i]["QCState/Label"]["value"]};
+ // console.log(idMap[results.rows[i]["Id"]["value"]]);
+ }
+ },
+ failure: function (error) {
+ console.log("error getting demographics data in death trigger onInit()\n" + error);
+ }
+ });
+
+ LABKEY.Query.selectRows({
+ requiredVersion: 9.1,
+ schemaName: 'study',
+ queryName: 'deaths',
+ columns: ['Id', 'QCState/Label'],
+ scope: this,
+ success: function (results) {
+ if (!results || !results.rows || results.rows.length < 1)
+ return;
+
+ for(var i=0; i < results.rows.length; i++) {
+ deathIdMap[results.rows[i]["Id"]["value"]] = {QCStateLabel: results.rows[i]["QCState/Label"]["value"]};
+ }
+ },
+ failure: function (error) {
+ console.log("error getting death data in death trigger onInit()\n" + error);
+ }
+ });
+}
+
+EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.AFTER_DELETE, 'study', 'Deaths', function(helper, errors, row, oldRow) {
+
+ var demographicsUpdates = [];
+ demographicsUpdates.push({
+ Id: row.Id,
+ death: null,
+ calculated_status: 'Alive',
+ QCState: helper.getJavaHelper().getQCStateForLabel('Completed').getRowId(),
+ });
+
+ console.log('removing demographics death date for animal:' + row.Id);
+ helper.getJavaHelper().updateDemographicsRecord(demographicsUpdates);
+});
+
+function onUpsert(helper, scriptErrors, row, oldRow) {
+
+ var demographicsUpdates = [];
+
+ if (!helper.isETL()) {
+
+ //skip other checks so that the admins can update a death record
+ if (helper.getEvent() === 'update' && LABKEY.Security.currentUser.isAdmin) {
+ return;
+ }
+
+ //only allow death record to be created if the animal is in the demographics table
+ if (idMap[row.Id]) {
+
+ // check if a death record already exists for this animal
+ if (idMap[row.Id].calculated_status.toUpperCase() === 'DEAD' && deathIdMap[row.Id].QCStateLabel.toUpperCase() === 'COMPLETED') {
+ EHR.Server.Utils.addError(scriptErrors, 'Id', 'Death record already exists for this animal.', 'ERROR');
+ }
+ // check if the animal is at the center
+ else if (idMap[row.Id].calculated_status.toUpperCase() === 'SHIPPED') {
+ EHR.Server.Utils.addError(scriptErrors, 'Id', 'Animal is not at the center.', 'ERROR');
+ }
+ // Check if an animal that's being entered is pending any request/review.
+ // Note 1: When trying to enter a new record for an animal, the QCState = 'IN PROGRESS'.
+ // Note 2: Upon 'Submit Death', the QCState will get set to 'REQUEST: PENDING', and upon 'Submit Necropsy for Review',
+ // the QCState will get set to 'Review Required' - this way we can distinguish between the two states in the Death/Necropsy workflow.
+ // If a user tries to submit a new Death record (identified by QCState = 'IN PROGRESS') for an animal that
+ // already has a pending request/review status in study.deaths, then below error message will be displayed.
+ else if (row.QCStateLabel.toUpperCase() === 'IN PROGRESS' &&
+ deathIdMap[row.Id] && deathIdMap[row.Id].QCStateLabel &&
+ (deathIdMap[row.Id].QCStateLabel.toUpperCase() === 'REQUEST: PENDING' ||
+ deathIdMap[row.Id].QCStateLabel.toUpperCase() === 'REVIEW REQUIRED')) {
+ EHR.Server.Utils.addError(scriptErrors, 'Id', 'Death record is pending review for this animal', 'ERROR');
+ }
+ // if 'Save Draft' record already exists, it doesn't allow to 'Save Draft' or 'Submit Death'
+ // on the same animal again - throws an error "duplicate key value violates unique constraint"
+ // So, added this check to allow 'Save Draft' record to be saved only once.
+ else if (oldRow === undefined && row.QCStateLabel.toUpperCase() === 'IN PROGRESS' &&
+ deathIdMap[row.Id] && deathIdMap[row.Id].QCStateLabel &&
+ deathIdMap[row.Id].QCStateLabel.toUpperCase() === 'IN PROGRESS') {
+ EHR.Server.Utils.addError(scriptErrors, 'Id', 'Death/Necropsy data entry is in progress for this animal', 'ERROR');
+ }
+ else if (!helper.isValidateOnly() && row.Id && row.date && row.QCStateLabel.toUpperCase() === 'COMPLETED') {
+
+ if (validIds.indexOf(row.id) !== -1) {
+
+ // update demographics
+ demographicsUpdates.push({
+ Id: row.Id,
+ death: row.date,
+ calculated_status: 'Dead',
+ QCState: helper.getJavaHelper().getQCStateForLabel(row.QCStateLabel).getRowId()
+ });
+
+ console.log('updating demographics death date for animal: ' + row.Id);
+ helper.getJavaHelper().updateDemographicsRecord(demographicsUpdates);
+ console.log('updated demographics death date for animal: ' + row.Id);
+ }
+ else {
+ console.log(row.id + " is not a valid animal id");
+ }
+ }
+
+ if(row.QCStateLabel && EHR.Server.Security.getQCStateByLabel(row.QCStateLabel).PublicData) {
+ var qcstate = helper.getJavaHelper().getQCStateForLabel(row.QCStateLabel).getRowId();
+
+ //add/update weight record
+ var weightRecord = {
+ Id: row.Id,
+ date: row.date,
+ weight: row.deathWeight,
+ taskid: row.taskid,
+ qcstate: qcstate,
+ performedby: row.performedby
+ };
+ triggerHelper.upsertWeightRecord(weightRecord);
+ }
+ }
+ }
+}
+
+EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.AFTER_INSERT, 'study', 'deaths', function(helper, scriptErrors, row, oldRow) {
+ helper.registerDeath(row.Id, row.date);
+ triggerHelper.reportDataChange("study", "deaths", [row.Id]);
+});
+
+EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.COMPLETE, 'study', 'Deaths', function(event, errors, helper){
+ var rows = helper.getRows() || [];
+ for (var i = 0; i < rows.length; i++) {
+ var row = rows[i].row;
+ var oldRow = rows[i].oldRow;
+
+ // Notification will get sent when:
+ // 1) a brand-new row saved directly as 'Request: Pending' (i.e., when a user clicks 'Submit Death'), or
+ // 2) a draft death record moving from 'In Progress' to 'Request: Pending'.
+ if (!helper.isETL() &&
+ row && row.Id &&
+ row.QCStateLabel &&
+ row.QCStateLabel.toUpperCase() === 'REQUEST: PENDING' &&
+ (!oldRow || !oldRow.QCStateLabel || oldRow.QCStateLabel.toUpperCase() === 'IN PROGRESS')) {
+ console.log("Sending NBRI Death Notification")
+ triggerHelper.sendDeathNotification(row.Id);
+
+ console.log("Updating Procedure Orders to Completed for Animal: " + row.Id + "")
+ triggerHelper.updateProcedureOrdersToCompleted([row.Id]);
+ }
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/deaths.query.xml b/nbri_ehr/resources/queries/study/deaths.query.xml
new file mode 100644
index 0000000..35c2e6a
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/deaths.query.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+ Death Date
+
+
+ Disposition
+
+ ehr_lookups
+ death_reason
+ value
+ title
+
+
+
+ Death Weight (kg)
+
+
+
+
+ true
+
+
+ true
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/deaths/.qview.xml b/nbri_ehr/resources/queries/study/deaths/.qview.xml
new file mode 100644
index 0000000..fdc063c
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/deaths/.qview.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographics.js b/nbri_ehr/resources/queries/study/demographics.js
new file mode 100644
index 0000000..ea36be3
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographics.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+let animalIds = [];
+
+function onInit(event, helper){
+ helper.setScriptOptions({
+ allowAnyId: true,
+ requiresStatusRecalc: false,
+ allowDatesInDistantPast: true
+ });
+}
+
+function onUpsert(helper, scriptErrors, row, oldRow){
+ //NOTE: this should be getting set by the birth, death, arrival & departure tables
+ //ALSO: it should be rare to insert directly into this table. usually this record will be created by inserting into either birth or arrival
+ if (!row.calculated_status && !helper.isETL()){
+ row.calculated_status = helper.getJavaHelper().getCalculatedStatusValue(row.Id);
+ }
+
+ if (helper.isETL() && row.damSire) {
+ let damSire = row.damSire.split('DAM:');
+ if (damSire.length === 1) {
+ row.damSire.split('DAM');
+ }
+
+ if (damSire.length > 1) {
+ let damAndSire = damSire[1].split("SIRE:")
+ if (damAndSire.length === 1) {
+ row.damSire.split('SIRE');
+ }
+ if (damAndSire[0].trim() === row.Id)
+ {
+ console.log(row.Id + " listed as it's own dam")
+ }
+ if (damAndSire[0].trim() !== 'UNK' && damAndSire[0].trim() !== row.Id)
+ row.dam = damAndSire[0].trim();
+
+ if (damAndSire.length > 1 && damAndSire[1].trim() === row.Id)
+ {
+ console.log(row.Id + " listed as it's own sire")
+ }
+ if (damAndSire.length > 1 && damAndSire[1].trim() !== 'UNK' && damAndSire[1].trim() !== row.Id)
+ row.sire = damAndSire[1].trim();
+
+ if (row.sire === row.dam) {
+ row.sire = '';
+ row.dam = '';
+ }
+ }
+ }
+}
diff --git a/nbri_ehr/resources/queries/study/demographics.query.xml b/nbri_ehr/resources/queries/study/demographics.query.xml
new file mode 100644
index 0000000..bff3d17
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographics.query.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+ true
+
+
+ Sex
+
+
+ Species
+
+
+ Date
+ Birth
+ /query/executeQuery.view?
+ schemaName=study&
+ query.queryName=Birth&
+ query.Id~eq=${Id}
+
+
+
+ Date
+ Death
+ /query/executeQuery.view?
+ schemaName=study&
+ query.queryName=Deaths&
+ query.Id~eq=${Id}
+
+
+
+ Dam
+
+ study
+ animal
+ id
+
+ /ehr/participantView.view?participantId=${dam}
+
+
+ Sire
+
+ study
+ animal
+ id
+
+ /ehr/participantView.view?participantId=${sire}
+
+
+ Disposition
+
+ ehr_lookups
+ disposition_codes
+ value
+ title
+
+
+
+ Termination
+
+ ehr_lookups
+ termination_reason_codes
+ value
+ title
+
+
+
+ Date Weaned
+ Date
+
+
+ Implant Number
+
+
+ Vendor Number
+
+
+ Active Status
+
+
+ Animal Status
+
+
+ Lot
+
+ nbri_ehr
+ Lot
+ LotId
+
+
+
+ Origin
+
+ ehr_lookups
+ geographic_origins
+ meaning
+ meaning
+
+
+
+ Source
+
+
+ CITES
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographics/.qview.xml b/nbri_ehr/resources/queries/study/demographics/.qview.xml
new file mode 100644
index 0000000..56f1f23
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographics/.qview.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsActiveFlags.sql b/nbri_ehr/resources/queries/study/demographicsActiveFlags.sql
new file mode 100644
index 0000000..f61af93
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsActiveFlags.sql
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+ f1.Id,
+ group_concat(DISTINCT f1.flag, chr(10)) as flags
+
+FROM (
+ SELECT
+ f.Id,
+ CASE
+ WHEN f.flag is null THEN null
+ WHEN f.flag.category IS NULL THEN f.flag.value
+ ELSE cast((CAST(f.flag.category as varchar(100)) || CAST(': ' as varchar(2)) || CAST(f.flag.value as varchar(100))) as varchar(202))
+ END as flag
+
+ FROM study.flags f
+ WHERE f.isActive = true
+
+ ) f1
+
+GROUP BY f1.Id
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsActiveProjectAssignment.sql b/nbri_ehr/resources/queries/study/demographicsActiveProjectAssignment.sql
new file mode 100644
index 0000000..8ccdbc8
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsActiveProjectAssignment.sql
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+ d.id,
+ a.project.name as project
+FROM study.demographics d
+LEFT JOIN study.assignment a ON (a.id = d.id AND a.enddate IS NULL)
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsActiveProtocolAssignment.query.xml b/nbri_ehr/resources/queries/study/demographicsActiveProtocolAssignment.query.xml
new file mode 100644
index 0000000..e7d003f
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsActiveProtocolAssignment.query.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographicsActiveProtocolAssignment.sql b/nbri_ehr/resources/queries/study/demographicsActiveProtocolAssignment.sql
new file mode 100644
index 0000000..ddc956d
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsActiveProtocolAssignment.sql
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+ d.id,
+ a.protocol.displayName as protocolDisplayName,
+ initcap(a.protocol.investigatorId.firstName) ||' '|| initcap(a.protocol.investigatorId.lastName) as investigator,
+ a.protocol.title as protocolTitle,
+ a.lsid,
+ cast(CASE
+ WHEN a.protocol.investigatorId.lastName IS NOT NULL THEN (a.protocol.investigatorId.lastName || ' - ' || a.protocol.displayName)
+ ELSE a.protocol.displayName
+ END as varchar(500)) as protocolString
+
+FROM study.demographics d
+LEFT JOIN study.protocolAssignment a ON (a.id = d.id AND a.enddate IS NULL)
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsAliases.query.xml b/nbri_ehr/resources/queries/study/demographicsAliases.query.xml
new file mode 100644
index 0000000..09b31f9
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsAliases.query.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ Animal Aliases
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsAliases.sql b/nbri_ehr/resources/queries/study/demographicsAliases.sql
new file mode 100644
index 0000000..f70f03f
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsAliases.sql
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT Id,
+ GROUP_CONCAT(alias, ', ') alias,
+ category.title as aliasType
+FROM alias
+GROUP BY Id, category.title
+ PIVOT alias BY aliasType IN (SELECT title FROM ehr_lookups.alias_category)
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsCagemates.query.xml b/nbri_ehr/resources/queries/study/demographicsCagemates.query.xml
new file mode 100644
index 0000000..c4877dd
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsCagemates.query.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ Animals Per Pairing
+
+
+ true
+
+
+ false
+
+
+ Total Animals In Pairing
+ /query/executeQuery.view?schemaName=study&query.queryName=Demographics&query.id/numPaired/room~eq=${room}&query.id/numPaired/effectiveCage~eq=${effectiveCage}
+
+
+ Category
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographicsCagemates.sql b/nbri_ehr/resources/queries/study/demographicsCagemates.sql
new file mode 100644
index 0000000..b3464b7
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsCagemates.sql
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+ d.id,
+-- t.room,
+ t.cage,
+ t.total,
+ cast(t.animals as varchar(4000)) as animals
+
+FROM study.demographics d
+LEFT JOIN (
+SELECT
+ h.id,
+-- h.room,
+ h.cage,
+ count(distinct h2.id) as total,
+ group_concat(distinct h2.id, ', ') as animals
+
+FROM study.housing h
+
+JOIN study.housing h2
+ON (h2.Id.demographics.calculated_status = 'Alive'
+ AND (h.cage = h2.cage))
+
+WHERE h.enddateTimeCoalesced >= now()
+GROUP BY h.id, h.room, h.cage
+
+) t ON (t.id = d.id)
+
+WHERE d.calculated_status = 'Alive'
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsCurLocation.query.xml b/nbri_ehr/resources/queries/study/demographicsCurLocation.query.xml
new file mode 100644
index 0000000..de68430
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsCurLocation.query.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographicsCurLocation.sql b/nbri_ehr/resources/queries/study/demographicsCurLocation.sql
new file mode 100644
index 0000000..7402989
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsCurLocation.sql
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+
+d2.id,
+
+d2.cage AS Location,
+
+d2.room.area,
+
+d2.room,
+
+d2.cage AS cage,
+
+ifdefined(d2.cond) as cond,
+
+d2.date,
+
+d2.reason,
+
+d2.remark,
+
+coalesce(d2.room, '') as room_order,
+d2.room_sortValue @hidden,
+
+coalesce(d2.cage, '') as cage_order,
+d2.cage_sortValue @hidden,
+
+d2.performedBy.displayName AS performedBy
+
+FROM study.housing d2
+
+WHERE d2.enddate IS NULL
+AND d2.qcstate.publicdata = true
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsLastProject.query.xml b/nbri_ehr/resources/queries/study/demographicsLastProject.query.xml
new file mode 100644
index 0000000..d01971e
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsLastProject.query.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Last Project
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographicsLastProject.sql b/nbri_ehr/resources/queries/study/demographicsLastProject.sql
new file mode 100644
index 0000000..16c6ca7
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsLastProject.sql
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+SELECT
+ d2.id,
+ d2.project,
+ h.enddate as enddate
+
+FROM study.assignment d2
+ JOIN (SELECT id, max(date) as maxDate, max(enddate) as enddate FROM study.assignment h GROUP BY id) h
+ ON (h.id = d2.id and d2.date = h.maxdate)
+WHERE d2.qcstate.publicdata = true
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsLastProtocol.query.xml b/nbri_ehr/resources/queries/study/demographicsLastProtocol.query.xml
new file mode 100644
index 0000000..94265b7
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsLastProtocol.query.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Last Protocol
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsLastProtocol.sql b/nbri_ehr/resources/queries/study/demographicsLastProtocol.sql
new file mode 100644
index 0000000..ace87a8
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsLastProtocol.sql
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+SELECT
+ d2.id,
+ d2.protocol,
+ h.enddate as enddate
+
+FROM study.protocolAssignment d2
+ JOIN (SELECT id, max(date) as maxDate, max(enddate) as enddate FROM study.protocolAssignment h GROUP BY id) h
+ ON (h.id = d2.id and d2.date = h.maxdate)
+WHERE d2.qcstate.publicdata = true
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/demographicsOffspring.query.xml b/nbri_ehr/resources/queries/study/demographicsOffspring.query.xml
new file mode 100644
index 0000000..877be40
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsOffspring.query.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ true
+
+
+
+ study
+ animal
+ Id
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographicsOffspring.sql b/nbri_ehr/resources/queries/study/demographicsOffspring.sql
new file mode 100644
index 0000000..9feda19
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsOffspring.sql
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+
+ d.id as Id,
+ d2.id AS Offspring,
+ d2.birth AS date,
+d2.sire,
+d2.dam,
+d2.gender AS sex,
+d.qcstate
+
+FROM study.Demographics d
+
+ INNER JOIN study.Demographics d2
+ON ((d2.sire = d.id OR d2.dam = d.id) AND d.id != d2.id)
+
+group by d.id, d2.id, d2.birth, d2.sire, d2.dam, d2.gender, d.qcstate
diff --git a/nbri_ehr/resources/queries/study/demographicsParents.query.xml b/nbri_ehr/resources/queries/study/demographicsParents.query.xml
new file mode 100644
index 0000000..a151cf1
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsParents.query.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ Parents
+
+
+ true
+
+
+
+ study
+ animal
+ id
+
+
+
+
+ study
+ animal
+ id
+
+
+
+ Number of Parents Known
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographicsParents.sql b/nbri_ehr/resources/queries/study/demographicsParents.sql
new file mode 100644
index 0000000..91b6d5c
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsParents.sql
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+SELECT
+ d.id,
+ d.dam as dam,
+ 'Observed' as damType,
+ d.sire as sire,
+ 'Observed' as sireType,
+ CASE
+ WHEN d.dam IS NOT NULL AND d.sire IS NOT NULL THEN 2
+ WHEN d.dam IS NOT NULL OR d.sire IS NOT NULL THEN 1
+ ELSE 0
+ END as numParents
+FROM study.demographics d
+
+-- TODO: Incorporate fostering? Genetic testing?
+
diff --git a/nbri_ehr/resources/queries/study/demographicsSiblings.query.xml b/nbri_ehr/resources/queries/study/demographicsSiblings.query.xml
new file mode 100644
index 0000000..a97b1d1
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsSiblings.query.xml
@@ -0,0 +1,44 @@
+
+ Siblings of Any Animal
+
+
+
+ Siblings of Any Animal
+
+
+ true
+
+
+
+ /query/executeQuery.view?schemaName=study&
+ query.queryName=demographicsSiblings&
+ query.Id~eq=${Id}
+
+
+
+
+ study
+ animal
+ Id
+
+
+
+
+ study
+ animal
+ Id
+
+
+
+
+ study
+ animal
+ Id
+
+
+
+ Relationship
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographicsSiblings.sql b/nbri_ehr/resources/queries/study/demographicsSiblings.sql
new file mode 100644
index 0000000..a774aed
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsSiblings.sql
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+
+ d1.id,
+
+ CASE
+ WHEN (COALESCE(d1.id.parents.sire, '') = COALESCE(d2.id.parents.sire, '') and COALESCE(d1.id.parents.dam, '') = COALESCE(d2.id.parents.dam, '') AND COALESCE(d1.id.parents.sire, '')!='' AND COALESCE(d1.id.parents.dam, '')!='')
+ THEN 'Full Sib'
+ WHEN (COALESCE(d1.id.parents.sire, '') = COALESCE(d2.id.parents.sire, '') AND COALESCE(d1.id.parents.sire, '') != '' AND (COALESCE(d1.id.parents.dam, '') != COALESCE(d2.id.parents.dam, '') OR COALESCE(d1.id.parents.dam, '') = ''))
+ THEN 'Half-Sib Paternal'
+ WHEN (COALESCE(d1.id.parents.dam, '') = COALESCE(d2.id.parents.dam, '') AND COALESCE(d1.id.parents.dam, '') != '' AND (COALESCE(d1.id.parents.sire, '') != COALESCE(d2.id.parents.sire, '') OR COALESCE(d1.id.parents.sire, '') = ''))
+ THEN 'Half-Sib Maternal'
+ WHEN (COALESCE(d1.id.parents.sire, '') != COALESCE(d2.id.parents.sire, '') and COALESCE(d1.id.parents.dam, '') != COALESCE(d2.id.parents.dam, ''))
+ THEN 'ERROR'
+ END AS Relationship,
+
+ d2.id AS Sibling,
+
+ d2.id.parents.dam AS SiblingDam,
+ d2.id.parents.sire AS SiblingSire,
+ d1.qcstate
+
+FROM study.Demographics d1
+
+--removed left join
+ JOIN study.Demographics d2
+ ON ((d2.id.parents.sire = d1.id.parents.sire OR d2.id.parents.dam = d1.id.parents.dam) AND d1.id != d2.id)
+
+WHERE d2.id is not null
+
+-- ) t
+
diff --git a/nbri_ehr/resources/queries/study/demographicsSource.query.xml b/nbri_ehr/resources/queries/study/demographicsSource.query.xml
new file mode 100644
index 0000000..98ae435
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsSource.query.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Source
+
+
+ Days Since Center Arrival
+
+
+ From Center?
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/demographicsSource.sql b/nbri_ehr/resources/queries/study/demographicsSource.sql
new file mode 100644
index 0000000..9de9dfd
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsSource.sql
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+SELECT
+ d.Id AS Id,
+
+ T1.mostRecentArrival,
+ T1.earliestArrival,
+ d.birth,
+ coalesce(T1.EarliestArrival, d.birth) as center_Arrival,
+ timestampdiff('SQL_TSI_DAY', coalesce(T1.EarliestArrival, d.birth), now()) as daysSinceCenterArrival,
+
+ CASE
+ WHEN T1.EarliestArrival IS NULL AND d.birth IS NOT NULL THEN true
+ ELSE false
+ END as fromCenter,
+ d.source as source,
+
+ CASE
+ WHEN T1.EarliestArrival IS NULL AND d.birth IS NOT NULL THEN 'Born at NBRI'
+ WHEN T1.EarliestArrival IS NOT NULL AND T2.sourceFacility IS NOT NULL THEN T2.sourceFacility.meaning
+ ELSE 'Unknown'
+ END as type
+
+FROM study.demographics d
+
+LEFT JOIN
+ (select T1.Id, max(T1.date) as MostRecentArrival, min(T1.date) as EarliestArrival FROM study.arrival T1 WHERE T1.qcstate.publicdata = true GROUP BY T1.Id) T1
+ ON (T1.Id = d.Id)
+
+LEFT JOIN study.arrival T2 ON (t2.id = d.id AND t2.date = t1.earliestArrival)
+
diff --git a/nbri_ehr/resources/queries/study/demographicsTotalOffspring.query.xml b/nbri_ehr/resources/queries/study/demographicsTotalOffspring.query.xml
new file mode 100644
index 0000000..d42f72b
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/demographicsTotalOffspring.query.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/notes/.qview.xml b/nbri_ehr/resources/queries/study/notes/.qview.xml
new file mode 100644
index 0000000..6cd9bab
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/notes/.qview.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/notificationAnimalProject.sql b/nbri_ehr/resources/queries/study/notificationAnimalProject.sql
new file mode 100644
index 0000000..8661a32
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/notificationAnimalProject.sql
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+-- Add more here to make a single query for animal details in notifications
+SELECT
+ Id,
+ date,
+ project.name AS project,
+ enddate
+FROM study.assignment
+WHERE enddate IS NULL
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/observationOrdersByDate.sql b/nbri_ehr/resources/queries/study/observationOrdersByDate.sql
new file mode 100644
index 0000000..2318e32
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/observationOrdersByDate.sql
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+SELECT
+s.*,
+co.qcstate.label AS obsStatus
+FROM
+ (SELECT
+ s1.*,
+ timestampadd('SQL_TSI_MINUTE', ((s1.hours * 60) + s1.minutes), s1.origDate) AS date,
+ ((s1.hours * 60) + s1.minutes) AS timeOffset
+
+ FROM (
+ SELECT
+ t1.lsid,
+ t1.dataset,
+ t1.id AS animalid,
+ coalesce(ft.hourofday, ((hour(t1.date) * 100) + minute(t1.date))) AS time,
+ (coalesce(ft.hourofday, (hour(t1.date) * 100)) / 100) AS hours,
+
+ CASE WHEN ft.hourofday IS NOT NULL
+ THEN (((ft.hourofday / 100.0) - floor(ft.hourofday / 100)) * 100)
+ ELSE minute(t1.date)
+ END AS minutes,
+
+ dr.date AS origDate,
+
+ t1.frequency.meaning AS frequency,
+ t1.date AS startDate,
+ timestampdiff('SQL_TSI_DAY', cast(t1.dateOnly AS timestamp), dr.dateOnly) + 1 AS daysElapsed,
+ t1.enddate,
+ t1.category,
+ t1.area,
+ t1.performedby,
+ t1.remark,
+ t1.caseid.objectid AS caseid,
+ t1.taskid,
+ t1.type,
+ t1.objectid,
+
+ t1.qcstate
+ FROM nbri_ehr.dateRange dr
+ JOIN
+
+ -- order by category to replace string with Daily Obs
+ (SELECT * FROM study.observation_order ORDER BY category) t1
+ ON (dr.dateOnly >= t1.dateOnly AND (dr.dateOnly <= t1.enddate OR t1.enddate IS NULL) AND
+ --technically the first day of the treatment is day 1, not day 0
+ ((mod(CAST(timestampdiff('SQL_TSI_DAY', CAST(t1.dateOnly AS timestamp), dr.dateOnly) AS integer), t1.frequency.intervalindays) = 0 AND t1.frequency.intervalindays IS NOT NULL AND t1.frequency.dayofweek IS NULL))
+ AND (t1.frequency.weekDays IS NULL OR LOCATE(CAST(dr.DayOfWeek AS VARCHAR), t1.frequency.weekDays) > 0)
+ )
+ LEFT JOIN ehr_lookups.treatment_frequency_times ft ON ft.frequency = t1.frequency.meaning
+ WHERE t1.date IS NOT NULL
+
+ ) s1
+) s
+LEFT JOIN study.clinical_observations co ON co.scheduledDate IS NOT NULL AND s.date = co.scheduledDate AND co.orderId = s.objectid
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/observationSchedule.query.xml b/nbri_ehr/resources/queries/study/observationSchedule.query.xml
new file mode 100644
index 0000000..4d03aac
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/observationSchedule.query.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/queries/study/parentageSummary.sql b/nbri_ehr/resources/queries/study/parentageSummary.sql
new file mode 100644
index 0000000..733a6ab
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/parentageSummary.sql
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+SELECT
+ d.Id,
+ d.date,
+ d.dam as parent,
+ 'Dam' as relationship,
+ 'Observed' as method
+
+FROM study.demographics d
+WHERE d.qcstate.publicdata = true
+
+UNION ALL
+
+SELECT
+ d.Id,
+ d.date,
+ d.sire as parent,
+ 'Sire' as relationship,
+ 'Observed' as method
+
+FROM study.demographics d
+WHERE d.qcstate.publicdata = true
diff --git a/nbri_ehr/resources/queries/study/physicalExam/.qview.xml b/nbri_ehr/resources/queries/study/physicalExam/.qview.xml
new file mode 100644
index 0000000..caf3fb0
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/physicalExam/.qview.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/prc.js b/nbri_ehr/resources/queries/study/prc.js
new file mode 100644
index 0000000..1cca5b5
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/prc.js
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+var triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
+
+EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_INSERT, 'study', 'prc', function(helper, scriptErrors, row, oldRow) {
+ if(row.orderid && triggerHelper.isProcedureOrderEntered(row.orderid)) {
+ EHR.Server.Utils.addError(scriptErrors, 'orderid', 'Procedure order has already been closed.', 'ERROR');
+ }
+});
+
+EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.COMPLETE, 'study', 'prc', function (event, errors, helper) {
+
+ if (!helper.isValidateOnly()){
+ var rows = helper.getRows();
+ var orderIds = [];
+ for (var i=0;i
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/treatmentSchedule.sql b/nbri_ehr/resources/queries/study/treatmentSchedule.sql
new file mode 100644
index 0000000..068cb4e
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/treatmentSchedule.sql
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+SELECT
+d.id,
+d_alias.alias AS Name,
+d.calculated_status,
+s.*,
+s.objectid AS treatmentid,
+drug.treatmentStatus,
+s.objectid || '-pkSeparator-' || s.date AS primaryKey -- -pkSeparator- is used to separate the two parts of the primary key in RecordTreatmentButton.js
+FROM study.demographics d
+JOIN(
+ SELECT
+ s1.*,
+ timestampadd('SQL_TSI_MINUTE', ((s1.hours * 60) + s1.minutes), s1.origDate) AS date,
+ ((s1.hours * 60) + s1.minutes) AS timeOffset
+ FROM (
+ SELECT
+ t1.lsid,
+ t1.objectid,
+ t1.dataset,
+ t1.id AS animalid,
+
+ COALESCE(ft.hourofday, ((hour(t1.date) * 100) + minute(t1.date))) AS time,
+ (COALESCE(ft.hourofday, (hour(t1.date) * 100)) / 100) AS hours,
+ CASE
+ WHEN (ft.hourofday IS NOT NULL) THEN (((ft.hourofday / 100.0) - floor(ft.hourofday / 100)) * 100)
+ ELSE minute(t1.date)
+ END AS minutes,
+ dr.date AS origDate,
+ t1.category,
+ t1.frequency.meaning AS frequency,
+ t1.date AS startDate,
+ timestampdiff('SQL_TSI_DAY', cast(t1.dateOnly AS timestamp), dr.dateOnly) + 1 AS daysElapsed,
+ t1.enddate,
+ t1.code,
+ t1.treatmentRecord,
+ t1.volume,
+ t1.vol_units,
+ t1.concentration,
+ t1.conc_units,
+ t1.amount,
+ t1.amount_units,
+ t1.amountWithUnits,
+ t1.amountAndVolume,
+ t1.dosage,
+ t1.dosage_units,
+ t1.route,
+ IFDEFINED(t1.reason) AS reason,
+ t1.performedby,
+ t1.remark,
+ t1.caseid,
+ t1.orderedby,
+ t1.qcstate
+ FROM nbri_ehr.dateRange dr
+ JOIN study."Treatment Orders" t1 ON (dr.dateOnly >= t1.dateOnly AND
+ --technically the first day of the treatment is day 1, not day 0
+ ((mod(CAST(timestampdiff('SQL_TSI_DAY', CAST(t1.dateOnly AS timestamp), dr.dateOnly) AS integer), t1.frequency.intervalindays) = 0 AND t1.frequency.intervalindays IS NOT NULL AND t1.frequency.dayofweek IS NULL ))
+ AND (t1.frequency.weekDays IS NULL OR LOCATE(CAST(dr.DayOfWeek AS VARCHAR), t1.frequency.weekDays) > 0)
+ )
+ LEFT JOIN ehr_lookups.treatment_frequency_times ft ON ft.frequency = t1.frequency.meaning
+ --NOTE: if we run this report on a future interval, we want to include those treatments
+ WHERE t1.date IS NOT NULL
+
+ ) s1
+
+) s ON (s.animalid = d.id)
+LEFT JOIN (
+ SELECT treatmentid, IFDEFINED(scheduledDate) AS scheduledDate, MAX(qcstate.label) AS treatmentStatus
+ FROM study.drug
+ GROUP BY treatmentid, IFDEFINED(scheduledDate)
+) drug ON s.objectid = drug.treatmentid AND s.date = drug.scheduledDate
+LEFT JOIN (SELECT Id, GROUP_CONCAT(alias, ', ') alias FROM alias WHERE category.title = 'Name' GROUP BY Id) d_alias ON d.id = d_alias.id
+WHERE (d.lastDayatCenter IS NULL OR d.lastDayAtCenter > s.enddate OR s.enddate IS NULL)
+ AND s.date >= s.startDate AND (s.enddate IS NULL OR s.date <= s.enddate)
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/treatmentSchedule/.qview.xml b/nbri_ehr/resources/queries/study/treatmentSchedule/.qview.xml
new file mode 100644
index 0000000..4c57f0b
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/treatmentSchedule/.qview.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/treatmentSchedule/Clinical Medications.qview.xml b/nbri_ehr/resources/queries/study/treatmentSchedule/Clinical Medications.qview.xml
new file mode 100644
index 0000000..5698039
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/treatmentSchedule/Clinical Medications.qview.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/treatment_order.js b/nbri_ehr/resources/queries/study/treatment_order.js
new file mode 100644
index 0000000..4589b9c
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/treatment_order.js
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
+
+let triggerHelper = new org.labkey.nbri_ehr.query.NBRI_EHRTriggerHelper(LABKEY.Security.currentUser.id, LABKEY.Security.currentContainer.id);
+
+EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_UPSERT, 'study', 'treatment_order', function(helper, scriptErrors, row, oldRow) {
+ // Max reasonable amount warning. Error when the amount is greater than the max reasonable amount
+ if (row.code) {
+ let drugFormulary = triggerHelper.getFormularyForDrug(row.code);
+ if (drugFormulary && drugFormulary.maxAmount && row.amount > drugFormulary.maxAmount) {
+ EHR.Server.Utils.addError(scriptErrors, 'amount', 'Amount is greater than the maximum reasonable amount: ' + drugFormulary.maxAmount, 'ERROR');
+ }
+ }
+
+ if (row.volume && !row.vol_units) {
+ EHR.Server.Utils.addError(scriptErrors, 'volume', 'Units required for volume.', 'ERROR');
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/treatment_order.query.xml b/nbri_ehr/resources/queries/study/treatment_order.query.xml
new file mode 100644
index 0000000..c2849aa
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/treatment_order.query.xml
@@ -0,0 +1,170 @@
+
+
+
+
+ /EHR/treatmentDetails.view?key=${lsid}
+
+
+
+
+
+
+
+
+ Begin Date
+
+
+ End Date
+ false
+
+
+
+ ehr
+ project
+ project
+
+
+
+ Category
+
+ ehr_lookups
+ data_category
+ value
+
+
+ Depending on what is selected, the treatment will appear on a different schedule (ie. Clinical, Surgical, etc)
+
+
+ false
+ Treatment
+
+ ehr_lookups
+ snomed
+ code
+
+
+
+ Frequency
+
+ ehr_lookups
+ treatment_frequency
+ meaning
+
+
+
+
+
+
+
+
+ Route
+
+ ehr_lookups
+ routes
+ route
+
+
+ 40
+
+
+ Volume
+
+
+ Volume Units
+
+ ehr_lookups
+ volume_units
+ unit
+
+
+
+
+ Amount
+
+
+ Amount Units
+
+ ehr_lookups
+ amount_units
+ unit
+
+
+
+
+ Drug Conc
+
+
+ Conc Units
+
+ ehr_lookups
+ conc_units
+ unit
+
+
+
+
+ Dosage
+
+
+ Dosage Units
+
+ ehr_lookups
+ dosage_units
+ unit
+
+
+
+
+ true
+
+
+ false
+
+
+ Parent ID
+
+
+
+ ehr.context
+ /ehr/studyButtons.js
+ /nbri_ehr/window/MarkTreatmentCompletedWindow.js
+ EHR.DatasetButtons.moreActionsHandler
+
+ Grid Views
+
+
+ Charts
+
+
+ Export
+
+
+ Print
+
+
+
+
+ EHR.DatasetButtons.historyHandler(dataRegion, dataRegionName);
+
+
+
+
+ EHR.window.GetDistinctWindow.getDistinctHandler(dataRegionName);
+
+
+
+
+ EHR.DatasetButtons.showAuditHistoryHandler(dataRegionName);
+
+
+
+
+ NBRI_EHR.window.MarkTreatmentCompletedWindow.buttonHandler(dataRegionName, 'study', 'treatment_order', 'datefield', 'lsid');
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 0000000..27608af
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/treatment_order/.qview.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/treatment_order/Behavior Active Treatment Orders.qview.xml b/nbri_ehr/resources/queries/study/treatment_order/Behavior Active Treatment Orders.qview.xml
new file mode 100644
index 0000000..a495f9e
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/treatment_order/Behavior Active Treatment Orders.qview.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/treatment_order/Clinical Active Treatment Orders.qview.xml b/nbri_ehr/resources/queries/study/treatment_order/Clinical Active Treatment Orders.qview.xml
new file mode 100644
index 0000000..6fc59ae
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/treatment_order/Clinical Active Treatment Orders.qview.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/vitals.js b/nbri_ehr/resources/queries/study/vitals.js
new file mode 100644
index 0000000..334a25c
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/vitals.js
@@ -0,0 +1,7 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+require("ehr/triggers").initScript(this);
\ No newline at end of file
diff --git a/nbri_ehr/resources/queries/study/vitals.query.xml b/nbri_ehr/resources/queries/study/vitals.query.xml
new file mode 100644
index 0000000..29e22d9
--- /dev/null
+++ b/nbri_ehr/resources/queries/study/vitals.query.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ Exemptions
+ Tracks approved exemptions from standard care or procedural requirements for individual primates.
+
+
+ varchar
+ http://cpas.labkey.com/Study#ParticipantId
+
+ ptid
+
+
+
+ timestamp
+ http://cpas.labkey.com/Study#VisitDate
+ http://cpas.labkey.com/Study#VisitDate
+
+
+ timestamp
+ urn:ehr.labkey.org/#EndDate
+
+
+ varchar
+
+
+ varchar
+
+
+
+
+
+
+ varchar
+ http://cpas.labkey.com/Study#ParticipantId
+
+ ptid
+
+
+
+ timestamp
+ http://cpas.labkey.com/Study#VisitDate
+ http://cpas.labkey.com/Study#VisitDate
+
+
+ varchar
+
+
+ timestamp
+ urn:ehr.labkey.org/#EndDate
+
+
+ Animal Record Flags
+ Stores active record flags and status indicators applied to individual primates for colony management purposes.
+
+
+ Histopathology
+ Records histopathology findings from tissue examinations performed on primates, including diagnoses and associated reports.
+
+
+ varchar
+ http://cpas.labkey.com/Study#ParticipantId
+
+ ptid
+
+
+
+ timestamp
+ http://cpas.labkey.com/Study#VisitDate
+ http://cpas.labkey.com/Study#VisitDate
+
+
+ varchar
+
+
+ varchar
+
+
+ varchar
+
+
+ Attachment File
+ varchar
+ http://cpas.fhcrc.org/exp/xml#fileLink
+
+
+
+
+
+
+ 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.
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/scripts/nbri_triggers.js b/nbri_ehr/resources/scripts/nbri_triggers.js
new file mode 100644
index 0000000..e4ad95b
--- /dev/null
+++ b/nbri_ehr/resources/scripts/nbri_triggers.js
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+var console = require("console");
+var LABKEY = require("labkey");
+
+// For more complicated logic, I highly recommend implementing this in java. This provides much better debugging, and the full use of the server-side java APIs
+// Methods on this TriggerHelper can be called from JS, and can return values to the JS code. In general, I try to only pass primitives; however, that may not always be required
+
+// NOTE: NBRI specific script options should be set here in INIT triggers
+
+exports.init = function (EHR) {
+
+
+ EHR.Server.TriggerManager.registerHandler(EHR.Server.TriggerManager.Events.INIT, function (event, helper, EHR) {
+
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('ehr_lookups', 'cage', EHR.Server.TriggerManager.Events.BEFORE_UPSERT);
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('ehr_lookups', 'procedures', EHR.Server.TriggerManager.Events.BEFORE_DELETE);
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('study', 'assignment', EHR.Server.TriggerManager.Events.BEFORE_UPSERT);
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('study', 'clinremarks', EHR.Server.TriggerManager.Events.BEFORE_UPSERT);
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('study', 'clinremarks', EHR.Server.TriggerManager.Events.AFTER_UPSERT);
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('study', 'blood', EHR.Server.TriggerManager.Events.BEFORE_UPSERT);
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('study', 'cases', EHR.Server.TriggerManager.Events.AFTER_UPSERT);
+ EHR.Server.TriggerManager.unregisterAllHandlersForQueryNameAndEvent('study', 'cases', EHR.Server.TriggerManager.Events.AFTER_DELETE);
+
+ helper.setScriptOptions({
+ datasetsToClose: ['assignment', 'protocolAssignment' , 'housing', 'treatment_order', 'observation_order', 'cases', 'pairings', 'exemptions', 'flags']
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'arrival', function(event, helper) {
+ helper.setScriptOptions({
+ allowAnyId: true,
+ requiresStatusRecalc: false, // set in upsert to handle rearrival
+ allowDatesInDistantPast: true,
+ skipAssignmentCheck: true,
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'protocolAssignment', function(event, helper) {
+ helper.setScriptOptions({
+ allowAnyId: false,
+ requiresStatusRecalc: false,
+ allowDatesInDistantPast: true
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'assignment', function(event, helper) {
+ helper.setScriptOptions({
+ allowAnyId: false,
+ requiresStatusRecalc: false,
+ allowDatesInDistantPast: true,
+ skipAssignmentCheck: true,
+ removeTimeFromDate: false,
+ doStandardProtocolCountValidation: false
+ });
+
+ helper.decodeExtraContextProperty('assignmentsInTransaction', []);
+
+ helper.registerRowProcessor(function(helper, row){
+ if (!row)
+ return;
+
+ if (!row.Id || !row.project){
+ return;
+ }
+
+ var assignmentsInTransaction = helper.getProperty('assignmentsInTransaction');
+ assignmentsInTransaction = assignmentsInTransaction || [];
+
+ var shouldAdd = true;
+ if (row.objectid){
+ LABKEY.ExtAdapter.each(assignmentsInTransaction, function(r){
+ if (r.objectid === row.objectid){
+ shouldAdd = false;
+ return false;
+ }
+ }, this);
+ }
+
+ if (shouldAdd){
+ assignmentsInTransaction.push({
+ Id: row.Id,
+ objectid: row.objectid,
+ date: row.date,
+ enddate: row.enddate,
+ qcstate: row.QCState,
+ project: row.project
+ });
+ }
+
+ helper.setProperty('assignmentsInTransaction', assignmentsInTransaction);
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'alias', function(event, helper) {
+ helper.setScriptOptions({
+ allowDatesInDistantPast: true,
+ removeTimeFromDate: false,
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'drug', function(event, helper) {
+ helper.setScriptOptions({
+ allowDeadIds: true,
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'treatment_order', function(event, helper) {
+ helper.setScriptOptions({
+ allowFutureDates: true,
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'observation_order', function(event, helper, EHR) {
+ helper.setScriptOptions({
+ allowFutureDates: true,
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'deaths', function(event, helper) {
+
+ helper.setScriptOptions({
+ datasetsToClose: ['assignment', 'protocolAssignment' , 'housing', 'treatment_order', 'observation_order', 'cases', 'pairings', 'exemptions', 'flags'],
+ allowShippedIds: false,
+ allowDeadIds: false,
+ requiresStatusRecalc: true,
+ allowRequestsInPast: true,
+ removeTimeFromDate: false,
+ allowDatesInDistantPast: true,
+ allowAnyId: true
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'necropsy', function(event, helper) {
+
+ helper.setScriptOptions({
+ removeTimeFromDate: false
+ });
+ });
+
+ EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.INIT, 'study', 'cases', function(event, helper) {
+ helper.setScriptOptions({
+ allowDatesInDistantPast: true
+ });
+ });
+}
diff --git a/nbri_ehr/resources/views/NBRI_amimalHistory.webpart.xml b/nbri_ehr/resources/views/NBRI_amimalHistory.webpart.xml
new file mode 100644
index 0000000..ad91414
--- /dev/null
+++ b/nbri_ehr/resources/views/NBRI_amimalHistory.webpart.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/acquisitionReport.html b/nbri_ehr/resources/views/acquisitionReport.html
new file mode 100644
index 0000000..f1ecf91
--- /dev/null
+++ b/nbri_ehr/resources/views/acquisitionReport.html
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/views/acquisitionReport.view.xml b/nbri_ehr/resources/views/acquisitionReport.view.xml
new file mode 100644
index 0000000..bbbab5f
--- /dev/null
+++ b/nbri_ehr/resources/views/acquisitionReport.view.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/animalHistory.html b/nbri_ehr/resources/views/animalHistory.html
new file mode 100644
index 0000000..5c2c43a
--- /dev/null
+++ b/nbri_ehr/resources/views/animalHistory.html
@@ -0,0 +1,107 @@
+
diff --git a/nbri_ehr/resources/views/animalHistory.view.xml b/nbri_ehr/resources/views/animalHistory.view.xml
new file mode 100644
index 0000000..885cf5f
--- /dev/null
+++ b/nbri_ehr/resources/views/animalHistory.view.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/begin.html b/nbri_ehr/resources/views/begin.html
new file mode 100644
index 0000000..0987b92
--- /dev/null
+++ b/nbri_ehr/resources/views/begin.html
@@ -0,0 +1,251 @@
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/begin.view.xml b/nbri_ehr/resources/views/begin.view.xml
new file mode 100644
index 0000000..2132d52
--- /dev/null
+++ b/nbri_ehr/resources/views/begin.view.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/begin.webpart.xml b/nbri_ehr/resources/views/begin.webpart.xml
new file mode 100644
index 0000000..6082de1
--- /dev/null
+++ b/nbri_ehr/resources/views/begin.webpart.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/cageDetails.html b/nbri_ehr/resources/views/cageDetails.html
new file mode 100644
index 0000000..a0c5da2
--- /dev/null
+++ b/nbri_ehr/resources/views/cageDetails.html
@@ -0,0 +1,83 @@
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/cageDetails.view.xml b/nbri_ehr/resources/views/cageDetails.view.xml
new file mode 100644
index 0000000..68b5372
--- /dev/null
+++ b/nbri_ehr/resources/views/cageDetails.view.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/clinicalHistoryExport.html b/nbri_ehr/resources/views/clinicalHistoryExport.html
new file mode 100644
index 0000000..8aff7e7
--- /dev/null
+++ b/nbri_ehr/resources/views/clinicalHistoryExport.html
@@ -0,0 +1,86 @@
+
+ National Biomedical Research Institute at UC Davis Animal Record
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/clinicalHistoryExport.view.xml b/nbri_ehr/resources/views/clinicalHistoryExport.view.xml
new file mode 100644
index 0000000..35f09e3
--- /dev/null
+++ b/nbri_ehr/resources/views/clinicalHistoryExport.view.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/datasets.html b/nbri_ehr/resources/views/datasets.html
new file mode 100644
index 0000000..f6a4150
--- /dev/null
+++ b/nbri_ehr/resources/views/datasets.html
@@ -0,0 +1,130 @@
+
diff --git a/nbri_ehr/resources/views/datasets.view.xml b/nbri_ehr/resources/views/datasets.view.xml
new file mode 100644
index 0000000..895d3d0
--- /dev/null
+++ b/nbri_ehr/resources/views/datasets.view.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/enterData.html b/nbri_ehr/resources/views/enterData.html
new file mode 100644
index 0000000..2d9c84f
--- /dev/null
+++ b/nbri_ehr/resources/views/enterData.html
@@ -0,0 +1,43 @@
+
+
+
diff --git a/nbri_ehr/resources/views/enterData.view.xml b/nbri_ehr/resources/views/enterData.view.xml
new file mode 100644
index 0000000..a2ae43f
--- /dev/null
+++ b/nbri_ehr/resources/views/enterData.view.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/nbri_login.html b/nbri_ehr/resources/views/nbri_login.html
new file mode 100644
index 0000000..30a475a
--- /dev/null
+++ b/nbri_ehr/resources/views/nbri_login.html
@@ -0,0 +1,48 @@
+
+
+
Or Sign In using
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/nbri_login.view.xml b/nbri_ehr/resources/views/nbri_login.view.xml
new file mode 100644
index 0000000..ec29417
--- /dev/null
+++ b/nbri_ehr/resources/views/nbri_login.view.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/necropsy.html b/nbri_ehr/resources/views/necropsy.html
new file mode 100644
index 0000000..9952555
--- /dev/null
+++ b/nbri_ehr/resources/views/necropsy.html
@@ -0,0 +1,98 @@
+
+
+
+ National Biomedical Research Institute at UC Davis Necropsy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nbri_ehr/resources/views/necropsy.view.xml b/nbri_ehr/resources/views/necropsy.view.xml
new file mode 100644
index 0000000..e10c578
--- /dev/null
+++ b/nbri_ehr/resources/views/necropsy.view.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/participantView.html b/nbri_ehr/resources/views/participantView.html
new file mode 100644
index 0000000..89f88bf
--- /dev/null
+++ b/nbri_ehr/resources/views/participantView.html
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/views/participantView.view.xml b/nbri_ehr/resources/views/participantView.view.xml
new file mode 100644
index 0000000..c41ebae
--- /dev/null
+++ b/nbri_ehr/resources/views/participantView.view.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/ehr/metadata/Default.js b/nbri_ehr/resources/web/ehr/metadata/Default.js
new file mode 100644
index 0000000..ba9ff26
--- /dev/null
+++ b/nbri_ehr/resources/web/ehr/metadata/Default.js
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+
+EHR.model.DataModelManager.registerMetadata('Default', {
+ byQuery: {
+ 'study.MHC': {
+ QCState: {
+ getInitialValue: function(v){
+ var qc;
+ if (!v && EHR.Security.getQCStateByLabel('Completed'))
+ qc = EHR.Security.getQCStateByLabel('Completed').RowId;
+ return v || qc;
+ },
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/buttons/ProcedureOrderCompleteButton.js b/nbri_ehr/resources/web/nbri_ehr/buttons/ProcedureOrderCompleteButton.js
new file mode 100644
index 0000000..c5969bd
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/buttons/ProcedureOrderCompleteButton.js
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+Ext4.namespace('NBRI_EHR.ProcedureOrderCompleteButton');
+
+Ext4.define('NBRI_EHR.window.ProcedureOrderCompleteWindow', {
+ extend: 'Ext.window.Window',
+ returnLocation: null,
+
+ initComponent: function() {
+ Ext4.apply(this, {
+ title: 'Mark Procedure Order Completed',
+ modal: true,
+ width: 450,
+ bodyStyle: 'padding: 5px;',
+ closeAction: 'destroy',
+ items: [{
+ xtype: 'panel',
+ border: false,
+ html: '
This will mark the procedure order(s) completed without entering a procedure. Ensure the procedure has been entered separately if it should be recorded.
If not already present, a demographics record will be created. This is the table holding 1 row for all known IDs.
' +
+ '
If birth, species, gender, and/or source facility are entered at the time the arrival is entered, the demographic record will use these. Editing the arrival record after the fact will automatically update the demographics table.
' +
+ '
If birth date is entered, a birth record will be created.
' +
+ '
If dam and/or sire are provided, the demographic and pedigree records will use these.
' +
+ '
',
+ style: 'padding: 5px;'
+ }
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/BirthInstructionsPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/BirthInstructionsPanel.js
new file mode 100644
index 0000000..c5d830b
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/BirthInstructionsPanel.js
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+Ext4.define('NBRI_EHR.panel.BirthInstructionsPanel', {
+ extend: 'NBRI_EHR.panel.InstructionsPanel',
+ alias: 'widget.nbri_ehr-birthinstructionspanel',
+
+ getItems: function(){
+ return {
+ html: 'This form allows you to enter new birth. The birth can be entered, and then saved. If saved as a draft, none of the extra steps below will occur. Only once the record has been finalized will these steps take place:
' +
+ '
' +
+ '
If not already present, a demographics record will be created. This is the table holding 1 row for all known IDs.
' +
+ '
If date, species, and gender are entered at the time the birth record is entered, the demographic record will use these.
' +
+ '
If room is entered, a housing record will be created with the room and enclosure starting on the birth date.
' +
+ '
',
+ style: 'padding: 5px;'
+ };
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/BloodSummaryPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/BloodSummaryPanel.js
new file mode 100644
index 0000000..40db419
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/BloodSummaryPanel.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+/**
+ * @param subjects
+ */
+Ext4.define('NBRI.panel.BloodSummaryPanel', {
+ extend: 'Ext.panel.Panel',
+ alias: 'widget.nbri-bloodsummarypanel',
+ intervals: {},
+ plotHeight: 400,
+
+ bloodPerKgCol: 'species/scientific_name/blood_per_kg',
+ bloodMaxDrawPctCol: 'species/scientific_name/max_draw_pct',
+ bloodDrawIntervalCol: 'species/scientific_name/blood_draw_interval',
+
+ initComponent: function(){
+ Ext4.apply(this, {
+ border: false,
+ defaults: {
+ border: false
+ }
+ });
+
+ this.callParent();
+
+ var target = this.add({
+ xtype: 'ehr-bloodsummarypanel',
+ subjects: this.subjects,
+ getSubjectItems: this.summaryItems,
+ bloodPerKgCol: this.bloodPerKgCol,
+ bloodMaxDrawPctCol: this.bloodMaxDrawPctCol,
+ bloodDrawIntervalCol: this.bloodDrawIntervalCol
+ });
+
+
+ if (!Ext4.isFunction(target.getWith)) {
+ target.getWidth = function () {
+ return 1000;
+ }
+ }
+ },
+
+ summaryItems: function(subject, demographics) {
+ var toAdd = [];
+ toAdd.push({
+ html: '',
+ border: false,
+ style: 'margin-bottom: 10px;'
+ });
+
+ toAdd.push({
+ xtype: 'ldk-querycmp',
+ style: 'margin-bottom: 10px;',
+ queryConfig: LDK.Utils.getReadOnlyQWPConfig({
+ title: 'Recent Blood Draws: ' + subject,
+ schemaName: 'study',
+ queryName: 'bloodDrawsByDay',
+ allowHeaderLock: false,
+ frame: 'title',
+ filters: [
+ LABKEY.Filter.create('Id', subject, LABKEY.Filter.Types.EQUAL),
+ LABKEY.Filter.create('date', '-' + (demographics.getValue(this.bloodDrawIntervalCol) * 2) + 'd', LABKEY.Filter.Types.DATE_GREATER_THAN_OR_EQUAL)
+ ],
+ sort: '-date'
+ })
+ });
+
+ toAdd.push({
+ html: '',
+ border: false,
+ style: 'margin-bottom: 10px;'
+ });
+
+ return toAdd;
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/CaseHistoryPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/CaseHistoryPanel.js
new file mode 100644
index 0000000..ce4695d
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/CaseHistoryPanel.js
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * @cfg caseId
+ * @cfg maxGridHeight
+ * @cfg autoLoadRecords
+ */
+Ext4.define('NBRI_EHR.panel.CaseHistoryPanel', {
+ extend: 'NBRI_EHR.panel.ClinicalHistoryPanel',
+ alias: 'widget.nbri_ehr-casehistorypanel',
+
+ getStoreConfig: function(){
+ return {
+ type: 'ehr-clinicalhistorystore',
+ containerPath: this.containerPath,
+ actionName: 'getCaseHistory',
+ sorters: [{property: 'group'}, {property: 'timeString'}]
+ };
+ }
+});
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/CaseTemplatePanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/CaseTemplatePanel.js
new file mode 100644
index 0000000..866daa7
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/CaseTemplatePanel.js
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+Ext4.define('NBRI_EHR.panel.CaseTemplatePanel', {
+ extend: 'Ext.panel.Panel',
+ alias: 'widget.nbri_ehr-casetemplatepanel',
+ plugins: ['ehr-collapsibleDataEntryPanel'],
+
+ initComponent: function(){
+ var buttons = [];
+ LDK.Assert.assertNotEmpty('No data entry panel', this.dataEntryPanel);
+ var btnCfg = EHR.DataEntryUtils.getDataEntryFormButton('APPLYFORMTEMPLATE_NO_ID');
+ if (btnCfg){
+ btnCfg = this.dataEntryPanel.configureButton(btnCfg);
+ if (btnCfg){
+ btnCfg.defaultDate = new Date();
+ btnCfg.text = 'Apply Template To Form';
+ buttons.push(btnCfg);
+ }
+ }
+
+ Ext4.apply(this, {
+ defaults: {
+
+ },
+ bodyStyle: 'padding: 5px;',
+ title: this.getTitle(),
+ items: [{
+ html: this.getHtml(),
+ maxWidth: Ext4.getBody().getWidth() * 0.8,
+ style: 'padding-top: 10px;padding-bottom: 10px;',
+ border: false
+ },{
+ layout: 'hbox',
+ border: false,
+ defaults: {
+ style: 'margin-right: 5px;'
+ },
+ items: buttons
+ }]
+ });
+
+ this.formConfig.initCollapsed = true;
+ this.formConfig.dataDependentCollapseHeader = false;
+
+ this.callParent(arguments);
+ },
+
+ getTitle: function(){
+ return 'Case Template';
+ },
+
+ getHtml: function(){
+ return 'Start with a case template by clicking \'Apply Template To Form\' below or fill out the sections directly.';
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/ClinicalHistoryPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/ClinicalHistoryPanel.js
new file mode 100644
index 0000000..bd1ea10
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/ClinicalHistoryPanel.js
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * @cfg subjectId
+ * @cfg minDate
+ * @cfg maxDate
+ * @cfg maxGridHeight
+ * @cfg autoLoadRecords
+ * @cfg hideExportBtn
+ * @cfg sortMode
+ * @cfg checkedItems
+ * @cfg showMaxDate
+ * @cfg redacted
+ * @cfg printMode
+ */
+Ext4.define('NBRI_EHR.panel.ClinicalHistoryPanel', {
+ extend: 'EHR.panel.ClinicalHistoryPanel',
+ alias: 'widget.nbri_ehr-clinicalhistorypanel',
+
+ showMaxDate: false,
+
+ getGridConfig: function(){
+ return {
+ xtype: 'grid',
+ border: this.printMode ? false : true,
+ minHeight: 100,
+ minWidth: this.width - 50,
+ cls: 'ldk-grid',
+ maxHeight: this.maxGridHeight,
+ height: this.gridHeight,
+ hideHeaders: true,
+ deferEmptyText: true,
+ viewConfig : {
+ emptyText: this.minDate ? 'No records found since: ' + Ext4.util.Format.date(this.minDate, LABKEY.extDefaultDateFormat): 'There are no records to display',
+ deferEmptyText: true,
+ enableTextSelection: true,
+ border: false,
+ stripeRows : true
+ },
+ columns: this.getColumnConfig(),
+ features: [this.getGroupingFeature()],
+ store: this.getStoreConfig(),
+ itemId: 'gridPanel',
+ width: this.width,
+ subjectId: this.subjectId,
+ caseId: this.caseId,
+ minDate: this.minDate,
+ maxDate: this.maxDate,
+ tbar: this.hideGridButtons ? null : {
+ border: true,
+ items: [{
+ xtype: 'datefield',
+ fieldLabel: 'Min Date',
+ itemId: 'minDate',
+ labelWidth: 80,
+ width: 200,
+ value: this.minDate
+ },{
+ xtype: 'datefield',
+ fieldLabel: 'Max Date',
+ itemId: 'maxDate',
+ labelWidth: 80,
+ width: 200,
+ hidden: this.showMaxDate,
+ value: this.maxDate
+ },{
+ xtype: 'button',
+ text: 'Reload',
+ handler: function(btn){
+ var panel = btn.up('ehr-clinicalhistorypanel');
+ panel.doReload();
+ }
+ },{
+ text: 'Show/Hide Types',
+ scope: this,
+ handler: function(btn){
+ this.showFilterPanel();
+ }
+ },{
+ text: 'Collapse All',
+ hidden: this.printMode,
+ collapsed: false,
+ handler: function(btn){
+ var grid = btn.up('grid');
+ var feature = grid.getView().getFeature('historyGrouping');
+
+ if (btn.collapsed){
+ feature.expandAll();
+ btn.setText('Collapse All');
+ }
+ else {
+ feature.collapseAll();
+ btn.setText('Expand All')
+ }
+
+ btn.collapsed = !btn.collapsed;
+ }
+ },{
+ hidden: this.printMode,
+ text: (this.sortMode == 'type' ? 'Group By Date' : 'Group By Type'),
+ sortMode: this.sortMode == 'type' ? 'date' : 'type',
+ scope: this,
+ handler: function(btn){
+ //toggle the button
+ if (btn.sortMode == 'type'){
+ btn.setText('Group By Date');
+ btn.sortMode = 'date';
+ this.changeMode('type');
+ }
+ else {
+ btn.setText('Group By Type');
+ btn.sortMode = 'type';
+ this.changeMode('date');
+ }
+ }
+ },{
+ text: 'Print Version',
+ hidden: this.hideExportBtn || this.printMode,
+ scope: this,
+ handler: function(btn){
+ var params = {
+ hideGridButtons: true
+ };
+ if (this.subjectId)
+ params.subjectId = [this.subjectId];
+ if (this.caseId)
+ params.caseId = this.caseId;
+ if (this.minDate)
+ params.minDate = Ext4.util.Format.date(this.minDate, LABKEY.extDefaultDateFormat);
+ if (this.maxDate)
+ params.maxDate = Ext4.util.Format.date(this.maxDate, LABKEY.extDefaultDateFormat);
+ if (this.sortMode)
+ params.sortMode = this.sortMode;
+ if (this.checkedItems && this.checkedItems.length)
+ params.checkedItems = this.checkedItems.join(';');
+
+ var url = LABKEY.ActionURL.buildURL('nbri_ehr', 'clinicalHistoryExport', null, params);
+ window.open(url, '_blank');
+ }
+ }]
+ }
+ };
+ }
+});
+
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/InstructionsPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/InstructionsPanel.js
new file mode 100644
index 0000000..7ed9128
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/InstructionsPanel.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+Ext4.define('NBRI_EHR.panel.InstructionsPanel', {
+ extend: 'Ext.panel.Panel',
+ alias: 'widget.nbri_ehr-instructionspanel',
+ plugins: ['ehr-collapsibleDataEntryPanel'],
+
+ initComponent: function(){
+ Ext4.apply(this, {
+ defaults: {
+ border: false
+ },
+ items: [this.getItems()]
+ });
+
+ this.formConfig.initCollapsed = true;
+ this.formConfig.dataDependentCollapseHeader = false;
+
+ this.callParent(arguments);
+ },
+
+ getItems: function(){
+ return {};
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/NBRIExamCasesDataEntryPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/NBRIExamCasesDataEntryPanel.js
new file mode 100644
index 0000000..f34cc4c
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/NBRIExamCasesDataEntryPanel.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+Ext4.define('NBRI_EHR.panel.ExamCasesDataEntryPanel', {
+ extend: 'EHR.panel.TaskDataEntryPanel',
+
+ onBeforeSubmit: function(btn){
+ if (!btn || !btn.targetQC || ['Completed', 'Review Required'].indexOf(btn.targetQC) == -1){
+ return;
+ }
+
+ var storeServer = this.storeCollection.getServerStoreByName('study.cases');
+ var storeClient = this.storeCollection.getClientStoreByName('cases');
+
+ var caseid;
+
+ // Existing case
+ if (storeClient && storeClient.data && storeClient.data.get(0) && storeClient.data.get(0).get('objectid')){
+ caseid = storeClient.data.get(0).get('objectid');
+ }
+ else { // New case
+ caseid = LABKEY.Utils.generateUUID();
+ storeServer.each(function(r){
+ r.set('objectid', caseid);
+ }, this);
+
+ storeClient.each(function(r){
+ r.set('objectid', caseid);
+ }, this);
+ }
+
+ this.storeCollection.clientStores.each(function(s){
+ if (s.getFields().get('caseid')) {
+ s.each(function(r){
+ r.set('caseid', caseid);
+ }, this);
+ }
+ }, this);
+
+ return true;
+ }
+ });
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/NarrowSnapshotPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/NarrowSnapshotPanel.js
new file mode 100644
index 0000000..21a0175
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/NarrowSnapshotPanel.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+Ext4.define('NBRI_EHR.panel.NarrowSnapshotPanel', {
+ extend: 'NBRI_EHR.panel.SnapshotPanel',
+ alias: 'widget.nbri_ehr-narrowsnapshotpanel',
+
+ showLocationDuration: false,
+ showExtendedInformation: true,
+
+ minWidth: 800,
+
+ initComponent: function(){
+
+ this.defaultLabelWidth = 120;
+ this.callParent();
+ },
+
+ getItems: function() {
+ var items = this.getBaseItems();
+
+ //combine the first and second columns
+ var secondCol = items[0].items[1].items[1];
+ var extended = this.getExtendedItems();
+
+ var index = items[0].items[1].items.indexOf(secondCol);
+ if (index !== -1) {
+ items[0].items[1].items = items[0].items[1].items.splice(index, 1);
+ }
+
+ items[0].items[1].items = items[0].items[1].items.concat(extended[0].items[1].items[0]);
+ items[0].items[1].items[1].items = items[0].items[1].items[1].items.concat(extended[0].items[1].items[1].items[0]);
+
+ items[0].items[1].items[0].columnWidth = 0.45;
+ items[0].items[1].items[1].columnWidth = 0.55;
+
+ return items;
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/SelectCasePanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/SelectCasePanel.js
new file mode 100644
index 0000000..2be44f0
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/SelectCasePanel.js
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * @cfg {String} animalId
+ * @cfg {Object} casepanel
+ */
+Ext4.define('NBRI_EHR.panel.SelectCasePanel', {
+ extend: 'Ext.panel.Panel',
+ alias: 'widget.nbri_ehr-selectcasepanel',
+
+ statics: {
+
+ getButtonConfig: function(){
+ return [{
+ xtype: 'button',
+ text: 'Use Selected Case',
+ handler: function(btn){
+ var window = btn.up('window');
+ var grid = window.down('grid');
+ var rec = grid.getSelectionModel().getSelection()[0];
+ EHR.DemographicsCache.reportCaseSelected(rec);
+ window.close();
+ }
+ }, {
+ xtype: 'button',
+ text: 'Show Closed Cases',
+ handler: function(btn){
+ var owner = btn.up('window');
+ if (owner)
+ owner = owner.down('panel');
+
+ var store = owner.getStore();
+ const formType = LABKEY.ActionURL.getParameter('formType');
+ const caseCategory = NBRI_EHR.panel.SelectCasePanel.getCaseCategory()[formType];
+
+ LDK.Assert.assertNotEmpty('Unable to find animalId in ManageCasesPanel', owner.animalId);
+ var filterArray = [
+ LABKEY.Filter.create('Id', owner.animalId, LABKEY.Filter.Types.EQUAL),
+ LABKEY.Filter.create('category', caseCategory, LABKEY.Filter.Types.EQUAL),
+ LABKEY.Filter.create('QCState/Label', "Completed", LABKEY.Filter.Types.EQUAL)
+ ];
+
+ if (btn.text == 'Show Closed Cases'){
+ btn.setText('Hide Closed Cases');
+ }
+ else {
+ btn.setText('Show Closed Cases');
+ filterArray.push(LABKEY.Filter.create('isOpen', true, LABKEY.Filter.Types.EQUAL));
+ }
+
+ store.filterArray = filterArray;
+ store.load();
+ }
+ }]
+ },
+
+ getCaseCategory: function(){
+ return {
+ 'Clinical Cases': 'Clinical',
+ 'Clinical Rounds': 'Clinical',
+ 'Behavioral Cases': 'Behavior',
+ 'Behavioral Rounds': 'Behavior',
+ };
+ }
+ },
+
+ initComponent: function(){
+ Ext4.apply(this, {
+ border: false,
+ items: [this.getGridConfig()],
+ buttons: this.hideButtons ? null : this.getButtonConfig()
+ });
+
+ this.callParent();
+
+ this.addEvents('storeloaded');
+ },
+
+ getStore: function(){
+ if (this.store)
+ return this.store;
+
+ const formType = LABKEY.ActionURL.getParameter('formType');
+ const caseCategory = NBRI_EHR.panel.SelectCasePanel.getCaseCategory()[formType];
+
+ this.store = Ext4.create('LABKEY.ext4.data.Store', {
+ schemaName: 'study',
+ queryName: 'cases',
+ columns: 'lsid,objectid,caseId,taskId,Id,date,enddate,reviewdate,category,openRemark,closeRemark,performedby,problemCategory,problemSubcategory,plan',
+ filterArray: [
+ LABKEY.Filter.create('Id', this.animalId, LABKEY.Filter.Types.EQUAL),
+ LABKEY.Filter.create('isOpen', true, LABKEY.Filter.Types.EQUAL),
+ LABKEY.Filter.create('category', caseCategory, LABKEY.Filter.Types.EQUAL),
+ LABKEY.Filter.create('QCState/Label', "Completed", LABKEY.Filter.Types.EQUAL),
+
+ ],
+ autoLoad: true,
+ listeners: {
+ scope: this,
+ synccomplete: function(store){
+ var grid = this.down('grid');
+ if (grid){
+ grid.getView().refresh();
+ }
+
+ EHR.DemographicsCache.clearCache(this.animalId);
+ },
+ load: function(store){
+ //NOTE: consumed by SnapshotPanel
+ this.fireEvent('storeloaded', this);
+ },
+ exception: function(store){
+ //NOTE: refresh the store in order to avoid invalid data on the client
+ store.load();
+ }
+ }
+ });
+
+ return this.store;
+ },
+
+ getGridConfig: function(){
+ var me = this;
+ return {
+ xtype: 'grid',
+ cls: 'ldk-grid', //variable row height
+ border: false,
+ store: this.getStore(),
+ viewConfig: {
+ loadMask: true
+ },
+ columns: [{
+ xtype: 'actioncolumn',
+ width: 40,
+ icon: LABKEY.ActionURL.getContextPath() + '/_images/editprops.png',
+ tooltip: 'Select',
+ handler: function(view, rowIndex, colIndex, item, e, rec){
+ if (me.casepanel){
+ EHR.DemographicsCache.reportCaseSelected(rec);
+ this.up("window").close();
+ }
+ }
+
+ },{
+ header: 'Open Date',
+ dataIndex: 'date',
+ xtype: 'datecolumn',
+ format: LABKEY.extDefaultDateFormat,
+ width: 110
+ },{
+ header: 'Close Date',
+ dataIndex: 'enddate',
+ xtype: 'datecolumn',
+ format: LABKEY.extDefaultDateFormat,
+ width: 110
+ },{
+ header: 'Problem',
+ dataIndex: 'problemCategory',
+ tdCls: 'ldk-wrap-text',
+ width: 150
+ },{
+ header: 'Subcategory',
+ dataIndex: 'problemSubcategory',
+ tdCls: 'ldk-wrap-text',
+ width: 150
+ },{
+ header: 'Open Remark',
+ dataIndex: 'openRemark',
+ tdCls: 'ldk-wrap-text',
+ width: 350
+ },{
+ header: 'Close Remark',
+ dataIndex: 'closeRemark',
+ tdCls: 'ldk-wrap-text',
+ width: 350
+ }]
+ }
+ }
+});
+
+
+
+
+
diff --git a/nbri_ehr/resources/web/nbri_ehr/panel/SnapshotPanel.js b/nbri_ehr/resources/web/nbri_ehr/panel/SnapshotPanel.js
new file mode 100644
index 0000000..dd59726
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/panel/SnapshotPanel.js
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+Ext4.define('NBRI_EHR.panel.SnapshotPanel', {
+ extend: 'EHR.panel.SnapshotPanel',
+ alias: 'widget.nbri_ehr-snapshotpanel',
+
+ initComponent: function() {
+ Ext4.apply(this, {
+ defaults: {
+ border: false,
+ },
+ showExtendedInformation: true,
+ items: this.getItems(),
+ });
+
+ this.callParent();
+
+
+ this.on('afterrender', function() {
+
+ var displayField = this.down('#flags');
+ if (displayField && displayField.getEl()) {
+
+ var anchor = displayField.getEl('nbriFlagsLink');
+
+ if (anchor) {
+ Ext4.get(anchor).on('click', function(e) {
+ e.preventDefault();
+ NBRI_EHR.Utils.showFlagPopup(this.subjectId, this);
+ });
+ }
+ }
+ });
+ },
+
+ onLoad: function(ids, resultMap){
+ if (ids && ids.length && ids[0] != this.subjectId){
+ return;
+ }
+
+ this.callParent(arguments);
+ },
+
+ getBaseItems: function(){
+ return [{
+ xtype: 'container',
+ border: false,
+ defaults: {
+ border: false
+ },
+ items: [{
+ xtype: 'container',
+ html: 'Summary:'
+ },{
+ bodyStyle: 'padding-bottom: 20px;',
+ layout: 'column',
+ defaults: {
+ border: false
+ },
+ items: [{
+ xtype: 'container',
+ columnWidth: 0.25,
+ defaults: {
+ labelWidth: this.defaultLabelWidth,
+ style: 'margin-right: 20px;'
+ },
+ items: [{
+ xtype: 'displayfield',
+ fieldLabel: 'Location',
+ name: 'location'
+ },{
+ xtype: 'displayfield',
+ hidden: this.redacted,
+ name: 'protocolAssignment',
+ fieldLabel: 'Protocol'
+ },{
+ xtype: 'displayfield',
+ hidden: this.redacted,
+ name: 'projectAssignment',
+ fieldLabel: 'Project'
+ },{
+ xtype: 'displayfield',
+ fieldLabel: 'Source',
+ name: 'source'
+ },{
+ xtype: 'displayfield',
+ fieldLabel: 'Prev Id',
+ name: 'prev_id'
+ }]
+ },{
+ xtype: 'container',
+ columnWidth: 0.25,
+ defaults: {
+ labelWidth: this.defaultLabelWidth,
+ style: 'margin-right: 20px;'
+ },
+ items: [{
+ xtype: 'displayfield',
+ fieldLabel: 'Status',
+ name: 'calculated_status'
+ },{
+ xtype: 'displayfield',
+ fieldLabel: 'Sex',
+ name: 'gender'
+ },{
+ xtype: 'displayfield',
+ fieldLabel: 'Species',
+ name: 'species'
+ },{
+ xtype: 'displayfield',
+ fieldLabel: 'Age',
+ name: 'age'
+ }]
+ },{
+ xtype: 'container',
+ columnWidth: 0.35,
+ defaults: {
+ labelWidth: this.defaultLabelWidth,
+ style: 'margin-right: 20px;'
+ },
+ items: [{
+ xtype: 'displayfield',
+ fieldLabel: 'Flags',
+ name: 'flags'
+ },{
+ xtype: 'displayfield',
+ fieldLabel: 'Remark',
+ name: 'remark'
+ },{
+ xtype: 'displayfield',
+ fieldLabel: 'Last TB',
+ name: 'lastTB'
+ },{
+ xtype: 'displayfield',
+ fieldLabel: 'Weights',
+ name: 'weights'
+ }]
+ }]
+ }]
+ }];
+ },
+
+ appendDemographicsResults: function(toSet, row, id){
+ if (!row){
+ console.log('Id not found');
+ return;
+ }
+
+ var animalId = row.getId() || id;
+ if (!Ext4.isEmpty(animalId)){
+ toSet['animalId'] = LABKEY.Utils.encodeHtml(id);
+ }
+
+ var data = row.getData();
+ var status = data ? data.necropsy_status : undefined;
+ var statusVal = 'Unknown';
+ if (status) {
+ if (typeof status == 'string') {
+ statusVal = status;
+ } else {
+ statusVal = status[0] && (Object.keys(status[0]).length > 0) ? status[0].necropsy_status : statusVal;
+ }
+ }
+ toSet['calculated_status'] = ''
+ + LABKEY.Utils.encodeHtml(statusVal) + '';
+
+ toSet['species'] = LABKEY.Utils.encodeHtml(row.getSpeciesCommonName());
+ toSet['geographic_origin'] = LABKEY.Utils.encodeHtml(row.getGeographicOrigin());
+ toSet['gender'] = LABKEY.Utils.encodeHtml(row.getGender());
+ toSet['age'] = LABKEY.Utils.encodeHtml(row.getAgeInYearsAndDays());
+ toSet['source'] = LABKEY.Utils.encodeHtml(row.getSourceRecord());
+
+ var location;
+ if (row.getActiveHousing() && row.getActiveHousing().length){
+ var housingRow = row.getActiveHousing();
+ location = '';
+ if (housingRow)
+ location += LABKEY.Utils.encodeHtml(housingRow[0]?.['cage/cage']);
+
+ if (location){
+ if (this.showLocationDuration && housingRow[0].date){
+ var date = LDK.ConvertUtils.parseDate(housingRow[0].date);
+ if (date)
+ location += ' (' + Ext4.Date.format(date, LABKEY.extDefaultDateFormat) + ')';
+ }
+ }
+ }
+
+ toSet['location'] = location || 'No active housing';
+ this.appendProtocolAssignment(toSet, row);
+ },
+
+ appendFlags: function(toSet, results){
+ var values = [];
+ if (results){
+ Ext4.each(results, function(row){
+ var category = row['flag/category'];
+ var highlight = row['flag/category/doHighlight'];
+ var omit = row['flag/category/omitFromOverview'];
+
+ //skip
+ if (omit === true)
+ return;
+
+ if (category)
+ category = Ext4.String.trim(category);
+
+ var val = LABKEY.Utils.encodeHtml(this.getFlagDisplayValue(row));
+ var text = val;
+ if (category)
+ text = LABKEY.Utils.encodeHtml(category) + ': ' + val;
+
+ if (text && highlight)
+ text = '' + text + '';
+
+ if (text)
+ values.push(text);
+ }, this);
+
+ if (values.length) {
+ values = Ext4.unique(values);
+ }
+ }
+
+ toSet['flags'] = values.length ? '' + values.join(' ') + '' : null;
+ },
+
+ appendAssignments: function(toSet, results){
+ toSet['projectAssignment'] = null;
+
+ if (this.redacted) {
+ return;
+ }
+
+ var values = [];
+ if (results){
+ Ext4.each(results, function(row){
+ if (row['project/name']) {
+ values.push(LABKEY.Utils.encodeHtml(row['project/name']));
+ }
+ }, this);
+ }
+
+ toSet['projectAssignment'] = values.length ? values.join(' ') : 'None';
+ },
+
+ appendProtocolAssignment: function(toSet, results){
+ let paRecords = results.getData()['protocolAssignments'];
+ let values = [];
+
+ let val;
+ if (Ext4.isArray(paRecords) && paRecords.length > 0) {
+ Ext4.each(paRecords, function(record) {
+ val = record['protocol/displayName'];
+ if (record['protocol/InvestigatorId/lastName']) {
+ val += " - " + LABKEY.Utils.encodeHtml(record['protocol/InvestigatorId/lastName']);
+ }
+ if (record['protocol/InvestigatorId/firstName']) {
+ val += ", " + LABKEY.Utils.encodeHtml(record['protocol/InvestigatorId/firstName']);
+ }
+ values.push(LABKEY.Utils.encodeHtml(val));
+ });
+ }
+
+ toSet['protocolAssignment'] = values.length > 0 ? values.join(' ') : 'None';
+ },
+
+ appendRoommateResults: function(toSet, results, id){
+ var cagemates = 0;
+ var animals = [];
+ if (results && results.length){
+ var row = results[0];
+ if (row.animals){
+ animals = row.animals.replace(/( )*,( )*/g, ',');
+ animals = animals.split(',');
+ animals.sort();
+ var index = animals.indexOf(id);
+ if (index !== -1) {
+ animals.splice(index, 1);
+ }
+ }
+ }
+
+ toSet['cagemates'] = LABKEY.Utils.encodeHtml(cagemates); // encoding not currently useful, but future-proofing here
+
+ if (animals.length > 3){
+ toSet['cagemates'] = animals.length + ' animals';
+ }
+ else if (animals.length == 0){
+ toSet['cagemates'] = 'None';
+ }
+ else {
+ var html = '';
+ var sep = '';
+ Ext4.each(animals, function(id) {
+ html += sep + '' + encodeURIComponent(id) + '';
+ sep = ', ';
+ });
+ toSet['cagemates'] = html;
+ }
+ },
+
+ appendCaseSummary: function(toSet, results){
+ var el = this.down('panel[name=caseSummary]');
+ if (!el){
+ return;
+ }
+
+ results = results || [];
+ var filteredResults = [];
+ Ext4.Array.forEach(results, function(row){
+ var enddate = row.enddate ? LDK.ConvertUtils.parseDate(row.enddate) : null;
+ if (!enddate || enddate.getTime() > (new Date()).getTime()){
+ filteredResults.push(row);
+ }
+ }, this);
+
+ el.appendTable({
+ rows: filteredResults
+ }, [{
+ name: 'category',
+ label: 'Category'
+ },{
+ name: 'problemCategory',
+ label: 'Problem Area',
+ attrs: {
+ style: 'width: 150px !important;"'
+ }
+ },{
+ name: 'problemSubcategory',
+ label: 'Problem Subcategory',
+ attrs: {
+ style: 'width: 150px !important;"'
+ }
+ },{
+ name: 'date',
+ label: 'Open Date',
+ attrs: {
+ style: 'width: 120px !important;"'
+ },
+ dateFormat: LABKEY.extDefaultDateFormat
+ },{
+ name: 'openRemark',
+ label: 'Open Remark',
+ maxWidth: 500,
+ attrs: {
+ style: 'white-space: normal !important;"'
+ }
+ }]);
+ },
+
+ appendParentageResults: function(toSet, results){
+ if (results){
+ var parentMap = {};
+ Ext4.each(results, function(row){
+ var parent = row.parent;
+ var relationship = row.relationship;
+
+ if (parent && relationship){
+ var text = LABKEY.Utils.encodeHtml(relationship + ' - ' + parent);
+
+ if (!parentMap[text])
+ parentMap[text] = [];
+
+ var method = row.method;
+ if (method){
+ parentMap[text].push(LABKEY.Utils.encodeHtml(method));
+ }
+ }
+ }, this);
+
+ var values = [];
+ Ext4.Array.forEach(Ext4.Object.getKeys(parentMap).sort(), function(text){
+ parentMap[text] = Ext4.unique(parentMap[text]);
+ var subject = text;
+ var textParts = text.split(' - ');
+ if (textParts.length > 1){
+ subject = textParts[1];
+ }
+
+ values.push('' + encodeURIComponent(subject) + '');
+ }, this);
+
+ if (values.length)
+ toSet['parents'] = values.join(' ');
+ }
+ else {
+ toSet['parents'] = 'No data';
+ }
+ },
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/plugin/RowEditor.js b/nbri_ehr/resources/web/nbri_ehr/plugin/RowEditor.js
new file mode 100644
index 0000000..2c67e78
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/plugin/RowEditor.js
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+Ext4.define('NBRI_EHR.plugin.RowEditor', {
+ extend: 'EHR.plugin.RowEditor',
+
+ getDetailsPanelCfg: function () {
+ return {
+ xtype: 'nbri_ehr-animaldetailspanel',
+ itemId: 'detailsPanel',
+ showDisableButton: false
+ }
+ },
+
+ getWindowCfg: function(){
+ return {
+ modal: true,
+ width: 1000,
+ border: false,
+ items: [{
+ items: [this.getDetailsPanelCfg(), this.getFormPanelCfg()]
+ }],
+ buttons: this.getWindowButtons(),
+ closeAction: 'destroy',
+ listeners: {
+ scope: this,
+ close: this.onWindowClose,
+ destroy: this.onWindowClose,
+ beforerender: function(win){
+ var cols = win.down('#formPanel').items.get(0).items.getCount();
+ if (cols > 1){
+ var newWidth = cols * (EHR.form.Panel.defaultFieldWidth + 20);
+ if (newWidth > win.width) {
+ win.setWidth(newWidth);
+ }
+ }
+ },
+ afterrender: function(editorWin){
+ this.keyNav = new Ext4.util.KeyNav({
+ target: editorWin.getId(),
+ scope: this,
+ up: function(e){
+ if (e.ctrlKey){
+ this.loadPreviousRecord();
+ }
+ },
+ down: function (e){
+ if (e.ctrlKey){
+ this.loadNextRecord();
+ }
+ }
+ });
+ },
+ animalchange: {
+ fn: function(id){
+ this.getEditorWindow().down('#detailsPanel').loadAnimal(id);
+ },
+ scope: this,
+ buffer: 200
+ }
+ }
+ }
+ },
+})
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/utils.js b/nbri_ehr/resources/web/nbri_ehr/utils.js
new file mode 100644
index 0000000..66751c0
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/utils.js
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+NBRI_EHR.Utils = new function () {
+ return {
+
+ showFlagPopup: function (id, el) {
+ var ctx = EHR.Utils.getEHRContext() || {};
+ Ext4.create('Ext.window.Window', {
+ title: 'Flag Details: ' + id,
+ width: 880,
+ modal: true,
+ bodyStyle: 'padding: 5px;',
+ items: [{
+ xtype: 'grid',
+ cls: 'ldk-grid', //variable row height
+ border: false,
+ store: {
+ type: 'labkey-store',
+ containerPath: ctx['EHRStudyContainer'],
+ schemaName: 'study',
+ queryName: 'flags',
+ columns: 'Id,date,enddate,flag/category,flag/value,remark,performedby/displayName',
+ filterArray: [LABKEY.Filter.create('Id', id), LABKEY.Filter.create('isActive', true)],
+ autoLoad: true
+ },
+ viewConfig: {
+ //loadMask: !(Ext4.isIE && Ext4.ieVersion <= 8)
+ },
+ columns: [{
+ header: 'Category',
+ dataIndex: 'flag/category',
+ width: 160
+ }, {
+ header: 'Meaning',
+ dataIndex: 'flag/value',
+ tdCls: 'ldk-wrap-text',
+ width: 160
+ }, {
+ header: 'Date Added',
+ dataIndex: 'date',
+ xtype: 'datecolumn',
+ format: LABKEY.extDefaultDateFormat,
+ width: 110
+ }, {
+ header: 'Date Removed',
+ dataIndex: 'enddate',
+ xtype: 'datecolumn',
+ format: LABKEY.extDefaultDateFormat,
+ width: 110
+ }, {
+ header: 'Remark',
+ dataIndex: 'remark',
+ tdCls: 'ldk-wrap-text',
+ width: 210
+ }, {
+ header: 'Performed By',
+ dataIndex: 'performedby/displayName',
+ width: 110
+ }],
+ border: false
+ }],
+ buttons: [{
+ text: 'Close',
+ handler: function (btn) {
+ btn.up('window').close();
+ }
+ }]
+ }).show(el);
+ },
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/window/AddAnimalsWindow.js b/nbri_ehr/resources/web/nbri_ehr/window/AddAnimalsWindow.js
new file mode 100644
index 0000000..4a0e215
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/window/AddAnimalsWindow.js
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+Ext4.define('NBRI_EHR.window.AddAnimalsWindow', {
+ extend: 'EHR.window.AddAnimalsWindow',
+ upperCaseAnimalId: true,
+
+ initComponent: function(){
+ this.callParent(arguments);
+ },
+
+ locationHandler: function(){
+ var form = this.resetFormFields(true);
+ form.add([{
+ html: 'This will return any animals currently housed in the selected location. You can leave any of the fields blank.',
+ style: 'padding-bottom: 10px;'
+ },{
+ xtype: 'nbri_ehr-locationField',
+ itemId: 'cageField',
+ fieldLabel: 'Cage(s)'
+ }]);
+
+ form.getAnimals = function(){
+
+ var cage = this.down('#cageField').getValue();
+
+ var filterArray = this.getBaseFilterArray().concat([LABKEY.Filter.create('isActive', true, LABKEY.Filter.Types.EQUAL)]);
+
+ if (cage)
+ filterArray.push(LABKEY.Filter.create('cage/rowid', cage, LABKEY.Filter.Types.EQUALS_ONE_OF));
+
+ this.doQuery({
+ schemaName: 'study',
+ queryName: 'housing',
+ sort: 'room,cage,Id',
+ filterArray: filterArray
+ });
+ }
+ },
+
+ projectHandler: function(){
+ var form = this.resetFormFields(true);
+ form.add([{
+ html: 'This will return any animals currently assigned to the selected project or protocol. Choose one or the other.',
+ style: 'padding-bottom: 10px;'
+ },{
+ xtype: 'ehr-projectfield',
+ emptyText: '',
+ itemId: 'projectField',
+ width: 400,
+ labelWidth: 140,
+ onlyIncludeProjectsWithAssignments: true
+ },{
+ xtype: 'ehr-protocolfield',
+ emptyText: '',
+ itemId: 'protocolField',
+ width: 400,
+ labelWidth: 140,
+ onlyIncludeProtocolsWithAssignments: true
+ }]);
+
+ form.getAnimals = function(){
+ var projectId = this.down('#projectField').getValue();
+ var protocol = this.down('#protocolField').getValue();
+ if (!projectId && !protocol){
+ Ext4.Msg.alert('Error', 'Must choose a project or protocol');
+ return;
+ }
+
+ if (projectId && protocol){
+ Ext4.Msg.alert('Error', 'Cannot pick both a project and protocol');
+ return;
+ }
+
+ var filterArray = this.getBaseFilterArray().concat([LABKEY.Filter.create('isActive', true, LABKEY.Filter.Types.EQUAL)]);
+
+ let queryName;
+ if (projectId) {
+ filterArray.push(LABKEY.Filter.create('project', projectId, LABKEY.Filter.Types.EQUAL));
+ queryName = 'assignment';
+ }
+ else if (protocol) {
+ filterArray.push(LABKEY.Filter.create('protocol', protocol, LABKEY.Filter.Types.EQUAL));
+ queryName = 'protocolAssignment';
+ }
+
+ this.doQuery({
+ schemaName: 'study',
+ queryName: queryName,
+ filterArray: filterArray
+ });
+ }
+ },
+});
+
+EHR.DataEntryUtils.registerGridButton('NBRI_ADDANIMALS', function(config){
+ return Ext4.Object.merge({
+ text: 'Add Batch',
+ tooltip: EHR.DataEntryUtils.shouldShowTooltips() ? 'Click to add a batch of animals, either as a list or by location' : undefined,
+ handler: function(btn){
+ var grid = btn.up('gridpanel');
+
+ Ext4.create('NBRI_EHR.window.AddAnimalsWindow', {
+ targetStore: grid.store,
+ formConfig: grid.formConfig
+ }).show();
+ }
+ }, config);
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/window/CopyFromSectionWindow.js b/nbri_ehr/resources/web/nbri_ehr/window/CopyFromSectionWindow.js
new file mode 100644
index 0000000..4476d28
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/window/CopyFromSectionWindow.js
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+/**
+ * This is similar to the EHR CopyFromSectionWindow with the following exceptions. Project and performedBy columns are
+ * removed. Date is a date field, not a datetime field.
+ *
+ * @cfg targetGrid
+ * @cfg parentStore
+ * @cfg sourceLabel
+ */
+Ext4.define('NBRI_EHR.window.CopyFromSectionWindow', {
+ extend: 'EHR.window.CopyFromSectionWindow',
+
+ getInitialItems: function(){
+ var items = [{
+ html: 'Animal'
+ },{
+ html: 'Date'
+ },{
+ html: 'Performed By'
+ },{
+ html: 'Skip?'
+ }];
+
+ var keys = {}, key;
+ var keyFields = ['Id'];
+
+ //console.log(keyFields);
+ var orderedKeys = [];
+ Ext4.Array.forEach(this.parentRecords, function(record){
+ key = this.getKeyValue(record, keyFields);
+ if (orderedKeys.indexOf(key) == -1){
+ orderedKeys.push(key);
+ }
+
+ keys[key] = keys[key] || {
+ Id: record.get('Id'),
+ performedby: [],
+ dates: [],
+ total: 0
+ };
+
+ keys[key].total++;
+ if (record.fields.get('performedby') && record.get('performedby'))
+ keys[key].performedby.push(record.get('performedby'));
+ keys[key].dates.push(record.get('date'))
+ }, this);
+
+ var existingIds = this.getExistingIds(keyFields);
+ Ext4.Array.forEach(orderedKeys, function(key){
+ var o = keys[key];
+
+ items.push({
+ xtype: 'displayfield',
+ key: key,
+ value: o.Id,
+ fieldName: 'Id'
+ });
+
+ var dates = [];
+ var minDate;
+ Ext4.Array.forEach(o.dates, function(date){
+ if (!minDate || date < minDate)
+ minDate = date;
+
+ dates.push(Ext4.Date.format(date, LABKEY.extDefaultDateTimeFormat));
+ }, this);
+
+ o.performedby = Ext4.unique(o.performedby);
+ var performedby = o.performedby.length == 1 ? o.performedby[0] : null;
+
+ items.push({
+ xtype: 'xdatetime',
+ width: 300,
+ format: LABKEY.extDefaultDateTimeFormat,
+ timeFormat: 'H:i',
+ fieldName: 'date',
+ key: key,
+ value: minDate
+ });
+
+ items.push({
+ xtype: 'textfield',
+ width: 200,
+ fieldName: 'performedby',
+ key: key,
+ value: performedby
+ });
+
+ items.push({
+ xtype: 'checkbox',
+ key: key,
+ fieldName: 'exclude',
+ checked: existingIds[key]
+ });
+ }, this);
+
+ return [{
+ itemId: 'theTable',
+ border: false,
+ layout: {
+ type: 'table',
+ columns: 4
+ },
+ defaults: {
+ border: false,
+ style: 'margin: 5px;'
+ },
+ items: items
+ }]
+ }
+});
+
+
+EHR.DataEntryUtils.registerGridButton('NBRICOPYFROMSECTION', function(config){
+ return Ext4.Object.merge({
+ text: 'Copy From Section',
+ xtype: 'button',
+ tooltip: EHR.DataEntryUtils.shouldShowTooltips() ? 'Click to copy records from one of the other sections' : undefined,
+ listeners: {
+ beforerender: function(btn){
+ var grid = btn.up('gridpanel');
+ LDK.Assert.assertNotEmpty('Unable to find gridpanel in NBRICOPYFROMSECTION button', grid);
+
+ btn.grid = grid;
+
+ btn.appendButtons.call(btn);
+ }
+ },
+ menu: {
+ xtype: 'menu',
+ items: [{
+ text: 'Loading...'
+ }]
+ },
+ appendButtons: function(){
+ this.dataEntryPanel = this.grid.up('ehr-dataentrypanel');
+ LDK.Assert.assertNotEmpty('Unable to find dataEntryPanel in NBRICOPYFROMSECTION button', this.dataEntryPanel);
+
+ var toAdd = [];
+ Ext4.Array.forEach(this.dataEntryPanel.formConfig.sections, function(section){
+ if (section.name == this.grid.formConfig.name){
+ return;
+ }
+
+ var store = this.dataEntryPanel.storeCollection.getClientStoreByName(section.name);
+ if (store){
+ //only allow copying from sections with an ID field
+ if (!store.getFields().get('Id')){
+ return;
+ }
+
+ toAdd.push({
+ text: section.label,
+ scope: this,
+ handler: function(menu){
+ Ext4.create('NBRI_EHR.window.CopyFromSectionWindow', {
+ targetGrid: this.grid,
+ sourceLabel: section.label,
+ parentStore: store
+ }).show();
+ }
+ });
+ }
+ }, this);
+
+ this.menu.removeAll();
+ if (toAdd.length){
+ this.menu.add(toAdd);
+ }
+ else {
+ this.menu.add({
+ text: 'There are no other sections'
+ })
+ }
+ }
+ });
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/window/DrugAmountWindow.js b/nbri_ehr/resources/web/nbri_ehr/window/DrugAmountWindow.js
new file mode 100644
index 0000000..9a1030b
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/window/DrugAmountWindow.js
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+(function getSnomedStore(){
+ if (NBRI_EHR._snomedStore)
+ return NBRI_EHR._snomedStore;
+
+ let storeId = ['ehr_lookups', 'snomed', 'code', 'meaning'].join('||');
+
+ NBRI_EHR._snomedStore = Ext4.create('LABKEY.ext4.data.Store', {
+ type: 'labkey-store',
+ schemaName: 'ehr_lookups',
+ queryName: 'snomed',
+ columns: 'code,meaning',
+ sort: 'meaning',
+ storeId: storeId,
+ autoLoad: true,
+ getRecordForCode: function(code){
+ let recIdx = this.findExact('code', code);
+ if (recIdx != -1){
+ return this.getAt(recIdx);
+ }
+ }
+ });
+
+ return NBRI_EHR._snomedStore;
+})();
+
+Ext4.define('NBRI_EHR.window.DrugAmountWindow', {
+ extend: 'EHR.window.DrugAmountWindow',
+
+ initComponent: function(){
+ this.callParent(arguments);
+ },
+
+ getDrugItems: function(){
+
+ var numCols = 13;
+ var items = [{
+ html: 'Animal'
+ },{
+ html: 'Drug'
+ },{
+ html: 'Weight (kg)',
+ width: 100
+ },{
+ html: 'Conc'
+ },{
+ html: 'Units'
+ },{
+ html: 'Dosage'
+ },{
+ html: 'Units'
+ },{
+ html: 'Vol'
+ },{
+ html: 'Units'
+ },{
+ html: 'Amount'
+ },{
+ html: 'Units'
+ },{
+ html: 'Auto Calc?'
+ },{
+ html: '' //placeholder for messages
+ }];
+
+ var fields = [{
+ name: 'concentration',
+ width: 70
+ },{
+ name: 'conc_units',
+ width: 80
+ },{
+ name: 'dosage',
+ width: 70
+ },{
+ name: 'dosage_units',
+ width: 80
+ },{
+ name: 'volume',
+ width: 70
+ },{
+ name: 'vol_units',
+ width: 80
+ },{
+ name: 'amount',
+ width: 70
+ },{
+ name: 'amount_units',
+ width: 80
+ }];
+
+ this.snomedStore = NBRI_EHR._snomedStore;
+ LDK.Assert.assertTrue('SNOMED store is not done loading', !this.snomedStore.isLoading());
+
+ Ext4.Array.forEach(this.getTargetRecords(), function(record, recordIdx){
+ if (!record.get('Id') || !record.get('code')){
+ return;
+ }
+
+ items.push({
+ xtype: 'displayfield',
+ value: record.get('Id'),
+ record: record,
+ recordIdx: recordIdx,
+ fieldName: 'Id'
+ });
+
+ var snomedRec = this.snomedStore.getRecordForCode(record.get('code'));
+ items.push({
+ xtype: 'displayfield',
+ fieldName: 'code',
+ snomedCode: record.get('code'),
+ recordIdx: recordIdx,
+ value: snomedRec ? snomedRec.get('meaning') : record.get('code')
+ });
+
+ items.push({
+ xtype: 'ldk-numberfield',
+ hideTrigger: true,
+ decimalPrecision: 3,
+ keyNavEnabled: false,
+ width: 80,
+ fieldName: 'weight',
+ recordIdx: recordIdx,
+ animalId: record.get('Id'),
+ value: this.weights[record.get('Id')] ? this.weights[record.get('Id')].weight : null,
+ listeners: {
+ scope: this,
+ change: this.onFieldChange
+ }
+ });
+
+ Ext4.Array.forEach(fields, function(fieldObj){
+ var fieldName = fieldObj.name;
+ var editor, found = false;
+ Ext4.each(this.formConfig.fieldConfigs, function(field, idx){
+ if (fieldName == field.name){
+ var cfg = Ext4.apply({}, field);
+ cfg = EHR.model.DefaultClientModel.getFieldConfig(cfg, this.formConfig.configSources);
+
+ editor = LABKEY.ext4.Util.getGridEditorConfig(cfg);
+ if (cfg.jsonType != 'string'){
+ editor.xtype = 'ldk-numberfield';
+ editor.hideTrigger = true;
+ }
+
+ found = true;
+
+ return false;
+ }
+ }, this);
+
+ LDK.Assert.assertTrue('Unable to find target field in DrugAmountWindow: ' + fieldName, found);
+
+ if (editor){
+ editor.width = fieldObj.width;
+ editor.value = record.get(fieldName);
+ editor.fieldName = fieldName;
+ editor.recordIdx = recordIdx;
+ editor.animalId = record.get('Id');
+ if (editor.xtype == 'ldk-numberfield'){
+ editor.hideTrigger = true;
+ }
+ editor.listeners = editor.listeners || {};
+ editor.listeners.scope = this;
+ editor.listeners.change = this.onFieldChange;
+
+ items.push(editor);
+ }
+ else {
+ items.push({
+ html: ''
+ });
+ }
+ }, this);
+
+ items.push({
+ xtype: 'checkbox',
+ recordIdx: recordIdx,
+ fieldName: 'include',
+ checked: true
+ });
+
+ items.push({
+ xtype: 'displayfield',
+ width: 80,
+ recordIdx: recordIdx,
+ fieldName: 'messages'
+ });
+ }, this);
+
+ return [{
+ html: 'This tab shows one row per drug, allowing you to review and re-calculate amount/volume for weight-based drugs. It will pre-populate doses based on the formulary. Any drug using kg in the dosage will have the option to auto-calculate dose. To exclude a given drug from auto-calculation, check the box to the right. Use the \'Recalculate\' button in the bottom-right to recalculate values.',
+ border: false,
+ style: 'padding-bottom: 10px;'
+ },{
+ border: false,
+ itemId: 'theTable',
+ layout: {
+ type: 'table',
+ columns: numCols
+ },
+ defaults: {
+ border: false,
+ style: 'margin-left: 5px;margin-right: 5px;'
+ },
+ items: items
+ },{
+ layout: {
+ type: 'vbox',
+ align: 'right'
+ },
+ border: false,
+ style: 'margin-bottom: 5px;margin-top: 5px;margin-right: 110px;',
+ items: [{
+ xtype: 'button',
+ text: 'Recalculate All',
+ border: true,
+ itemId: 'recalculate',
+ menu: [{
+ text: 'Recalculate Both Amount/Volume',
+ scope: this,
+ handler: function(btn){
+ var panel = this.down('#drugTab');
+ var cbs = panel.query('checkbox');
+ for (var i=0;i');
+ }
+ }
+ }]
+ }
+ },
+
+ statics: {
+ showRecentCases: function(animalId){
+ Ext4.create('NBRI_EHR.window.RecentCasesWindow', {
+ animalId: animalId
+ }).show();
+ }
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/window/NBRIRecentRemarksWindow.js b/nbri_ehr/resources/web/nbri_ehr/window/NBRIRecentRemarksWindow.js
new file mode 100644
index 0000000..0e3f295
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/window/NBRIRecentRemarksWindow.js
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+Ext4.define('NBRI_EHR.window.RecentRemarksWindow', {
+ extend: 'Ext.window.Window',
+
+ initComponent: function(){
+ Ext4.apply(this, {
+ modal: true,
+ width: 1000,
+ closeAction: 'destroy',
+ minHeight: 400,
+ bodyStyle: 'padding: 5px;',
+ title: 'Recent Remarks',
+ items: [{
+ html: 'The grid below will show any vet and behavior remarks and observations entered in the previous 5 days for this animal',
+ style: 'padding-bottom: 10px;',
+ border: false
+ }, this.getGridConfig()],
+ buttons: [{
+ text: 'Close',
+ handler: function(btn){
+ btn.up('window').close();
+ }
+ }]
+ });
+
+ this.callParent(arguments);
+ },
+
+ getStore: function(){
+ if (this.store)
+ return this.store;
+
+ this.store = Ext4.create('LABKEY.ext4.data.Store', {
+ schemaName: 'study',
+ queryName: 'clinremarks',
+ columns: 'Id,date,category,s,o,a,p,remark,performedBy',
+ sort: '-date',
+ filterArray: [
+ LABKEY.Filter.create('Id', this.animalId, LABKEY.Filter.Types.EQUAL),
+ LABKEY.Filter.create('date', (Ext4.Date.add(new Date(), Ext4.Date.DAY, -5)), LABKEY.Filter.Types.DATE_GREATER_THAN_OR_EQUAL)
+ ],
+ autoLoad: true
+ });
+
+ return this.store;
+ },
+
+ getGridConfig: function(){
+ return {
+ xtype: 'grid',
+ maxHeight: 400,
+ border: true,
+ store: this.getStore(),
+ viewConfig: {
+ loadMask: !(Ext4.isIE && Ext4.ieVersion <= 8)
+ },
+ columns: [{
+ header: 'Date',
+ xtype: 'datecolumn',
+ width: 160,
+ format: LABKEY.extDefaultDateTimeFormat,
+ dataIndex: 'date'
+ },{
+ header: 'Subjective',
+ width: 200,
+ dataIndex: 's',
+ tdCls: 'ldk-wrap-text',
+ noWrap: false,
+ renderer: function(v){
+ if (v){
+ return v.replace(/\n/g, ' ');
+ }
+ }
+ },{
+ header: 'Objective',
+ width: 200,
+ dataIndex: 'o',
+ tdCls: 'ldk-wrap-text',
+ noWrap: false,
+ renderer: function(v){
+ if (v){
+ return v.replace(/\n/g, ' ');
+ }
+ }
+ },{
+ header: 'Assessment',
+ width: 200,
+ dataIndex: 'a',
+ tdCls: 'ldk-wrap-text',
+ noWrap: false,
+ renderer: function(v){
+ if (v){
+ return v.replace(/\n/g, ' ');
+ }
+ }
+ },{
+ header: 'Plan',
+ width: 200,
+ dataIndex: 'p',
+ tdCls: 'ldk-wrap-text',
+ noWrap: false,
+ renderer: function(v){
+ if (v){
+ return v.replace(/\n/g, ' ');
+ }
+ }
+ }]
+ }
+ },
+
+ statics: {
+ showRecentRemarks: function(animalId){
+ Ext4.create('NBRI_EHR.window.RecentRemarksWindow', {
+ animalId: animalId
+ }).show();
+ }
+ }
+});
\ No newline at end of file
diff --git a/nbri_ehr/resources/web/nbri_ehr/window/SelectCaseWindow.js b/nbri_ehr/resources/web/nbri_ehr/window/SelectCaseWindow.js
new file mode 100644
index 0000000..d3c2f74
--- /dev/null
+++ b/nbri_ehr/resources/web/nbri_ehr/window/SelectCaseWindow.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * @cfg {String} animalId
+ * @cfg {Object} panel
+ */
+Ext4.define('NBRI_EHR.window.SelectCaseWindow', {
+ extend: 'Ext.window.Window',
+
+ width: 1200,
+ minHeight: 50,
+
+ initComponent: function(){
+ Ext4.apply(this, {
+ title: 'Select Case: ' + this.animalId,
+ modal: true,
+ closeAction: 'destroy',
+ items: [{
+ xtype: 'nbri_ehr-selectcasepanel',
+ animalId: this.animalId,
+ hideButtons: true,
+ casepanel: this.casepanel
+ }],
+ buttons: this.getButtonConfig()
+ });
+
+ this.callParent(arguments);
+ },
+
+ getButtonConfig: function(){
+ var buttons = NBRI_EHR.panel.SelectCasePanel.getButtonConfig();
+ buttons.push({
+ text: 'Close',
+ handler: function(btn){
+ btn.up('window').close();
+ }
+ });
+
+ return buttons;
+ }
+});
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRController.java b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRController.java
new file mode 100644
index 0000000..b38bf4c
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRController.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.labkey.nbri_ehr;
+
+import org.labkey.api.action.SpringActionController;
+
+public class NBRI_EHRController extends SpringActionController
+{
+ private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(NBRI_EHRController.class);
+ public static final String NAME = "nbri_ehr";
+
+ public NBRI_EHRController()
+ {
+ setActionResolver(_actionResolver);
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRManager.java b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRManager.java
new file mode 100644
index 0000000..fabfb9f
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRManager.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr;
+
+import org.labkey.api.query.Queryable;
+
+import java.util.List;
+
+public class NBRI_EHRManager
+{
+ @Queryable
+ public static final String DAILY_CLINICAL_OBS_TITLE = "Daily Clinical Observations";
+ public static final List DAILY_CLINICAL_OBS = List.of("Activity", "Appetite", "BCS", "Hydration", "Stool", "Verified Id?");
+ public static final String SIB_OBS_TITLE = "SIB Observations";
+ public static final List SIB_OBS = List.of("Environmental Change", "Self Biting Observed", "Other Stereotopy", "New Injury Observed", "Special Enrichment", "Wound Status", "Wound Severity");
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java
new file mode 100644
index 0000000..4b262ad
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRModule.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.labkey.nbri_ehr;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.labkey.api.data.UpgradeCode;
+import org.labkey.api.ehr.EHRService;
+import org.labkey.api.ehr.SharedEHRUpgradeCode;
+import org.labkey.api.ehr.buttons.MarkCompletedButton;
+import org.labkey.api.ehr.dataentry.DefaultDataEntryFormFactory;
+import org.labkey.api.ehr.demographics.ParentsDemographicsProvider;
+import org.labkey.api.ehr.demographics.SourceDemographicsProvider;
+import org.labkey.api.ehr.history.DefaultAlopeciaDataSource;
+import org.labkey.api.ehr.history.DefaultAnimalRecordFlagDataSource;
+import org.labkey.api.ehr.history.DefaultClinicalRemarksDataSource;
+import org.labkey.api.ehr.history.DefaultNotesDataSource;
+import org.labkey.api.ehr.history.DefaultVitalsDataSource;
+import org.labkey.api.ehr.security.EHRDataAdminPermission;
+import org.labkey.api.ldk.ExtendedSimpleModule;
+import org.labkey.api.ldk.buttons.ShowEditUIButton;
+import org.labkey.api.ldk.notification.NotificationService;
+import org.labkey.api.module.Module;
+import org.labkey.api.module.ModuleContext;
+import org.labkey.api.query.DefaultSchema;
+import org.labkey.api.query.DetailsURL;
+import org.labkey.api.query.QuerySchema;
+import org.labkey.api.resource.Resource;
+import org.labkey.api.security.roles.RoleManager;
+import org.labkey.api.util.NetworkDrive;
+import org.labkey.api.view.WebPartFactory;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.form.*;
+import org.labkey.nbri_ehr.demographics.ActiveAssignmentsDemographicsProvider;
+import org.labkey.nbri_ehr.demographics.ActiveCasesDemographicsProvider;
+import org.labkey.nbri_ehr.demographics.ActiveFlagsDemographicsProvider;
+import org.labkey.nbri_ehr.demographics.ActiveTreatmentsDemographicsProvider;
+import org.labkey.nbri_ehr.demographics.CagematesDemographicsProvider;
+import org.labkey.nbri_ehr.demographics.HousingDemographicsProvider;
+import org.labkey.nbri_ehr.demographics.NecropsyStatusDemographicsProvider;
+import org.labkey.nbri_ehr.demographics.ProtocolAssignmentDemographicsProvider;
+import org.labkey.nbri_ehr.history.*;
+import org.labkey.nbri_ehr.notification.NBRIClinicalMoveNotification;
+import org.labkey.nbri_ehr.notification.NBRIDeathNotification;
+import org.labkey.nbri_ehr.notification.NBRIPregnancyOutcomeNotification;
+import org.labkey.nbri_ehr.notification.NBRIProcedureOverdueNotification;
+import org.labkey.nbri_ehr.query.NBRI_EHRUserSchema;
+import org.labkey.nbri_ehr.security.NBRIEHRVetTechRole;
+import org.labkey.nbri_ehr.table.NBRI_EHRCustomizer;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class NBRI_EHRModule extends ExtendedSimpleModule
+{
+ public static final String NAME = "NBRI_EHR";
+
+ @Override
+ public String getName()
+ {
+ return NAME;
+ }
+
+ @Override
+ public @Nullable Double getSchemaVersion()
+ {
+ return 26.000;
+ }
+
+ @Override
+ @NotNull
+ protected Collection createWebPartFactories()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ protected void init()
+ {
+ addController(NBRI_EHRController.NAME, NBRI_EHRController.class);
+
+ EHRService ehrService = EHRService.get();
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/nbriReports.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/NBRIRecentCasesWindow.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/NBRIRecentRemarksWindow.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("ehr/sharedReports.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/SnapshotPanel.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/NarrowSnapshotPanel.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/BloodSummaryPanel.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/AnimalDetailsPanel.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/NBRIClinicalHistoryWindow.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/NBRICaseHistoryWindow.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/ClinicalHistoryPanel.js"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/CaseHistoryPanel.js"), this);
+
+ }
+
+ @Override
+ protected void doStartupAfterSpringConfig(ModuleContext moduleContext)
+ {
+ EHRService ehrService = EHRService.get();
+ ehrService.registerModule(this);
+
+ Resource r = getModuleResource("/scripts/nbri_triggers.js");
+ assert r != null;
+ EHRService.get().registerTriggerScript(this, r);
+
+ EHRService.get().registerTableCustomizer(this, NBRI_EHRCustomizer.class);
+
+ ehrService.addModulePreferringTaskFormEditUI(this);
+
+ ehrService.registerDemographicsProvider(new ActiveFlagsDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new ParentsDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new ActiveAssignmentsDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new ProtocolAssignmentDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new HousingDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new CagematesDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new ActiveCasesDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new ActiveTreatmentsDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new SourceDemographicsProvider(this));
+ ehrService.registerDemographicsProvider(new NecropsyStatusDemographicsProvider(this));
+
+ EHRService.get().registerHistoryDataSource(new ArrivalDataSource(this));
+ EHRService.get().registerHistoryDataSource(new BiopsyDataSource(this));
+ EHRService.get().registerHistoryDataSource(new BirthDataSource(this));
+ EHRService.get().registerHistoryDataSource(new BloodDrawDataSource(this));
+ EHRService.get().registerHistoryDataSource(new BreederDataSource(this));
+ EHRService.get().registerHistoryDataSource(new DeathDataSource(this));
+ EHRService.get().registerHistoryDataSource(new DefaultAlopeciaDataSource(this));
+ EHRService.get().registerHistoryDataSource(new DefaultAnimalRecordFlagDataSource(this));
+ EHRService.get().registerHistoryDataSource(new DefaultClinicalRemarksDataSource(this));
+ EHRService.get().registerHistoryDataSource(new DefaultNotesDataSource(this));
+ EHRService.get().registerHistoryDataSource(new DefaultVitalsDataSource(this));
+ EHRService.get().registerHistoryDataSource(new DepartureDataSource(this));
+ EHRService.get().registerHistoryDataSource(new DrugAdminDataSource(this));
+ EHRService.get().registerHistoryDataSource(new FlagsDataSource(this));
+ EHRService.get().registerHistoryDataSource(new FosteringDataSource(this));
+ EHRService.get().registerHistoryDataSource(new ExemptionsDataSource(this));
+ EHRService.get().registerHistoryDataSource(new HistopathologyDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRICaseCloseDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRICaseOpenDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRIClinicalObservationsDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRIBehaviorObservationsDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRIClinicalRemarksDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRIEndTreatmentOrderDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRIHousingDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRIObservationOrdersDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRIVitalsDataSource(this));
+ EHRService.get().registerHistoryDataSource(new PairingsDataSource(this));
+ EHRService.get().registerHistoryDataSource(new PhysicalExamDataSource(this));
+ EHRService.get().registerHistoryDataSource(new PregnancyDataSource(this));
+ EHRService.get().registerHistoryDataSource(new ProceduresDataSource(this));
+ EHRService.get().registerHistoryDataSource(new NBRIProcedureOrdersDataSource(this));
+ EHRService.get().registerHistoryDataSource(new ProjectAssignmentDataSource(this));
+ EHRService.get().registerHistoryDataSource(new ProtocolDataSource(this));
+ EHRService.get().registerHistoryDataSource(new SerologyDataSource(this));
+
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/nbri_ehr_api"), this);
+ ehrService.registerClientDependency(ClientDependency.supplierFromPath("nbri_ehr/nbriOverrides.js"), this);
+ ehrService.registerActionOverride("animalHistory", this, "views/animalHistory.html");
+ ehrService.registerActionOverride("participantView", this, "views/participantView.html");
+ ehrService.registerActionOverride("enterData", this, "views/enterData.html");
+
+ ehrService.registerTriggerScriptOption("datasetsToCloseOnNewEntry", List.of("assignment", "protocolAssignment"));
+ RoleManager.registerRole(new NBRIEHRVetTechRole());
+
+ EHRService.get().registerMoreActionsButton(new ShowEditUIButton(this, "ehr", "observation_types", EHRDataAdminPermission.class), "ehr", "observation_types");
+
+ EHRService.get().registerMoreActionsButton(new MarkCompletedButton(this, "study", "observation_order", "Set End Date"), "study", "observation_order");
+ EHRService.get().registerMoreActionsButton(new MarkCompletedButton(this, "study", "flags", "Set End Date"), "study", "flags");
+
+ registerDataEntry();
+ NotificationService.get().registerNotification(new NBRIDeathNotification(this));
+ NotificationService.get().registerNotification(new NBRIClinicalMoveNotification(this));
+ NotificationService.get().registerNotification(new NBRIProcedureOverdueNotification(this));
+ NotificationService.get().registerNotification(new NBRIPregnancyOutcomeNotification(this));
+
+ EHRService.get().registerReportLink(EHRService.REPORT_LINK_TYPE.moreReports, "Printable Necropsy Report", this, DetailsURL.fromString("/nbri_ehr-necropsy.view"), "Pathology");
+ EHRService.get().registerReportLink(EHRService.REPORT_LINK_TYPE.moreReports, "Acquisition Report", this, DetailsURL.fromString("/nbri_ehr-acquisitionReport.view"), "Population Overview");
+
+
+ // Ensure N: is mounted if it's configured, as it's being mapped in via a symlink/shortcut, so we can't
+ // recognize paths using it based solely on their drive letter and mount just-in-time
+ if (NetworkDrive.getNetworkDrive("N:\\") != null)
+ {
+ NetworkDrive.exists(new File("N:\\"));
+ }
+ }
+
+ private void registerDataEntry()
+ {
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIAliasFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIAssignmentFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIArrivalFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIBirthFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIBulkClinicalFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIDepartureFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIDeathNecropsyFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIHousingFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIMedicationTreatmentFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIProjectFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIProtocolFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIPregnancyFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIWeightFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIFlagsFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIExemptionsFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRINotesFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRICasesFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIBehavioralCasesFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIBuildingFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIRoomFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIFloorFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRICageFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIClinicalObservationsFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIClinicalRoundsFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIAnimalTrainingFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIPairingsFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIBulkBehaviorFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIBehaviorRoundsFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIChemistryImportFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRISerologyImportFormType.class, this));
+ EHRService.get().registerFormType(new DefaultDataEntryFormFactory(NBRIRearrivalFormType.class, this));
+ }
+
+ @Override
+ protected void registerSchemas()
+ {
+ DefaultSchema.registerProvider(NBRI_EHRSchema.NAME, new DefaultSchema.SchemaProvider(this)
+ {
+ @Override
+ public @NotNull QuerySchema createSchema(DefaultSchema schema, Module module)
+ {
+ return new NBRI_EHRUserSchema(NBRI_EHRSchema.NAME, null, schema.getUser(), schema.getContainer(), NBRI_EHRSchema.getInstance().getSchema());
+ }
+ });
+ }
+
+ @Override
+ public @NotNull Collection getSchemaNames()
+ {
+ return Collections.singleton(NBRI_EHRSchema.NAME);
+ }
+
+ @Override
+ public @NotNull UpgradeCode getUpgradeCode()
+ {
+ return SharedEHRUpgradeCode.getInstance(this);
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRSchema.java b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRSchema.java
new file mode 100644
index 0000000..59eefe2
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/NBRI_EHRSchema.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr;
+
+import org.labkey.api.data.DbSchema;
+import org.labkey.api.data.DbSchemaType;
+import org.labkey.api.data.dialect.SqlDialect;
+
+public class NBRI_EHRSchema
+{
+ private static final NBRI_EHRSchema _instance = new NBRI_EHRSchema();
+ public static final String NAME = "nbri_ehr";
+
+ public static NBRI_EHRSchema getInstance()
+ {
+ return _instance;
+ }
+
+ private NBRI_EHRSchema()
+ {
+ // singleton accessed via org.labkey.nbri_ehr.NBRI_EHRSchema.getInstance()
+ }
+
+ public DbSchema getSchema()
+ {
+ return DbSchema.get(NAME, DbSchemaType.Module);
+ }
+
+ public SqlDialect getSqlDialect()
+ {
+ return getSchema().getSqlDialect();
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/buttons/MarkTreatmentCompletedButton.java b/nbri_ehr/src/org/labkey/nbri_ehr/buttons/MarkTreatmentCompletedButton.java
new file mode 100644
index 0000000..ac46c64
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/buttons/MarkTreatmentCompletedButton.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.buttons;
+
+import org.labkey.api.data.ColumnInfo;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.ehr.buttons.MarkCompletedButton;
+import org.labkey.api.ehr.security.EHRCompletedUpdatePermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.security.permissions.Permission;
+import org.labkey.api.util.PageFlowUtil;
+
+public class MarkTreatmentCompletedButton extends MarkCompletedButton
+{
+ public MarkTreatmentCompletedButton(Module owner, String schemaName, String queryName, String label)
+ {
+ this(owner, schemaName, queryName, label, EHRCompletedUpdatePermission.class, false);
+ }
+
+ public MarkTreatmentCompletedButton(Module owner, String schemaName, String queryName, String label, Class extends Permission> perm, boolean forceDateOnlyField)
+ {
+ super(owner, schemaName, queryName, label, perm, forceDateOnlyField);
+ }
+
+ @Override
+ protected String getJsHandler(TableInfo ti)
+ {
+ // NOTE: we have a problem if this is called before all XML metadata is applied. for example, a dataset could call the tablecustomizer from studyData.query.xml
+ // but the dataset-specific query.xml file will apply a different format to the enddate column. as a result, defer creating the JS handler as long as we can.
+ ColumnInfo col = ti.getColumn("enddate");
+ String xtype = "xdatetime";
+ if (!_forceDateOnlyField && col != null && col.getFormat() != null && col.getFormat().contains("HH"))
+ xtype = "xdatetime";
+
+ String pkColName = null;
+ if (ti.getPkColumnNames() != null && ti.getPkColumnNames().size() == 1)
+ {
+ pkColName = ti.getPkColumnNames().getFirst();
+ }
+
+ return "NBRI_EHR.window.MarkTreatmentCompletedWindow.buttonHandler(dataRegionName, " + PageFlowUtil.jsString(_schemaName) + ", " + PageFlowUtil.jsString(_queryName) + ", " + PageFlowUtil.jsString(xtype) + ", " + PageFlowUtil.jsString(pkColName) + ");";
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAliasFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAliasFormType.java
new file mode 100644
index 0000000..f1ed60b
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAliasFormType.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAliasFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.List;
+
+public class NBRIAliasFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Alias";
+ public static final String LABEL = "Alias";
+
+ public NBRIAliasFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Colony Management", List.of(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIAliasFormSection(LABEL)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Alias.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("Alias");
+ }
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAnimalTrainingFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAnimalTrainingFormType.java
new file mode 100644
index 0000000..7742b3a
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAnimalTrainingFormType.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.EHRService;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.module.Module;
+import org.labkey.nbri_ehr.dataentry.section.BaseFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.Arrays;
+
+public class NBRIAnimalTrainingFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Animal Training";
+
+ public NBRIAnimalTrainingFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, NAME, "Behavior", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new BaseFormSection("study", "nhpTraining", NAME, "ehr-gridpanel", EHRService.FORM_SECTION_LOCATION.Body, true, false)
+ ));
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIArrivalFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIArrivalFormType.java
new file mode 100644
index 0000000..b79b90b
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIArrivalFormType.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.dataentry.forms.LockAnimalsFormSection;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIArrivalFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIArrivalInstructionsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIWeightFormSection;
+
+import java.util.Arrays;
+
+public class NBRIArrivalFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "arrival";
+
+ public NBRIArrivalFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, "Arrivals", "Colony Management", Arrays.asList(
+ new LockAnimalsFormSection(),
+ new NBRIArrivalInstructionsFormSection(),
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIArrivalFormSection(),
+ new NBRIWeightFormSection(true, true)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Arrival.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("Arrival");
+ }
+
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAssignmentFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAssignmentFormType.java
new file mode 100644
index 0000000..000aa72
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIAssignmentFormType.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIProjectAssignmentFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIProtocolAssignmentFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.List;
+
+public class NBRIAssignmentFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Assignment";
+ public static final String LABEL = "Assignment";
+
+ public NBRIAssignmentFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Colony Management", List.of(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIProtocolAssignmentFormSection(true, true, true),
+ new NBRIProjectAssignmentFormSection(true, true, true)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Assignment.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("Assignment");
+ }
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBaseTaskFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBaseTaskFormType.java
new file mode 100644
index 0000000..f595a45
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBaseTaskFormType.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.dataentry.TaskForm;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class NBRIBaseTaskFormType extends TaskForm
+{
+ protected NBRIBaseTaskFormType(DataEntryFormContext ctx, Module owner, String name, String label, String category, List sections)
+ {
+ super(ctx, owner, name, label, category, sections);
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/plugin/RowEditor.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/NBRIDefault.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/saveDraftButton.js"));
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List defaultButtons = new ArrayList<>();
+ defaultButtons.add("NBRISAVEDRAFTBUTTON");
+ defaultButtons.add("REVIEW");
+ defaultButtons.add("SUBMIT");
+
+ return defaultButtons;
+ }
+
+ @Override
+ protected List getMoreActionButtonConfigs()
+ {
+ List configs = super.getMoreActionButtonConfigs();
+ configs.remove("REVIEW");
+ return configs;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBehaviorRoundsFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBehaviorRoundsFormType.java
new file mode 100644
index 0000000..b85e71a
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBehaviorRoundsFormType.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.AbstractFormSection;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.dataentry.SimpleFormSection;
+import org.labkey.api.ehr.security.EHRClinicalEntryPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.query.Queryable;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRICaseTemplateFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRICasesFormPanelSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalObservationsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentGivenFormSection;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class NBRIBehaviorRoundsFormType extends NBRIBaseTaskFormType
+{
+ @Queryable
+ public static final String NAME = "Behavioral Rounds";
+ public static final String LABEL = "Behavioral Rounds";
+
+ public NBRIBehaviorRoundsFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Behavior", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRICaseTemplateFormSection("Case Template", "Case Template", "nbri_ehr-casetemplatepanel", Arrays.asList(ClientDependency.supplierFromPath("nbri_ehr/panel/CaseTemplatePanel.js"))),
+ new NBRICasesFormPanelSection("Behavior Case", ctx, true),
+ new NBRIClinicalObservationsFormSection(true, "cases"),
+ new NBRITreatmentGivenFormSection(true, "cases")
+ ));
+
+ setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID);
+ setDisplayReviewRequired(true);
+
+ for (FormSection s : this.getFormSections())
+ {
+ s.addConfigSource("BehavioralCase");
+ s.addConfigSource("BehaviorDefaults");
+ s.addConfigSource("TreatmentSchedule");
+ s.addConfigSource("BehaviorRounds");
+ s.addConfigSource("MedicationEndDate");
+
+ if (s instanceof SimpleFormSection && !s.getName().equals("tasks"))
+ s.setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID);
+
+ if (s instanceof AbstractFormSection)
+ {
+ ((AbstractFormSection)s).setAllowBulkAdd(false);
+ }
+ }
+ setStoreCollectionClass("NBRI_EHR.data.CaseStoreCollection");
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/CaseStoreCollection.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/TreatmentSchedule.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/DrugVolumeField.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/DrugAmountWindow.js"));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/BehavioralCase.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/BehaviorDefaults.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/BehaviorRounds.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/MedicationEndDate.js"));
+ addClientDependency(ClientDependency.supplierFromPath("ehr/panel/ExamDataEntryPanel.js"));
+ setJavascriptClass("EHR.panel.ExamDataEntryPanel");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/NBRIExamCasesDataEntryPanel.js"));
+ setJavascriptClass("NBRI_EHR.panel.ExamCasesDataEntryPanel");
+
+ // Needed for case and scheduled date/time when navigating from treatment or observation schedule
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/treatmentSubmit.js"));
+ }
+
+ @Override
+ protected boolean canInsert()
+ {
+ if (!getCtx().getContainer().hasPermission(getCtx().getUser(), EHRClinicalEntryPermission.class))
+ return false;
+
+ return super.canInsert();
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List ret = super.getButtonConfigs();
+
+ ret.remove("SUBMIT");
+ ret.add("NBRI_TREATMENT_SUBMIT");
+
+ return ret;
+ }
+
+ @Override
+ protected List getMoreActionButtonConfigs()
+ {
+ List configs = super.getMoreActionButtonConfigs();
+ configs.remove("DISCARD");
+ return configs;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBehavioralCasesFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBehavioralCasesFormType.java
new file mode 100644
index 0000000..3bcc93a
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBehavioralCasesFormType.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.AbstractFormSection;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.dataentry.SimpleFormSection;
+import org.labkey.api.ehr.security.EHRBehaviorEntryPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.query.Queryable;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRICaseTemplateFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRICasesFormPanelSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalObservationsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalRemarksFormPanelSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIObservationOrdersFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentGivenFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentOrderFormSection;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class NBRIBehavioralCasesFormType extends NBRIBaseTaskFormType
+{
+ @Queryable
+ public static final String NAME = "Behavioral Cases";
+ public static final String LABEL = "Behavioral Cases";
+
+ public NBRIBehavioralCasesFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Behavior", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRICaseTemplateFormSection("Case Template", "Case Template", "nbri_ehr-casetemplatepanel", Arrays.asList(ClientDependency.supplierFromPath("nbri_ehr/panel/CaseTemplatePanel.js"))),
+ new NBRICasesFormPanelSection("Behavior Case", ctx, true),
+ new NBRIClinicalRemarksFormPanelSection(true, "cases", "Behavior Assessment", ctx, true),
+ new NBRIClinicalObservationsFormSection(true, "cases"),
+ new NBRIObservationOrdersFormSection(null, true, "cases"),
+ new NBRITreatmentGivenFormSection(true, "cases"),
+ new NBRITreatmentOrderFormSection(true, "cases")
+ ));
+
+ setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID);
+ setDisplayReviewRequired(true);
+
+ for (FormSection s : this.getFormSections())
+ {
+ s.addConfigSource("BehavioralCase");
+ s.addConfigSource("BehaviorDefaults");
+ s.addConfigSource("TreatmentSchedule");
+ s.addConfigSource("MedicationEndDate");
+
+ if (s instanceof SimpleFormSection && !s.getName().equals("tasks"))
+ s.setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID);
+
+ if (s instanceof AbstractFormSection)
+ {
+ ((AbstractFormSection)s).setAllowBulkAdd(false);
+ }
+ }
+ setStoreCollectionClass("NBRI_EHR.data.CaseStoreCollection");
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/CaseStoreCollection.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/TreatmentSchedule.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/DrugVolumeField.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/DrugAmountWindow.js"));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/BehavioralCase.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/BehaviorDefaults.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/MedicationEndDate.js"));
+ addClientDependency(ClientDependency.supplierFromPath("ehr/panel/ExamDataEntryPanel.js"));
+ setJavascriptClass("EHR.panel.ExamDataEntryPanel");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/NBRIExamCasesDataEntryPanel.js"));
+ setJavascriptClass("NBRI_EHR.panel.ExamCasesDataEntryPanel");
+
+ // Needed for case and scheduled date/time when navigating from treatment or observation schedule
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/treatmentSubmit.js"));
+ }
+
+ @Override
+ protected boolean canInsert()
+ {
+ if (!getCtx().getContainer().hasPermission(getCtx().getUser(), EHRBehaviorEntryPermission.class))
+ return false;
+
+ return super.canInsert();
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List ret = super.getButtonConfigs();
+
+ ret.remove("SUBMIT");
+ ret.add("NBRI_TREATMENT_SUBMIT");
+
+ return ret;
+ }
+
+ @Override
+ protected List getMoreActionButtonConfigs()
+ {
+ List configs = super.getMoreActionButtonConfigs();
+ configs.remove("DISCARD");
+ return configs;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBirthFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBirthFormType.java
new file mode 100644
index 0000000..ebe9c2b
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBirthFormType.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.dataentry.forms.BirthFormType;
+import org.labkey.api.ehr.dataentry.forms.LockAnimalsFormSection;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIBirthFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIBirthInstructionsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class NBRIBirthFormType extends BirthFormType
+{
+ public NBRIBirthFormType (DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, Arrays.asList(
+ new LockAnimalsFormSection(),
+ new NBRIBirthInstructionsFormSection(),
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIBirthFormSection()
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/plugin/RowEditor.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/NBRIDefault.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Birth.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/AddAnimalsWindow.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("Birth");
+ }
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List defaultButtons = new ArrayList<>();
+ defaultButtons.add("SAVEDRAFT");
+ defaultButtons.add("BIRTHARRIVALREVIEW");
+ defaultButtons.add("BIRTHARRIVALFINAL");
+
+ return defaultButtons;
+ }
+
+ @Override
+ protected List getMoreActionButtonConfigs()
+ {
+ List configs = super.getMoreActionButtonConfigs();
+ configs.remove("REVIEW");
+ configs.remove("DISCARD");
+ return configs;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBuildingFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBuildingFormType.java
new file mode 100644
index 0000000..2628766
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBuildingFormType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
+import org.labkey.api.module.Module;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.ActionURL;
+
+import java.util.ArrayList;
+
+public class NBRIBuildingFormType extends AdminLinksFormType
+{
+ public NBRIBuildingFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, "Building", "Building", "Locations", new ArrayList<>());
+ }
+
+ @Override
+ protected ActionURL dataEntryLink()
+ {
+ ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
+ url.addParameter("schemaName", "ehr_lookups");
+ url.addParameter("query.queryName", "buildings");
+ url.addParameter("showImport", "true");
+ return url;
+ }
+
+ @Override
+ public boolean isAvailable()
+ {
+ return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBulkBehaviorFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBulkBehaviorFormType.java
new file mode 100644
index 0000000..35c87c0
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBulkBehaviorFormType.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.security.EHRVeterinarianPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalObservationsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalRemarksFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIObservationOrdersFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentGivenFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentOrderFormSection;
+import org.labkey.nbri_ehr.security.NBRIEHRVetTechPermission;
+
+import java.util.List;
+
+public class NBRIBulkBehaviorFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Bulk Behavior Entry";
+ public static final String LABEL = "Bulk Behavior Entry";
+
+ public NBRIBulkBehaviorFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Behavior", List.of(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIClinicalRemarksFormSection("Behavior Assessment", ctx.getContainer().hasPermission(ctx.getUser(), NBRIEHRVetTechPermission.class),
+ ctx.getContainer().hasPermission(ctx.getUser(), EHRVeterinarianPermission.class),
+ ctx.getContainer().hasPermission(ctx.getUser(), AdminPermission.class)),
+ new NBRIClinicalObservationsFormSection(false, null),
+ new NBRIObservationOrdersFormSection("NBRI_DAILY_CLINICAL_OBS_ORDERS", false, null),
+ new NBRITreatmentGivenFormSection(),
+ new NBRITreatmentOrderFormSection()
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/BehaviorDefaults.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/TreatmentSchedule.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/DrugVolumeField.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/DrugAmountWindow.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/MedicationEndDate.js"));
+
+ // Needed for scheduled date/time when navigating from treatment or observation schedule
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/treatmentSubmit.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("BehaviorDefaults");
+ s.addConfigSource("TreatmentSchedule");
+ s.addConfigSource("MedicationEndDate");
+ }
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List ret = super.getButtonConfigs();
+
+ ret.remove("SUBMIT");
+ ret.add("NBRI_TREATMENT_SUBMIT");
+
+ return ret;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBulkClinicalFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBulkClinicalFormType.java
new file mode 100644
index 0000000..a4317ff
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIBulkClinicalFormType.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.security.EHRVeterinarianPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIBloodDrawFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalObservationsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalRemarksFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIObservationOrdersFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIProcedureFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIProcedureOrderFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentGivenFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentOrderFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIVitalsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIWeightFormSection;
+import org.labkey.nbri_ehr.security.NBRIEHRVetTechPermission;
+
+import java.util.List;
+
+public class NBRIBulkClinicalFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Bulk Clinical Entry";
+ public static final String LABEL = "Bulk Clinical Entry";
+
+ public NBRIBulkClinicalFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Clinical", List.of(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIClinicalRemarksFormSection(ctx.getContainer().hasPermission(ctx.getUser(), NBRIEHRVetTechPermission.class),
+ ctx.getContainer().hasPermission(ctx.getUser(), EHRVeterinarianPermission.class),
+ ctx.getContainer().hasPermission(ctx.getUser(), AdminPermission.class)),
+ new NBRIWeightFormSection(true, true),
+ new NBRIClinicalObservationsFormSection(false, null),
+ new NBRIObservationOrdersFormSection("NBRI_DAILY_CLINICAL_OBS_ORDERS", false, null),
+ new NBRIProcedureFormSection(),
+ new NBRIProcedureOrderFormSection(),
+ new NBRITreatmentGivenFormSection(),
+ new NBRITreatmentOrderFormSection(),
+ new NBRIVitalsFormSection(),
+ new NBRIBloodDrawFormSection(true, true, true)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ClinicalDefaults.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/BulkClinical.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/TreatmentSchedule.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/MedicationEndDate.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/DrugVolumeField.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/DrugAmountWindow.js"));
+
+ // Needed for case and scheduled date/time when navigating from treatment or observation schedule
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/treatmentSubmit.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("ClinicalDefaults");
+ s.addConfigSource("BulkClinical");
+ s.addConfigSource("TreatmentSchedule");
+ s.addConfigSource("MedicationEndDate");
+ }
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List ret = super.getButtonConfigs();
+
+ ret.remove("SUBMIT");
+ ret.add("NBRI_TREATMENT_SUBMIT");
+
+ return ret;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICageFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICageFormType.java
new file mode 100644
index 0000000..eb7ed4e
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICageFormType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
+import org.labkey.api.module.Module;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.ActionURL;
+
+import java.util.ArrayList;
+
+public class NBRICageFormType extends AdminLinksFormType
+{
+ public NBRICageFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, "Cage", "Cage", "Locations", new ArrayList<>());
+ }
+
+ @Override
+ protected ActionURL dataEntryLink()
+ {
+ ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
+ url.addParameter("schemaName", "ehr_lookups");
+ url.addParameter("query.queryName", "cage");
+ url.addParameter("showImport", "true");
+ return url;
+ }
+
+ @Override
+ public boolean isAvailable()
+ {
+ return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICasesFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICasesFormType.java
new file mode 100644
index 0000000..04c3292
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRICasesFormType.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.AbstractFormSection;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.dataentry.SimpleFormSection;
+import org.labkey.api.ehr.security.EHRClinicalEntryPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.query.Queryable;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIBloodDrawFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRICaseTemplateFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRICasesFormPanelSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalObservationsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalRemarksFormPanelSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIHousingFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIObservationOrdersFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIProcedureFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIProcedureOrderFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentGivenFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentOrderFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIVitalsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIWeightFormSection;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class NBRICasesFormType extends NBRIBaseTaskFormType
+{
+ @Queryable
+ public static final String NAME = "Clinical Cases";
+ public static final String LABEL = "Clinical Cases";
+
+ public NBRICasesFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Clinical", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRICaseTemplateFormSection("Case Template", "Case Template", "nbri_ehr-casetemplatepanel", Arrays.asList(ClientDependency.supplierFromPath("nbri_ehr/panel/CaseTemplatePanel.js"))),
+ new NBRICasesFormPanelSection("Clinical Case", ctx, false),
+ new NBRIClinicalRemarksFormPanelSection(true, "cases", "Clinical Remarks", ctx, false),
+ new NBRIWeightFormSection(true, false, true, "cases"),
+ new NBRIClinicalObservationsFormSection(true, "cases"),
+ new NBRIObservationOrdersFormSection(null, true, "cases"),
+ new NBRIProcedureFormSection(true, "cases"),
+ new NBRIProcedureOrderFormSection(true, "cases"),
+ new NBRITreatmentGivenFormSection(true, "cases"),
+ new NBRITreatmentOrderFormSection(true, "cases"),
+ new NBRIVitalsFormSection(true, "cases"),
+ new NBRIBloodDrawFormSection(true, "cases"),
+ new NBRIHousingFormSection(true, true, true, "cases")
+ ));
+
+ setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID);
+ setDisplayReviewRequired(true);
+
+ for (FormSection s : this.getFormSections())
+ {
+ s.addConfigSource("ClinicalDefaults");
+ s.addConfigSource("ClinicalCase");
+ s.addConfigSource("TreatmentSchedule");
+ s.addConfigSource("MedicationEndDate");
+
+ if (s instanceof SimpleFormSection && !s.getName().equals("tasks"))
+ s.setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID);
+
+ if (s instanceof AbstractFormSection)
+ {
+ ((AbstractFormSection)s).setAllowBulkAdd(false);
+ }
+ }
+ setStoreCollectionClass("NBRI_EHR.data.CaseStoreCollection");
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/CaseStoreCollection.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/TreatmentSchedule.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/DrugVolumeField.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/DrugAmountWindow.js"));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ClinicalDefaults.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ClinicalCase.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/MedicationEndDate.js"));
+ addClientDependency(ClientDependency.supplierFromPath("ehr/panel/ExamDataEntryPanel.js"));
+ setJavascriptClass("EHR.panel.ExamDataEntryPanel");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/NBRIExamCasesDataEntryPanel.js"));
+ setJavascriptClass("NBRI_EHR.panel.ExamCasesDataEntryPanel");
+
+ // Needed for case and scheduled date/time when navigating from treatment or observation schedule
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/treatmentSubmit.js"));
+ }
+
+ @Override
+ protected boolean canInsert()
+ {
+ if (!getCtx().getContainer().hasPermission(getCtx().getUser(), EHRClinicalEntryPermission.class))
+ return false;
+
+ return super.canInsert();
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List ret = super.getButtonConfigs();
+
+ ret.remove("SUBMIT");
+ ret.add("NBRI_TREATMENT_SUBMIT");
+
+ return ret;
+ }
+
+ @Override
+ protected List getMoreActionButtonConfigs()
+ {
+ List configs = super.getMoreActionButtonConfigs();
+ configs.remove("DISCARD");
+ return configs;
+ }
+
+ @Override
+ public JSONObject toJSON()
+ {
+ JSONObject ret = super.toJSON();
+
+ //this form involves extra work on save, so relax warning thresholds to reduce error logging
+ ret.put("perRowWarningThreshold", 0.5);
+ ret.put("totalTransactionWarningThrehsold", 60);
+ ret.put("perRowValidationWarningThrehsold", 6);
+ ret.put("totalValidationTransactionWarningThrehsold", 60);
+
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIChemistryImportFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIChemistryImportFormType.java
new file mode 100644
index 0000000..3c87a7e
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIChemistryImportFormType.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.AbstractDataEntryForm;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.ActionURL;
+
+import java.util.ArrayList;
+
+public class NBRIChemistryImportFormType extends AbstractDataEntryForm
+{
+ private final DataEntryFormContext _formContext;
+
+ public NBRIChemistryImportFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, "ChemistryImport", "Chemistry Import", "Lab Results", new ArrayList<>());
+ _formContext = ctx;
+ }
+
+ @Override
+ public JSONObject toJSON(boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(includeFormElements);
+
+ ActionURL url = new ActionURL("study", "import", _formContext.getContainer());
+ url.addParameter("datasetId", "1024");
+ json.put("url", url);
+
+ return json;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIClinicalObservationsFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIClinicalObservationsFormType.java
new file mode 100644
index 0000000..9a2094f
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIClinicalObservationsFormType.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalObservationsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIWeightFormSection;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class NBRIClinicalObservationsFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Observations";
+
+ public NBRIClinicalObservationsFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, NAME, "Clinical", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIClinicalObservationsFormSection(false, false),
+ new NBRIWeightFormSection(true, true)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ClinicalDefaults.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ObsDefaults.js"));
+
+ // Needed for case and scheduled date/time when navigating from treatment or observation schedule
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/treatmentSubmit.js"));
+
+ for (FormSection s : this.getFormSections())
+ {
+ s.addConfigSource("ClinicalDefaults");
+ s.addConfigSource("ObsDefaults");
+ }
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List ret = super.getButtonConfigs();
+
+ ret.remove("SUBMIT");
+ ret.add("NBRI_TREATMENT_SUBMIT");
+
+ return ret;
+ }
+
+ @Override
+ public JSONObject toJSON()
+ {
+ JSONObject ret = super.toJSON();
+
+ //this form involves extra work on save, so relax warning thresholds to reduce error logging
+ ret.put("perRowWarningThreshold", 0.5);
+ ret.put("totalTransactionWarningThrehsold", 60);
+ ret.put("perRowValidationWarningThrehsold", 6);
+ ret.put("totalValidationTransactionWarningThrehsold", 60);
+
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIClinicalRoundsFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIClinicalRoundsFormType.java
new file mode 100644
index 0000000..2864256
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIClinicalRoundsFormType.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.AbstractFormSection;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.dataentry.SimpleFormSection;
+import org.labkey.api.ehr.security.EHRClinicalEntryPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIBloodDrawFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRICaseTemplateFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRICasesFormPanelSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIClinicalObservationsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIHousingFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIProcedureFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentGivenFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIVitalsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIWeightFormSection;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class NBRIClinicalRoundsFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Clinical Rounds";
+ public static final String LABEL = "Clinical Rounds";
+
+ public NBRIClinicalRoundsFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Clinical", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRICaseTemplateFormSection("Case Template", "Case Template", "nbri_ehr-casetemplatepanel", Arrays.asList(ClientDependency.supplierFromPath("nbri_ehr/panel/CaseTemplatePanel.js"))),
+ new NBRICasesFormPanelSection("Clinical Case", ctx, false),
+ new NBRIWeightFormSection(true, false, true, "cases"),
+ new NBRIClinicalObservationsFormSection(true, "cases"),
+ new NBRIProcedureFormSection(true, "cases"),
+ new NBRITreatmentGivenFormSection(true, "cases"),
+ new NBRIVitalsFormSection(true, "cases"),
+ new NBRIBloodDrawFormSection(true, "cases"),
+ new NBRIHousingFormSection(true, true, true, "cases")
+ ));
+
+ setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID);
+ setDisplayReviewRequired(true);
+
+ for (FormSection s : this.getFormSections())
+ {
+ s.addConfigSource("ClinicalDefaults");
+ s.addConfigSource("ClinicalCase");
+ s.addConfigSource("ClinicalRounds");
+ s.addConfigSource("TreatmentSchedule");
+ s.addConfigSource("MedicationEndDate");
+
+ if (s instanceof SimpleFormSection && !s.getName().equals("tasks"))
+ s.setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID);
+
+ if (s instanceof AbstractFormSection)
+ {
+ ((AbstractFormSection)s).setAllowBulkAdd(false);
+ }
+ }
+ setStoreCollectionClass("NBRI_EHR.data.CaseStoreCollection");
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/CaseStoreCollection.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/TreatmentSchedule.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/DrugVolumeField.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/DrugAmountWindow.js"));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ClinicalDefaults.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ClinicalCase.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ClinicalRounds.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/MedicationEndDate.js"));
+ addClientDependency(ClientDependency.supplierFromPath("ehr/panel/ExamDataEntryPanel.js"));
+ setJavascriptClass("EHR.panel.ExamDataEntryPanel");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/NBRIExamCasesDataEntryPanel.js"));
+ setJavascriptClass("NBRI_EHR.panel.ExamCasesDataEntryPanel");
+
+ // Needed for case and scheduled date/time when navigating from treatment or observation schedule
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/treatmentSubmit.js"));
+ }
+
+ @Override
+ protected boolean canInsert()
+ {
+ if (!getCtx().getContainer().hasPermission(getCtx().getUser(), EHRClinicalEntryPermission.class))
+ return false;
+
+ return super.canInsert();
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List ret = super.getButtonConfigs();
+
+ ret.remove("SUBMIT");
+ ret.add("NBRI_TREATMENT_SUBMIT");
+
+ return ret;
+ }
+
+ @Override
+ protected List getMoreActionButtonConfigs()
+ {
+ List configs = super.getMoreActionButtonConfigs();
+ configs.remove("DISCARD");
+ return configs;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIDeathNecropsyFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIDeathNecropsyFormType.java
new file mode 100644
index 0000000..b8b614f
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIDeathNecropsyFormType.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.security.EHRVeterinarianPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIDeathFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIGrossPathologyFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRINecropsyFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITissueDispositionFormSection;
+import org.labkey.nbri_ehr.security.NBRIEHRVetTechPermission;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class NBRIDeathNecropsyFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Necropsy";
+ public static final String LABEL = "Death/Necropsy";
+
+ public NBRIDeathNecropsyFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Pathology", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIDeathFormSection(),
+ new NBRINecropsyFormSection(true),
+ new NBRIGrossPathologyFormSection(true),
+ new NBRITissueDispositionFormSection(true)
+
+ ));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/DeathNecropsy.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/deathNecropsyButtons.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("DeathNecropsy");
+ }
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List defaultButtons = new ArrayList<>();
+ boolean isVetTech = getCtx().getContainer().hasPermission(getCtx().getUser(), NBRIEHRVetTechPermission.class);
+ boolean isVet = getCtx().getContainer().hasPermission(getCtx().getUser(), EHRVeterinarianPermission.class);
+
+ defaultButtons.add("NBRISAVEDRAFTBUTTON");
+ defaultButtons.add("DEATHSUBMIT");
+
+ if (isVet)
+ {
+ defaultButtons.add("SUBMIT"); //submit final
+ defaultButtons.add("DEATH_NECROPSY_VET_REVIEW"); //submit for review
+ }
+ else if (isVetTech)
+ {
+ defaultButtons.add("DEATH_NECROPSY_VET_REVIEW"); //submit for review
+ }
+ return defaultButtons;
+ }
+
+ @Override
+ protected List getMoreActionButtonConfigs()
+ {
+ List configs = super.getMoreActionButtonConfigs();
+ configs.remove("DISCARD");
+ return configs;
+ }
+
+ @Override
+ public JSONObject toJSON()
+ {
+ JSONObject ret = super.toJSON();
+
+ //this form involves extra work on save, so relax warning thresholds to reduce error logging
+ ret.put("perRowWarningThreshold", 0.5);
+ ret.put("totalTransactionWarningThrehsold", 60);
+ ret.put("perRowValidationWarningThrehsold", 6);
+ ret.put("totalValidationTransactionWarningThrehsold", 60);
+
+ return ret;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIDepartureFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIDepartureFormType.java
new file mode 100644
index 0000000..7e0d955
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIDepartureFormType.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.module.Module;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIDepartureFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.Arrays;
+
+public class NBRIDepartureFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "departure";
+
+ public NBRIDepartureFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, "Departure", "Colony Management", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIDepartureFormSection()
+ ));
+ }
+}
+
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIExemptionsFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIExemptionsFormType.java
new file mode 100644
index 0000000..4d2854d
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIExemptionsFormType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIExemptionsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.Arrays;
+
+public class NBRIExemptionsFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Exemptions";
+
+ public NBRIExemptionsFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, NAME, "Colony Management", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIExemptionsFormSection(NAME)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/NBRIDefault.js"));
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFlagsFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFlagsFormType.java
new file mode 100644
index 0000000..615d84e
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFlagsFormType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIFlagsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.Arrays;
+
+public class NBRIFlagsFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Flags";
+
+ public NBRIFlagsFormType (DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, NAME, "Colony Management", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIFlagsFormSection(NAME)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/NBRIDefault.js"));
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFloorFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFloorFormType.java
new file mode 100644
index 0000000..d847464
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIFloorFormType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
+import org.labkey.api.module.Module;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.ActionURL;
+
+import java.util.ArrayList;
+
+public class NBRIFloorFormType extends AdminLinksFormType
+{
+ public NBRIFloorFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, "Floor", "Floor", "Locations", new ArrayList<>());
+ }
+
+ @Override
+ protected ActionURL dataEntryLink()
+ {
+ ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
+ url.addParameter("schemaName", "ehr_lookups");
+ url.addParameter("query.queryName", "floors");
+ url.addParameter("showImport", "true");
+ return url;
+ }
+
+ @Override
+ public boolean isAvailable()
+ {
+ return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIHousingFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIHousingFormType.java
new file mode 100644
index 0000000..91b7c65
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIHousingFormType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.security.EHRHousingTransferPermission;
+import org.labkey.api.module.Module;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIHousingFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.Arrays;
+
+public class NBRIHousingFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Housing";
+
+ public NBRIHousingFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, "Housing Transfers", "Colony Management", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIHousingFormSection(true, false)
+ ));
+ }
+
+ @Override
+ protected boolean canInsert()
+ {
+ if (!getCtx().getContainer().hasPermission(getCtx().getUser(), EHRHousingTransferPermission.class))
+ return false;
+
+ return super.canInsert();
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIMedicationTreatmentFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIMedicationTreatmentFormType.java
new file mode 100644
index 0000000..e9d8bfc
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIMedicationTreatmentFormType.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentGivenFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITreatmentOrderFormSection;
+
+import java.util.List;
+
+public class NBRIMedicationTreatmentFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "medicationTreatment";
+ public static final String LABEL = "Medications/Treatments";
+
+ public NBRIMedicationTreatmentFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Clinical", List.of(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRITreatmentGivenFormSection(),
+ new NBRITreatmentOrderFormSection()
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/TreatmentSchedule.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/DrugVolumeField.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/DrugAmountWindow.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ClinicalDefaults.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/MedicationEndDate.js"));
+
+ // Needed for case and scheduled date/time when navigating from treatment or observation schedule
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/treatmentSubmit.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("TreatmentSchedule");
+ s.addConfigSource("ClinicalDefaults");
+ s.addConfigSource("MedicationEndDate");
+ }
+ }
+
+ @Override
+ protected List getButtonConfigs()
+ {
+ List ret = super.getButtonConfigs();
+
+ ret.remove("SUBMIT");
+ ret.add("NBRI_TREATMENT_SUBMIT");
+
+ return ret;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRINotesFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRINotesFormType.java
new file mode 100644
index 0000000..9e4ea37
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRINotesFormType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRINotesFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.Arrays;
+
+public class NBRINotesFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Notes";
+
+ public NBRINotesFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, NAME, "Colony Management", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRINotesFormSection(NAME)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/NBRIDefault.js"));
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIPairingsFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIPairingsFormType.java
new file mode 100644
index 0000000..c83bb7c
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIPairingsFormType.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.EHRService;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.security.EHRBehaviorEntryPermission;
+import org.labkey.api.module.Module;
+import org.labkey.nbri_ehr.dataentry.section.BaseFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.Arrays;
+
+public class NBRIPairingsFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Pairing Observations";
+
+ public NBRIPairingsFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, NAME, "Behavior", Arrays.asList(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new BaseFormSection("study", "pairings", NAME, EHRService.FORM_SECTION_LOCATION.Body, true, false)
+ ));
+ }
+
+ @Override
+ protected boolean canInsert()
+ {
+ if (!getCtx().getContainer().hasPermission(getCtx().getUser(), EHRBehaviorEntryPermission.class))
+ return false;
+
+ return super.canInsert();
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIPregnancyFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIPregnancyFormType.java
new file mode 100644
index 0000000..0e29e81
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIPregnancyFormType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIPregnancyFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.List;
+
+public class NBRIPregnancyFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Pregnancy";
+ public static final String LABEL = "Pregnancy Outcomes";
+
+ public NBRIPregnancyFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Colony Management", List.of(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIPregnancyFormSection(LABEL)
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Pregnancy.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("Pregnancy");
+ }
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProjectFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProjectFormType.java
new file mode 100644
index 0000000..2a0b6cb
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProjectFormType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
+import org.labkey.api.ehr.security.EHRProtocolEditPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.ActionURL;
+
+import java.util.ArrayList;
+
+public class NBRIProjectFormType extends AdminLinksFormType
+{
+ public NBRIProjectFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, "Projects", "Projects", "Admin", new ArrayList<>());
+ }
+
+ @Override
+ protected ActionURL dataEntryLink()
+ {
+ ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
+ url.addParameter("schemaName", "ehr");
+ url.addParameter("query.queryName", "project");
+ url.addParameter("showImport", "true");
+ return url;
+ }
+
+ @Override
+ public boolean isAvailable()
+ {
+ return (super.isAvailable() || getCtx().getContainer().hasPermission(getCtx().getUser(), EHRProtocolEditPermission.class));
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProtocolFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProtocolFormType.java
new file mode 100644
index 0000000..540602a
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIProtocolFormType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
+import org.labkey.api.ehr.security.EHRProtocolEditPermission;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.ActionURL;
+
+import java.util.ArrayList;
+
+public class NBRIProtocolFormType extends AdminLinksFormType
+{
+ public NBRIProtocolFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, "Protocols", "Protocols", "Admin", new ArrayList<>());
+ }
+
+ @Override
+ protected ActionURL dataEntryLink()
+ {
+ ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
+ url.addParameter("schemaName", "ehr");
+ url.addParameter("query.queryName", "protocol");
+ url.addParameter("showImport", "true");
+ return url;
+ }
+
+ @Override
+ public boolean isAvailable()
+ {
+ return (super.isAvailable() || getCtx().getContainer().hasPermission(getCtx().getUser(), EHRProtocolEditPermission.class));
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRearrivalFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRearrivalFormType.java
new file mode 100644
index 0000000..b926cc4
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRearrivalFormType.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormSection;
+import org.labkey.api.ehr.dataentry.forms.LockAnimalsFormSection;
+import org.labkey.api.module.Module;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIArrivalInstructionsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIRearrivalFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+
+import java.util.Arrays;
+
+public class NBRIRearrivalFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Rearrival";
+
+ public NBRIRearrivalFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, "Rearrivals", "Colony Management", Arrays.asList(
+ new LockAnimalsFormSection(),
+ new NBRIArrivalInstructionsFormSection(),
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIRearrivalFormSection()
+ ));
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/Rearrival.js"));
+
+ for (FormSection s : getFormSections())
+ {
+ s.addConfigSource("Rearrival");
+ }
+ }
+
+ @Override
+ public boolean isAvailable()
+ {
+ return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRoomFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRoomFormType.java
new file mode 100644
index 0000000..4ce0aa4
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIRoomFormType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.forms.AdminLinksFormType;
+import org.labkey.api.module.Module;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.ActionURL;
+
+import java.util.ArrayList;
+
+public class NBRIRoomFormType extends AdminLinksFormType
+{
+ public NBRIRoomFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, "Room", "Room", "Locations", new ArrayList<>());
+ }
+
+ @Override
+ protected ActionURL dataEntryLink()
+ {
+ ActionURL url = new ActionURL("ldk", "updateQuery", getCtx().getContainer());
+ url.addParameter("schemaName", "ehr_lookups");
+ url.addParameter("query.queryName", "rooms");
+ url.addParameter("showImport", "true");
+ return url;
+ }
+
+ @Override
+ public boolean isAvailable()
+ {
+ return super.isAvailable() && getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRISerologyImportFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRISerologyImportFormType.java
new file mode 100644
index 0000000..2bacdcc
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRISerologyImportFormType.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.AbstractDataEntryForm;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.module.Module;
+import org.labkey.api.view.ActionURL;
+
+import java.util.ArrayList;
+
+public class NBRISerologyImportFormType extends AbstractDataEntryForm
+{
+ private final DataEntryFormContext _formContext;
+
+ public NBRISerologyImportFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, "SerologyImport", "Serology Import", "Lab Results", new ArrayList<>());
+ _formContext = ctx;
+ }
+
+ @Override
+ public JSONObject toJSON(boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(includeFormElements);
+
+ ActionURL url = new ActionURL("study", "import", _formContext.getContainer());
+ url.addParameter("datasetId", "1032");
+ json.put("url", url);
+
+ return json;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIWeightFormType.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIWeightFormType.java
new file mode 100644
index 0000000..a63e265
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/form/NBRIWeightFormType.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.form;
+
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.module.Module;
+import org.labkey.nbri_ehr.dataentry.section.NBRIAnimalDetailsFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRITaskFormSection;
+import org.labkey.nbri_ehr.dataentry.section.NBRIWeightFormSection;
+
+import java.util.List;
+
+public class NBRIWeightFormType extends NBRIBaseTaskFormType
+{
+ public static final String NAME = "Weight";
+ public static final String LABEL = "Weights";
+
+ public NBRIWeightFormType(DataEntryFormContext ctx, Module owner)
+ {
+ super(ctx, owner, NAME, LABEL, "Clinical", List.of(
+ new NBRITaskFormSection(),
+ new NBRIAnimalDetailsFormSection(),
+ new NBRIWeightFormSection(false, false)
+ ));
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/BaseFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/BaseFormSection.java
new file mode 100644
index 0000000..a01d915
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/BaseFormSection.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.EHRService;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.SimpleFormSection;
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class BaseFormSection extends SimpleFormSection
+{
+ private boolean _collapsible = false;
+ private boolean _initCollapsed = false;
+ private boolean _addCopyFromSection = false;
+
+ public BaseFormSection(String schemaName, String queryName, String label)
+ {
+ this(schemaName, queryName, label, "ehr-gridpanel");
+ }
+
+ public BaseFormSection(String schemaName, String queryName, String label, String xtype, boolean collapsible, boolean initCollapsed, boolean addCopyFromSection)
+ {
+ this(schemaName, queryName, label, xtype, EHRService.FORM_SECTION_LOCATION.Body);
+
+ if (!collapsible && initCollapsed)
+ throw new IllegalArgumentException("Cannot set initCollapsed to true if collapsible is false");
+
+ _collapsible = collapsible;
+ _initCollapsed = initCollapsed;
+ _addCopyFromSection = addCopyFromSection;
+ }
+
+ public BaseFormSection(String schemaName, String queryName, String label, String xtype)
+ {
+ this(schemaName, queryName, label, xtype, EHRService.FORM_SECTION_LOCATION.Body);
+ }
+
+ public BaseFormSection(String schemaName, String queryName, String label, EHRService.FORM_SECTION_LOCATION location, boolean collapsible, boolean initCollapsed)
+ {
+ this(schemaName, queryName, label, "ehr-gridpanel", location);
+
+ if (!collapsible && initCollapsed)
+ throw new IllegalArgumentException("Cannot set initCollapsed to true if collapsible is false");
+
+ _collapsible = collapsible;
+ _initCollapsed = initCollapsed;
+ }
+
+ public BaseFormSection(String schemaName, String queryName, String label, EHRService.FORM_SECTION_LOCATION location)
+ {
+ this(schemaName, queryName, label, "ehr-gridpanel", location);
+ }
+
+ public BaseFormSection(String schemaName, String queryName, String label, String xtype, EHRService.FORM_SECTION_LOCATION location, boolean collapsible, boolean initCollapsed)
+ {
+ this(schemaName, queryName, label, xtype, location);
+
+ if (!collapsible && initCollapsed)
+ throw new IllegalArgumentException("Cannot set initCollapsed to true if collapsible is false");
+
+ _collapsible = collapsible;
+ _initCollapsed = initCollapsed;
+ }
+
+ public BaseFormSection(String schemaName, String queryName, String label, String xtype, EHRService.FORM_SECTION_LOCATION location)
+ {
+ super(schemaName, queryName, label, xtype, location);
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/plugin/RowEditor.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/AddAnimalsWindow.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/LocationField.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/CopyFromSectionWindow.js"));
+ addClientDependency(ClientDependency.supplierFromPath("ehr/window/FormBulkAddWindow.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/FormBulkAddWindow.js"));
+
+ setSupportFormSort(false);
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaultButtons = super.getTbarButtons();
+
+ int idx = defaultButtons.indexOf("ADDANIMALS");
+ if (idx > -1)
+ {
+ defaultButtons.remove(idx);
+ defaultButtons.add(idx, "NBRI_ADDANIMALS");
+ }
+
+ idx = defaultButtons.indexOf("COPYFROMSECTION");
+ if (idx > -1)
+ {
+ if (!_addCopyFromSection)
+ defaultButtons.remove(idx);
+ else
+ {
+ defaultButtons.remove(idx);
+ defaultButtons.add(idx, "NBRICOPYFROMSECTION");
+ }
+ }
+
+ return defaultButtons;
+ }
+
+ @Override
+ public List getTbarMoreActionButtons()
+ {
+ List defaultButtons = super.getTbarMoreActionButtons();
+ defaultButtons.remove("GUESSPROJECT");
+ defaultButtons.add("NBRI_FORM_BULK_ADD");
+ return defaultButtons;
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", _collapsible);
+ json.put("initCollapsed", _initCollapsed);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIAliasFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIAliasFormSection.java
new file mode 100644
index 0000000..a507c95
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIAliasFormSection.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+public class NBRIAliasFormSection extends BaseFormSection
+{
+ public NBRIAliasFormSection(String label)
+ {
+ super("study", "alias", label, "ehr-gridpanel", true, false, false);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIAnimalDetailsFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIAnimalDetailsFormSection.java
new file mode 100644
index 0000000..2660cfd
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIAnimalDetailsFormSection.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.ehr.dataentry.NonStoreFormSection;
+
+public class NBRIAnimalDetailsFormSection extends NonStoreFormSection
+{
+ public NBRIAnimalDetailsFormSection()
+ {
+ this("nbri_ehr-animaldetailspanel");
+ }
+
+ public NBRIAnimalDetailsFormSection(String xtype)
+ {
+ super("AnimalDetails", "Animal Details", xtype);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIArrivalFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIArrivalFormSection.java
new file mode 100644
index 0000000..2bd152c
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIArrivalFormSection.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.query.FieldKey;
+
+import java.util.List;
+
+public class NBRIArrivalFormSection extends BaseFormSection
+{
+ public NBRIArrivalFormSection()
+ {
+ super("study", "arrival", "Arrivals", "ehr-gridpanel", true, true, true);
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+
+ @Override
+ protected List getFieldKeys(TableInfo ti)
+ {
+ List keys = super.getFieldKeys(ti);
+
+ keys.add(6, FieldKey.fromString("Id/demographics/dam"));
+ keys.add(7, FieldKey.fromString("Id/demographics/sire"));
+ keys.add(8, FieldKey.fromString("Id/demographics/species"));
+ keys.add(9, FieldKey.fromString("Id/demographics/birth"));
+ keys.add(10, FieldKey.fromString("Id/demographics/gender"));
+ keys.add(12, FieldKey.fromString("Id/demographics/geographic_origin"));
+
+ return keys;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIArrivalInstructionsFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIArrivalInstructionsFormSection.java
new file mode 100644
index 0000000..09e70a9
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIArrivalInstructionsFormSection.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.ehr.dataentry.AbstractFormSection;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormElement;
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.Collections;
+import java.util.List;
+
+public class NBRIArrivalInstructionsFormSection extends AbstractFormSection
+{
+ public NBRIArrivalInstructionsFormSection()
+ {
+ super("ArrivalInstructions", "Instructions", "nbri_ehr-arrivalinstructionspanel");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/InstructionsPanel.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/ArrivalInstructionsPanel.js"));
+ }
+
+ @Override
+ protected List getFormElements(DataEntryFormContext ctx)
+ {
+ return Collections.emptyList();
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java
new file mode 100644
index 0000000..604cbb9
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthFormSection.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.forms.NewAnimalFormSection;
+import org.labkey.api.query.FieldKey;
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRIBirthFormSection extends NewAnimalFormSection
+{
+ public NBRIBirthFormSection()
+ {
+ super("study", "birth", "Births", false);
+ addClientDependency(ClientDependency.supplierFromPath("ehr/window/FormBulkAddWindow.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/FormBulkAddWindow.js"));
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", true);
+ json.put("initCollapsed", true);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+
+ @Override
+ protected List getFieldKeys(TableInfo ti)
+ {
+ List keys = super.getFieldKeys(ti);
+
+ keys.add(2, FieldKey.fromString("Id/demographics/species"));
+ keys.add(3, FieldKey.fromString("Id/demographics/gender"));
+ keys.add(4, FieldKey.fromString("Id/demographics/dam"));
+ keys.add(5, FieldKey.fromString("Id/demographics/sire"));
+
+ return keys;
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaultButtons = super.getTbarButtons();
+
+ int idx = defaultButtons.indexOf("ADDANIMALS");
+ if (idx > -1)
+ {
+ defaultButtons.remove(idx);
+ defaultButtons.add(idx, "NBRI_ADDANIMALS");
+ }
+ defaultButtons.remove("COPYFROMSECTION");
+ return defaultButtons;
+ }
+
+ @Override
+ public List getTbarMoreActionButtons()
+ {
+ List defaultButtons = super.getTbarMoreActionButtons();
+ defaultButtons.remove("GUESSPROJECT");
+ defaultButtons.add("NBRI_FORM_BULK_ADD");
+ return defaultButtons;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthInstructionsFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthInstructionsFormSection.java
new file mode 100644
index 0000000..995665b
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBirthInstructionsFormSection.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.ehr.dataentry.AbstractFormSection;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.FormElement;
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.Collections;
+import java.util.List;
+
+public class NBRIBirthInstructionsFormSection extends AbstractFormSection
+{
+ public NBRIBirthInstructionsFormSection()
+ {
+ super("BirthInstructions", "Instructions", "nbri_ehr-birthinstructionspanel");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/InstructionsPanel.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/BirthInstructionsPanel.js"));
+ }
+
+ @Override
+ protected List getFormElements(DataEntryFormContext ctx)
+ {
+ return Collections.emptyList();
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBloodDrawFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBloodDrawFormSection.java
new file mode 100644
index 0000000..cf9e201
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIBloodDrawFormSection.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.EHRService;
+import org.labkey.api.ehr.dataentry.BloodDrawFormSection;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRIBloodDrawFormSection extends BloodDrawFormSection
+{
+ private boolean _collapsible;
+ private boolean _initCollapsed;
+ private boolean _addCopyFromSection;
+
+ public NBRIBloodDrawFormSection(boolean collapsible, boolean initCollapsed, boolean addCopyFromSection)
+ {
+ super(false, EHRService.FORM_SECTION_LOCATION.Body);
+
+ if (!collapsible && initCollapsed)
+ throw new IllegalArgumentException("Cannot set initCollapsed to true if collapsible is false");
+
+ _collapsible = collapsible;
+ _initCollapsed = initCollapsed;
+ _addCopyFromSection = addCopyFromSection;
+ }
+
+ public NBRIBloodDrawFormSection(boolean isChild, String parentQueryName)
+ {
+ this(true, true, true);
+
+ if (isChild && null != parentQueryName)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaultButtons = super.getTbarButtons();
+
+ int idx = defaultButtons.indexOf("ADDANIMALS");
+ if (idx > -1)
+ {
+ defaultButtons.remove(idx);
+ defaultButtons.add(idx, "NBRI_ADDANIMALS");
+ }
+
+ idx = defaultButtons.indexOf("COPYFROMSECTION");
+ if (idx > -1)
+ {
+ if (!_addCopyFromSection)
+ defaultButtons.remove(idx);
+ else
+ {
+ defaultButtons.remove(idx);
+ defaultButtons.add(idx, "NBRICOPYFROMSECTION");
+ }
+ }
+
+ return defaultButtons;
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", _collapsible);
+ json.put("initCollapsed", _initCollapsed);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRICaseTemplateFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRICaseTemplateFormSection.java
new file mode 100644
index 0000000..6d14b36
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRICaseTemplateFormSection.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.NonStoreFormSection;
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+public class NBRICaseTemplateFormSection extends NonStoreFormSection
+{
+ public NBRICaseTemplateFormSection(String name, String label, String xtype, List> dependencies)
+ {
+ super(name, label, xtype, dependencies);
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", true);
+ json.put("initCollapsed", true);
+ json.put("dataDependentCollapseHeader", false);
+ return json;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRICasesFormPanelSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRICasesFormPanelSection.java
new file mode 100644
index 0000000..62c9336
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRICasesFormPanelSection.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.ParentFormPanelSection;
+import org.labkey.api.ehr.security.EHRVeterinarianPermission;
+import org.labkey.api.query.FieldKey;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.security.NBRIEHRVetTechPermission;
+
+import java.util.List;
+
+public class NBRICasesFormPanelSection extends ParentFormPanelSection
+{
+ private final boolean isVetTech;
+ private final boolean isVet;
+ private final boolean isFolderAdmin;
+ private final boolean isBehavior;
+
+ public NBRICasesFormPanelSection(String label, DataEntryFormContext ctx, boolean isBehavior)
+ {
+ super("study", "cases", label);
+ this.isBehavior = isBehavior;
+ this.isVetTech = ctx.getContainer().hasPermission(ctx.getUser(), NBRIEHRVetTechPermission.class);
+ this.isVet = ctx.getContainer().hasPermission(ctx.getUser(), EHRVeterinarianPermission.class);
+ this.isFolderAdmin = ctx.getContainer().hasPermission(ctx.getUser(), AdminPermission.class);
+ setSupportFormSort(false);
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/AnimalIdCases.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/field/EditCase.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/SelectCaseButton.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/panel/SelectCasePanel.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/window/SelectCaseWindow.js"));
+
+ setClientStoreClass("NBRI_EHR.data.CaseClientStore");
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/CaseClientStore.js"));
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", true);
+ json.put("initCollapsed", false);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+
+ @Override
+ protected List getFieldKeys(TableInfo ti)
+ {
+ List keys = super.getFieldKeys(ti);
+
+ // only Vets and Folder Admins can see the enddate ('Close date') field to be able to close the case.
+ // Does not apply to behavior cases.
+ if (!this.isBehavior && !isVet && (!isFolderAdmin || isVetTech))
+ {
+ keys.remove(FieldKey.fromString("enddate"));
+ keys.remove(FieldKey.fromString("closeRemark"));
+ }
+
+ keys.add(FieldKey.fromString("qcstate/label"));
+
+ return keys;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalObservationsFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalObservationsFormSection.java
new file mode 100644
index 0000000..b435c34
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalObservationsFormSection.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRIClinicalObservationsFormSection extends BaseFormSection
+{
+ public static final String LABEL = "Observations";
+ private boolean _autoPopulateDailyObs;
+
+ public NBRIClinicalObservationsFormSection(boolean autoPopulateDailyObs, boolean initCollapsed)
+ {
+ super("study", "clinical_observations", LABEL, "ehr-clinicalobservationgridpanel", true, initCollapsed, true);
+
+ _autoPopulateDailyObs = autoPopulateDailyObs;
+ addClientDependency(ClientDependency.supplierFromPath("ehr/plugin/ClinicalObservationsCellEditing.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/ClinicalObservationClientStore.js"));
+ addClientDependency(ClientDependency.supplierFromPath("ehr/grid/ClinicalObservationGridPanel.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/clinicalObsGridButton.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/addClinicalObsButton.js"));
+ setClientStoreClass("NBRI_EHR.data.ClinicalObservationsClientStore");
+ }
+
+ public NBRIClinicalObservationsFormSection(boolean isChild, String parentQueryName)
+ {
+ this(false, true);
+
+ if (isChild && null != parentQueryName)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaults = super.getTbarButtons();
+
+ if (_autoPopulateDailyObs)
+ {
+ defaults.add("NBRI_AUTO_POPULATE_DAILY_OBS");
+ }
+ else {
+ defaults.add("NBRI_DAILY_CLINICAL_OBS");
+ }
+
+ return defaults;
+
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalRemarksFormPanelSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalRemarksFormPanelSection.java
new file mode 100644
index 0000000..b9ac8dd
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalRemarksFormPanelSection.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.ParentFormPanelSection;
+import org.labkey.api.ehr.security.EHRVeterinarianPermission;
+import org.labkey.api.query.FieldKey;
+import org.labkey.api.security.permissions.AdminPermission;
+import org.labkey.api.view.template.ClientDependency;
+import org.labkey.nbri_ehr.security.NBRIEHRVetTechPermission;
+
+import java.util.List;
+
+public class NBRIClinicalRemarksFormPanelSection extends ParentFormPanelSection
+{
+ private boolean isVetTech;
+ private boolean isVet;
+ private boolean isFolderAdmin;
+ private boolean isBehavior;
+
+ public NBRIClinicalRemarksFormPanelSection(String label)
+ {
+ super("study", "clinremarks", label);
+ setSupportFormSort(false);
+ }
+
+ public NBRIClinicalRemarksFormPanelSection(boolean isChild, String parentQueryName, String label, DataEntryFormContext ctx, boolean isBehavior)
+ {
+ this(label);
+ this.isVetTech = ctx.getContainer().hasPermission(ctx.getUser(), NBRIEHRVetTechPermission.class);
+ this.isVet = ctx.getContainer().hasPermission(ctx.getUser(), EHRVeterinarianPermission.class);
+ this.isFolderAdmin = ctx.getContainer().hasPermission(ctx.getUser(), AdminPermission.class);
+ this.isBehavior = isBehavior;
+
+ if (isChild)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", true);
+ json.put("initCollapsed", false);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+
+ @Override
+ protected List getFieldKeys(TableInfo ti)
+ {
+ List keys = super.getFieldKeys(ti);
+
+ // only Vets and Folder Admins can enter S.O.A.P.
+ if (isBehavior || (!isVet && (!isFolderAdmin || isVetTech)))
+ {
+ keys.remove(FieldKey.fromString("s"));
+ keys.remove(FieldKey.fromString("o"));
+ keys.remove(FieldKey.fromString("a"));
+ keys.remove(FieldKey.fromString("p"));
+ }
+
+ return keys;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalRemarksFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalRemarksFormSection.java
new file mode 100644
index 0000000..e5307f4
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIClinicalRemarksFormSection.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.query.FieldKey;
+
+import java.util.List;
+
+public class NBRIClinicalRemarksFormSection extends BaseFormSection
+{
+ public static final String LABEL = "Clinical Remarks";
+ private final boolean isVetTech;
+ private final boolean isVet;
+ private final boolean isFolderAdmin;
+
+ public NBRIClinicalRemarksFormSection(boolean isVetTech, boolean isVet, boolean isFolderAdmin)
+ {
+ super("study", "clinremarks", LABEL, "ehr-gridpanel", true, true, true);
+ this.isVetTech = isVetTech;
+ this.isVet = isVet;
+ this.isFolderAdmin = isFolderAdmin;
+ }
+
+ public NBRIClinicalRemarksFormSection(String label, boolean isVetTech, boolean isVet, boolean isFolderAdmin)
+ {
+ super("study", "clinremarks", label, "ehr-gridpanel", true, true, true);
+ this.isVetTech = isVetTech;
+ this.isVet = isVet;
+ this.isFolderAdmin = isFolderAdmin;
+ }
+
+ @Override
+ protected List getFieldKeys(TableInfo ti)
+ {
+ List keys = super.getFieldKeys(ti);
+
+ // only Vets and Folder Admins can enter S.O.A.P.
+ if (!isVet && (!isFolderAdmin || isVetTech))
+ {
+ keys.remove(FieldKey.fromString("s"));
+ keys.remove(FieldKey.fromString("o"));
+ keys.remove(FieldKey.fromString("a"));
+ keys.remove(FieldKey.fromString("p"));
+ }
+
+ return keys;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIDeathFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIDeathFormSection.java
new file mode 100644
index 0000000..931dc98
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIDeathFormSection.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.ParentFormPanelSection;
+
+public class NBRIDeathFormSection extends ParentFormPanelSection
+{
+ public NBRIDeathFormSection()
+ {
+ super("study", "deaths", "Death");
+ setSupportFormSort(false);
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", true);
+ json.put("initCollapsed", false);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIDepartureFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIDepartureFormSection.java
new file mode 100644
index 0000000..7682b6f
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIDepartureFormSection.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+public class NBRIDepartureFormSection extends BaseFormSection
+{
+ public NBRIDepartureFormSection()
+ {
+ super("study", "departure", "Departure", "ehr-gridpanel", true, false, false);
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIExemptionsFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIExemptionsFormSection.java
new file mode 100644
index 0000000..5e10258
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIExemptionsFormSection.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+public class NBRIExemptionsFormSection extends BaseFormSection
+{
+ public NBRIExemptionsFormSection(String label)
+ {
+ super("study", "exemptions", label, "ehr-gridpanel", true, false, false);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIFlagsFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIFlagsFormSection.java
new file mode 100644
index 0000000..555986b
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIFlagsFormSection.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+public class NBRIFlagsFormSection extends BaseFormSection
+{
+ public NBRIFlagsFormSection(String label)
+ {
+ super("study", "flags", label, "ehr-gridpanel", true, false, false);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIGrossPathologyFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIGrossPathologyFormSection.java
new file mode 100644
index 0000000..a1905cd
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIGrossPathologyFormSection.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRIGrossPathologyFormSection extends BaseFormSection
+{
+ public NBRIGrossPathologyFormSection(boolean isChild)
+ {
+ super("study", "grossPathology", "Gross Pathology", "ehr-gridpanel", true, true, true);
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/necropsyGridButtons.js"));
+ if (isChild)
+ {
+ addExtraProperty("parentQueryName", "deaths");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ }
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaults = super.getTbarButtons();
+ defaults.clear();
+ defaults.add("ADDGROSSPATHOLOGY");
+ return defaults;
+ }
+
+ @Override
+ public List getTbarMoreActionButtons()
+ {
+ List defaultButtons = super.getTbarMoreActionButtons();
+ defaultButtons.clear();
+ return defaultButtons;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIHousingFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIHousingFormSection.java
new file mode 100644
index 0000000..4fb8ded
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIHousingFormSection.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+public class NBRIHousingFormSection extends BaseFormSection
+{
+ public NBRIHousingFormSection(boolean collapsible, boolean initCollapsed)
+ {
+ super("study", "housing", "Housing Transfers", "ehr-gridpanel", collapsible, initCollapsed, true);
+ }
+
+ public NBRIHousingFormSection(boolean collapsible, boolean initCollapsed, boolean isChild, String parentQueryName)
+ {
+ this(collapsible, initCollapsed);
+ if (isChild)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRINecropsyFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRINecropsyFormSection.java
new file mode 100644
index 0000000..077b274
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRINecropsyFormSection.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.SimpleFormPanelSection;
+import org.labkey.api.view.template.ClientDependency;
+
+public class NBRINecropsyFormSection extends SimpleFormPanelSection
+{
+
+ public NBRINecropsyFormSection(boolean isChild)
+ {
+ super("study", "necropsy", "Necropsy");
+ setSupportFormSort(false);
+ if (isChild)
+ {
+ addExtraProperty("parentQueryName", "deaths");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ }
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", true);
+ json.put("initCollapsedWithData", true);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRINotesFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRINotesFormSection.java
new file mode 100644
index 0000000..470ccc2
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRINotesFormSection.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+public class NBRINotesFormSection extends BaseFormSection
+{
+ public NBRINotesFormSection(String label)
+ {
+ super("study", "notes", label, "ehr-gridpanel", true, false, false);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIObservationOrdersFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIObservationOrdersFormSection.java
new file mode 100644
index 0000000..9a2171e
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIObservationOrdersFormSection.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRIObservationOrdersFormSection extends BaseFormSection
+{
+
+ public static final String LABEL = "Observation Orders";
+ private final String _dailyObsOption;
+
+ public NBRIObservationOrdersFormSection(String dailyObsOption, boolean initCollapsed)
+ {
+ super("study", "observation_order", LABEL, "ehr-clinicalobservationgridpanel", true, initCollapsed, true);
+
+ _dailyObsOption = dailyObsOption;
+ addClientDependency(ClientDependency.supplierFromPath("ehr/plugin/ClinicalObservationsCellEditing.js"));
+ addClientDependency(ClientDependency.supplierFromPath("ehr/grid/ClinicalObservationGridPanel.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/clinicalObsGridButton.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/buttons/addClinicalObsButton.js"));
+
+ setClientStoreClass("NBRI_EHR.data.ObsOrdersClientStore");
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/ObsOrdersClientStore.js"));
+ }
+
+ public NBRIObservationOrdersFormSection(String dailyObsOption, boolean isChild, String parentQueryName)
+ {
+ this(dailyObsOption, true);
+
+ if (isChild && null != parentQueryName)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/ObsOrderChildClientStore.js"));
+ setClientStoreClass("NBRI_EHR.data.ObsOrderChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaults = super.getTbarButtons();
+
+ if (_dailyObsOption != null)
+ {
+ defaults.add(_dailyObsOption);
+ }
+
+ return defaults;
+
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIPregnancyFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIPregnancyFormSection.java
new file mode 100644
index 0000000..1c9a240
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIPregnancyFormSection.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+public class NBRIPregnancyFormSection extends BaseFormSection
+{
+ public NBRIPregnancyFormSection(String label)
+ {
+ super("study", "pregnancy", label, "ehr-gridpanel", true, false, false);
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProcedureFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProcedureFormSection.java
new file mode 100644
index 0000000..a551418
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProcedureFormSection.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+public class NBRIProcedureFormSection extends BaseFormSection
+{
+ public static final String LABEL = "Procedures";
+
+ public NBRIProcedureFormSection()
+ {
+ super("study", "prc", LABEL, "ehr-gridpanel", true, true, true);
+ }
+
+ public NBRIProcedureFormSection(boolean isChild, String parentQueryName)
+ {
+ this();
+ if (isChild)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProcedureOrderFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProcedureOrderFormSection.java
new file mode 100644
index 0000000..9672016
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProcedureOrderFormSection.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.data.TableInfo;
+import org.labkey.api.query.FieldKey;
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRIProcedureOrderFormSection extends BaseFormSection
+{
+ public static final String LABEL = "Procedure Orders";
+
+ public NBRIProcedureOrderFormSection()
+ {
+ super("study", "prc_order", LABEL, "ehr-gridpanel", true, true, true);
+ }
+
+ public NBRIProcedureOrderFormSection(boolean isChild, String parentQueryName)
+ {
+ this();
+ if (isChild)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+
+ @Override
+ protected List getFieldKeys(TableInfo ti)
+ {
+ List keys = super.getFieldKeys(ti);
+ keys.remove(FieldKey.fromString("procedure"));
+ keys.add(2, FieldKey.fromString("procedure"));
+
+ return keys;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProjectAssignmentFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProjectAssignmentFormSection.java
new file mode 100644
index 0000000..bc00399
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProjectAssignmentFormSection.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+public class NBRIProjectAssignmentFormSection extends BaseFormSection
+{
+ public NBRIProjectAssignmentFormSection(boolean allowAnyId, boolean collapsible, boolean initCollapsed)
+ {
+ super("study", "assignment", "Project Assignment", "ehr-gridpanel", collapsible, initCollapsed, true);
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/AssignmentsClientStore.js"));
+
+ if (allowAnyId)
+ {
+ setClientStoreClass("NBRI_EHR.data.AssignmentsAnyIdClientStore");
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/AssignmentsAnyIdClientStore.js"));
+ }
+ else
+ {
+ setClientStoreClass("NBRI_EHR.data.AssignmentsClientStore");
+ }
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProtocolAssignmentFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProtocolAssignmentFormSection.java
new file mode 100644
index 0000000..4180bb2
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIProtocolAssignmentFormSection.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+public class NBRIProtocolAssignmentFormSection extends BaseFormSection
+{
+ public NBRIProtocolAssignmentFormSection(boolean allowAnyId, boolean collapsible, boolean initCollapsed)
+ {
+ super("study", "protocolAssignment", "Protocol Assignment","ehr-gridpanel", collapsible, initCollapsed, true);
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/AssignmentsClientStore.js"));
+
+ if (allowAnyId)
+ {
+ setClientStoreClass("NBRI_EHR.data.AssignmentsAnyIdClientStore");
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/AssignmentsAnyIdClientStore.js"));
+ }
+ else
+ {
+ setClientStoreClass("NBRI_EHR.data.AssignmentsClientStore");
+ }
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIRearrivalFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIRearrivalFormSection.java
new file mode 100644
index 0000000..ed11060
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIRearrivalFormSection.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+
+public class NBRIRearrivalFormSection extends BaseFormSection
+{
+ public NBRIRearrivalFormSection()
+ {
+ super("study", "arrival", "Rearrivals", "ehr-gridpanel", true, true, true);
+ }
+
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("dataDependentCollapseHeader", true);
+ return json;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITaskFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITaskFormSection.java
new file mode 100644
index 0000000..803eae7
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITaskFormSection.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.json.JSONObject;
+import org.labkey.api.ehr.dataentry.DataEntryFormContext;
+import org.labkey.api.ehr.dataentry.TaskFormSection;
+
+public class NBRITaskFormSection extends TaskFormSection
+{
+ @Override
+ public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements)
+ {
+ JSONObject json = super.toJSON(ctx, includeFormElements);
+ json.put("collapsible", true);
+ json.put("initCollapsed", true);
+ json.put("dataDependentCollapseHeader", false);
+ return json;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITissueDispositionFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITissueDispositionFormSection.java
new file mode 100644
index 0000000..27f7b79
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITissueDispositionFormSection.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRITissueDispositionFormSection extends BaseFormSection
+{
+ public NBRITissueDispositionFormSection(boolean isChild)
+ {
+ super("study", "tissueDisposition", "Tissue Disposition", "ehr-gridpanel", true, true, true);
+ if (isChild)
+ {
+ addExtraProperty("parentQueryName", "deaths");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ }
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaults = super.getTbarButtons();
+ defaults.remove("ADDANIMALS");
+ defaults.remove("TEMPLATE");
+ defaults.add("TEMPLATE_NO_ID_NO_DATE");
+ return defaults;
+ }
+
+ @Override
+ public List getTbarMoreActionButtons()
+ {
+ List defaultButtons = super.getTbarMoreActionButtons();
+ defaultButtons.remove("COPY_IDS");
+ return defaultButtons;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITreatmentGivenFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITreatmentGivenFormSection.java
new file mode 100644
index 0000000..09a8714
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITreatmentGivenFormSection.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRITreatmentGivenFormSection extends BaseFormSection
+{
+ public static final String LABEL = "Medications/Treatments Given";
+
+ public NBRITreatmentGivenFormSection()
+ {
+ super("study", "drug", LABEL, "ehr-gridpanel", true, true, true);
+ setClientStoreClass("NBRI_EHR.data.DrugAdministrationRunsClientStore");
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/DrugAdministrationRunsClientStore.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/DrugAdministrationRunsClientStore.js"));
+ }
+
+ public NBRITreatmentGivenFormSection(boolean isChild, String parentQueryName)
+ {
+ this();
+ if (isChild)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/DrugAdministrationRunsChildClientStore.js"));
+ setClientStoreClass("NBRI_EHR.data.DrugAdministrationRunsChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaultButtons = super.getTbarButtons();
+ int idx = defaultButtons.indexOf("SELECTALL");
+ if (idx > -1)
+ defaultButtons.add(idx + 1, "NBRI_DRUG_AMOUNT_HELPER");
+ else
+ defaultButtons.add("DRUGAMOUNTHELPER");
+ return defaultButtons;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITreatmentOrderFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITreatmentOrderFormSection.java
new file mode 100644
index 0000000..48f35bf
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRITreatmentOrderFormSection.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+import java.util.List;
+
+public class NBRITreatmentOrderFormSection extends BaseFormSection
+{
+ public static final String LABEL = "Medications/Treatments Orders";
+
+ public NBRITreatmentOrderFormSection()
+ {
+ super("study", "treatment_order", LABEL, "ehr-gridpanel", true, true, true);
+ setClientStoreClass("NBRI_EHR.data.DrugAdministrationRunsClientStore");
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/DrugAdministrationRunsClientStore.js"));
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/DrugAdministrationRunsClientStore.js"));
+ }
+
+ public NBRITreatmentOrderFormSection(boolean isChild, String parentQueryName)
+ {
+ this();
+ if (isChild)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/data/DrugAdministrationRunsChildClientStore.js"));
+ setClientStoreClass("NBRI_EHR.data.DrugAdministrationRunsChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+
+ @Override
+ public List getTbarButtons()
+ {
+ List defaultButtons = super.getTbarButtons();
+ int idx = defaultButtons.indexOf("SELECTALL");
+ if (idx > -1)
+ defaultButtons.add(idx + 1, "NBRI_DRUG_AMOUNT_HELPER");
+ else
+ defaultButtons.add("DRUGAMOUNTHELPER");
+ return defaultButtons;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIVitalsFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIVitalsFormSection.java
new file mode 100644
index 0000000..990752c
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIVitalsFormSection.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+public class NBRIVitalsFormSection extends BaseFormSection
+{
+ public static final String LABEL = "Vitals";
+
+ public NBRIVitalsFormSection()
+ {
+ super("study", "vitals", LABEL, "ehr-gridpanel", true, true, true);
+ }
+
+ public NBRIVitalsFormSection(boolean isChild, String parentQueryName)
+ {
+ this();
+ if (isChild)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIWeightFormSection.java b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIWeightFormSection.java
new file mode 100644
index 0000000..ebb3494
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/dataentry/section/NBRIWeightFormSection.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.dataentry.section;
+
+import org.labkey.api.view.template.ClientDependency;
+
+public class NBRIWeightFormSection extends BaseFormSection
+{
+ public NBRIWeightFormSection(boolean initCollapsed, boolean addCopyFromSection)
+ {
+ super("study", "weight", "Weights", "ehr-gridpanel", true, initCollapsed, addCopyFromSection);
+ }
+
+ public NBRIWeightFormSection(boolean initCollapsed, boolean addCopyFromSection, boolean isChild, String parentQueryName)
+ {
+ this(initCollapsed, addCopyFromSection);
+ if (isChild)
+ {
+ addClientDependency(ClientDependency.supplierFromPath("nbri_ehr/model/sources/ParentChild.js"));
+ addConfigSource("ParentChild");
+
+ addClientDependency(ClientDependency.supplierFromPath("ehr/data/ChildClientStore.js"));
+ setClientStoreClass("EHR.data.ChildClientStore");
+ addExtraProperty("parentQueryName", parentQueryName);
+ }
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveAssignmentsDemographicsProvider.java b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveAssignmentsDemographicsProvider.java
new file mode 100644
index 0000000..f1f6139
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveAssignmentsDemographicsProvider.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.demographics;
+
+import org.labkey.api.data.CompareType;
+import org.labkey.api.data.SimpleFilter;
+import org.labkey.api.ehr.demographics.AbstractListDemographicsProvider;
+import org.labkey.api.module.Module;
+import org.labkey.api.query.FieldKey;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ActiveAssignmentsDemographicsProvider extends AbstractListDemographicsProvider
+{
+ public ActiveAssignmentsDemographicsProvider(Module owner)
+ {
+ super(owner, "study", "assignment", "activeAssignments");
+ }
+
+ @Override
+ protected Set getFieldKeys()
+ {
+ Set keys = new HashSet<>();
+ keys.add(FieldKey.fromString("lsid"));
+ keys.add(FieldKey.fromString("Id"));
+ keys.add(FieldKey.fromString("project"));
+ keys.add(FieldKey.fromString("project/name"));
+
+ return keys;
+ }
+
+ @Override
+ public SimpleFilter getFilter(Collection ids)
+ {
+ SimpleFilter filter = super.getFilter(ids);
+ filter.addCondition(FieldKey.fromString("enddate"), null, CompareType.ISBLANK);
+
+ return filter;
+ }
+
+ @Override
+ public boolean requiresRecalc(String schema, String query)
+ {
+ return ("study".equalsIgnoreCase(schema) && "assignment".equalsIgnoreCase(query)) ||
+ ("ehr".equalsIgnoreCase(schema) && "project".equalsIgnoreCase(query));
+ }
+
+ @Override
+ public Collection getKeysToTest()
+ {
+ //for now, simply skip the whole provider. because different records can be active from day to day, this makes validation tricky
+ Set keys = new HashSet<>(super.getKeysToTest());
+ keys.remove(_propName);
+
+ return keys;
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveCasesDemographicsProvider.java b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveCasesDemographicsProvider.java
new file mode 100644
index 0000000..035352e
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveCasesDemographicsProvider.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.labkey.nbri_ehr.demographics;
+
+import org.labkey.api.data.CompareType;
+import org.labkey.api.data.SimpleFilter;
+import org.labkey.api.ehr.demographics.AbstractListDemographicsProvider;
+import org.labkey.api.module.Module;
+import org.labkey.api.query.FieldKey;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ActiveCasesDemographicsProvider extends AbstractListDemographicsProvider
+{
+ public ActiveCasesDemographicsProvider(Module module)
+ {
+ super(module, "study", "cases", "activeCases");
+ }
+
+ @Override
+ protected Set getFieldKeys()
+ {
+ Set keys = new HashSet<>();
+ keys.add(FieldKey.fromString("lsid"));
+ keys.add(FieldKey.fromString("objectid"));
+ keys.add(FieldKey.fromString("Id"));
+ keys.add(FieldKey.fromString("date"));
+ keys.add(FieldKey.fromString("problemCategory"));
+ keys.add(FieldKey.fromString("problemSubcategory"));
+ keys.add(FieldKey.fromString("date"));
+ keys.add(FieldKey.fromString("enddate"));
+ keys.add(FieldKey.fromString("openRemark"));
+ keys.add(FieldKey.fromString("category"));
+ keys.add(FieldKey.fromString("plan"));
+ keys.add(FieldKey.fromString("isActive"));
+ keys.add(FieldKey.fromString("isOpen"));
+
+ return keys;
+ }
+
+ @Override
+ public Collection getKeysToTest()
+ {
+ //for now, simply skip the whole provider. because different records can be active from day to day, this makes validation tricky
+ Set keys = new HashSet<>(super.getKeysToTest());
+ keys.remove(_propName);
+
+ return keys;
+ }
+
+ @Override
+ protected SimpleFilter getFilter(Collection ids)
+ {
+ SimpleFilter filter = super.getFilter(ids);
+ filter.addCondition(FieldKey.fromString("isOpen"), true, CompareType.EQUAL);
+ filter.addCondition(FieldKey.fromString("qcstate/publicData"), true, CompareType.EQUAL);
+
+ return filter;
+ }
+
+ @Override
+ public boolean requiresRecalc(String schema, String query)
+ {
+ return ("study".equalsIgnoreCase(schema) && "cases".equalsIgnoreCase(query));
+ }
+}
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveFlagsDemographicsProvider.java b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveFlagsDemographicsProvider.java
new file mode 100644
index 0000000..d327a44
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveFlagsDemographicsProvider.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.demographics;
+
+import org.labkey.api.data.CompareType;
+import org.labkey.api.data.SimpleFilter;
+import org.labkey.api.ehr.demographics.AbstractListDemographicsProvider;
+import org.labkey.api.module.Module;
+import org.labkey.api.query.FieldKey;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * copied from ehr.ActiveFlagsDemographicsProvider.java
+ * 10/9/2017
+ */
+public class ActiveFlagsDemographicsProvider extends AbstractListDemographicsProvider
+{
+ public ActiveFlagsDemographicsProvider(Module module)
+ {
+ super(module, "study", "flags", "activeFlags");
+ }
+
+ @Override
+ protected Set getFieldKeys()
+ {
+ Set keys = new HashSet<>();
+ keys.add(FieldKey.fromString("lsid"));
+ keys.add(FieldKey.fromString("Id"));
+ keys.add(FieldKey.fromString("date"));
+ keys.add(FieldKey.fromString("enddate"));
+ keys.add(FieldKey.fromString("flag"));
+ keys.add(FieldKey.fromString("flag/category"));
+ keys.add(FieldKey.fromString("flag/value"));
+ keys.add(FieldKey.fromString("performedby"));
+ keys.add(FieldKey.fromString("remark"));
+
+ keys.add(FieldKey.fromString("flag/category/doHighlight"));
+ keys.add(FieldKey.fromString("flag/category/omitFromOverview"));
+
+ return keys;
+ }
+
+ @Override
+ protected SimpleFilter getFilter(Collection ids)
+ {
+ SimpleFilter filter = super.getFilter(ids);
+ filter.addCondition(FieldKey.fromString("isActive"), true, CompareType.EQUAL);
+ filter.addCondition(FieldKey.fromString("qcstate/publicData"), true, CompareType.EQUAL);
+
+ return filter;
+ }
+
+ @Override
+ public Collection getKeysToTest()
+ {
+ //for now, simply skip the whole provider. because different records can be active from day to day, this makes validation tricky
+ Set keys = new HashSet<>(super.getKeysToTest());
+ keys.remove(_propName);
+
+ return keys;
+ }
+}
+
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveTreatmentsDemographicsProvider.java b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveTreatmentsDemographicsProvider.java
new file mode 100644
index 0000000..35327bd
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/ActiveTreatmentsDemographicsProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.demographics;
+
+import org.labkey.api.data.SimpleFilter;
+import org.labkey.api.ehr.demographics.AbstractListDemographicsProvider;
+import org.labkey.api.module.Module;
+import org.labkey.api.query.FieldKey;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ActiveTreatmentsDemographicsProvider extends AbstractListDemographicsProvider
+{
+ public ActiveTreatmentsDemographicsProvider(Module owner)
+ {
+ super(owner, "study", "Treatment Orders", "activeTreatments");
+ _supportsQCState = false;
+ }
+
+ @Override
+ protected Set getFieldKeys()
+ {
+ Set keys = new HashSet<>();
+ keys.add(FieldKey.fromString("lsid"));
+ keys.add(FieldKey.fromString("Id"));
+ keys.add(FieldKey.fromString("code"));
+ keys.add(FieldKey.fromString("code/meaning"));
+ keys.add(FieldKey.fromString("date"));
+ keys.add(FieldKey.fromString("enddate"));
+ keys.add(FieldKey.fromString("performedby"));
+ keys.add(FieldKey.fromString("route"));
+
+ keys.add(FieldKey.fromString("dosage"));
+ keys.add(FieldKey.fromString("dosage_units"));
+ keys.add(FieldKey.fromString("amount"));
+ keys.add(FieldKey.fromString("amount_units"));
+ keys.add(FieldKey.fromString("concentration"));
+ keys.add(FieldKey.fromString("concentration_units"));
+ keys.add(FieldKey.fromString("volume"));
+ keys.add(FieldKey.fromString("vol_units"));
+ keys.add(FieldKey.fromString("amountAndVolume"));
+
+ keys.add(FieldKey.fromString("remark"));
+ keys.add(FieldKey.fromString("frequency"));
+ keys.add(FieldKey.fromString("frequency/meaning"));
+
+ keys.add(FieldKey.fromString("amountWithUnits"));
+ keys.add(FieldKey.fromString("category"));
+
+ return keys;
+ }
+
+ @Override
+ public Collection getKeysToTest()
+ {
+ //for now, simply skip the whole provider. because different records can be active from day to day, this makes validation tricky
+ Set keys = new HashSet<>(super.getKeysToTest());
+ keys.remove(_propName);
+
+ return keys;
+ }
+
+ @Override
+ protected SimpleFilter getFilter(Collection ids)
+ {
+ SimpleFilter filter = super.getFilter(ids);
+ filter.addCondition(FieldKey.fromString("isOpen"), true);
+
+ return filter;
+ }
+}
\ No newline at end of file
diff --git a/nbri_ehr/src/org/labkey/nbri_ehr/demographics/CagematesDemographicsProvider.java b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/CagematesDemographicsProvider.java
new file mode 100644
index 0000000..a3e2f62
--- /dev/null
+++ b/nbri_ehr/src/org/labkey/nbri_ehr/demographics/CagematesDemographicsProvider.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2026 LabKey Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.labkey.nbri_ehr.demographics;
+
+import org.apache.commons.lang3.StringUtils;
+import org.labkey.api.data.Container;
+import org.labkey.api.ehr.demographics.AbstractListDemographicsProvider;
+import org.labkey.api.module.Module;
+import org.labkey.api.query.FieldKey;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class CagematesDemographicsProvider extends AbstractListDemographicsProvider
+{
+ public CagematesDemographicsProvider(Module module)
+ {
+ super(module, "study", "demographicsCagemates", "cagemates");
+ _supportsQCState = false;
+ }
+
+ @Override
+ public boolean isAsync()
+ {
+ return true;
+ }
+
+ @Override
+ protected Set getFieldKeys()
+ {
+ Set keys = new HashSet<>();
+ keys.add(FieldKey.fromString("total"));
+ keys.add(FieldKey.fromString("animals"));
+
+ return keys;
+ }
+
+ @Override
+ public Set getIdsToUpdate(Container c, String id, Map originalProps, Map newProps)
+ {
+ List