From ab56f95dfd51df12361a1ad5ee95e7767f33fcd1 Mon Sep 17 00:00:00 2001 From: Ohsudev <76500320+Ohsudev@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:21:24 -0700 Subject: [PATCH 1/4] 26.3 fb arrival departure permissions (#1748) Created a new Arrival/Departure Admin permission group. --------- Co-authored-by: blasar --- .../onprc_ehr/dataentry/BirthFormType.java | 16 ++++++++++++++++ .../onprc_ehr/dataentry/FlagsFormType.java | 8 +++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BirthFormType.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BirthFormType.java index 54208f1e7..d5b9bc654 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BirthFormType.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BirthFormType.java @@ -21,7 +21,11 @@ import org.labkey.api.ehr.dataentry.TaskForm; import org.labkey.api.ehr.dataentry.TaskFormSection; import org.labkey.api.module.Module; +import org.labkey.api.security.Group; +import org.labkey.api.security.GroupManager; +import org.labkey.api.security.permissions.AdminPermission; import org.labkey.api.view.template.ClientDependency; +import org.labkey.security.xml.GroupEnumType; import java.util.ArrayList; import java.util.Arrays; @@ -106,5 +110,17 @@ protected List getButtonConfigs() return defaultButtons; } + //Added: 6-3-26 R.Blasa + @Override + public boolean isVisible() + { + Group g = GroupManager.getGroup(getCtx().getContainer(), "Arrival-Departure SF", GroupEnumType.SITE); + if (g != null && getCtx().getUser().isInGroup(g.getUserId()) && !getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class)) + { + return false; + } + return super.isVisible(); + } + } diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/FlagsFormType.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/FlagsFormType.java index 362f42144..007fabfd4 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/FlagsFormType.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/FlagsFormType.java @@ -89,7 +89,7 @@ protected boolean canInsert() return canInsert; } - //Added: 8-7-2024 R.Blasa + //Added: 6-3-26 R.Blasa @Override public boolean isVisible() { @@ -98,6 +98,12 @@ public boolean isVisible() { return false; } + Group h = GroupManager.getGroup(getCtx().getContainer(), "Arrival-Departure SF", GroupEnumType.SITE); + if (h != null && getCtx().getUser().isInGroup(h.getUserId()) && !getCtx().getContainer().hasPermission(getCtx().getUser(), AdminPermission.class)) + { + return false; + } return super.isVisible(); } + } From e48ae38b11d88aa35affc59e767d1c3a17b6e640 Mon Sep 17 00:00:00 2001 From: Ohsudev <76500320+Ohsudev@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:21:59 -0700 Subject: [PATCH 2/4] 26.3 fb bsu rounds template (#1742) Converted to Prime 26.3 schemes --------- Co-authored-by: blasar --- .../onprc_ehr/model/sources/BehaviorRounds.js | 7 + .../window/AddBehaviorCasesWindow.js | 198 ++++++++++++++++++ ...BehaviorRoundsObservationsFormSection.java | 4 +- .../test/tests/onprc_ehr/ONPRC_EHRTest.java | 2 +- 4 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 onprc_ehr/resources/web/onprc_ehr/window/AddBehaviorCasesWindow.js diff --git a/onprc_ehr/resources/web/onprc_ehr/model/sources/BehaviorRounds.js b/onprc_ehr/resources/web/onprc_ehr/model/sources/BehaviorRounds.js index 5b2e316a9..8d5840d5b 100644 --- a/onprc_ehr/resources/web/onprc_ehr/model/sources/BehaviorRounds.js +++ b/onprc_ehr/resources/web/onprc_ehr/model/sources/BehaviorRounds.js @@ -48,6 +48,13 @@ EHR.model.DataModelManager.registerMetadata('BehaviorRounds', { columnConfig: { editable: false } + }, + caseid: { + hidden: false, + columnConfig: { + width: 10, + editable: false + } } } } diff --git a/onprc_ehr/resources/web/onprc_ehr/window/AddBehaviorCasesWindow.js b/onprc_ehr/resources/web/onprc_ehr/window/AddBehaviorCasesWindow.js new file mode 100644 index 000000000..212abe495 --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/window/AddBehaviorCasesWindow.js @@ -0,0 +1,198 @@ +/* Copyright (c) 2014-2019 LabKey Corporation +* +* Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +/** + * This window will allow users to query open cases and add records to a task based on them + */ +Ext4.define('ONPRC_EHR.window.AddBehaviorCasesWindow', { + extend: 'EHR.window.AddSurgicalCasesWindow', + caseCategory: 'Behavior', + templateName: null, + + allowNoSelection: true, + showAssignedVetCombo: false, + showAllowOpen: true, + defaultRemark: 'BSU Rounds Entered', + + getCases: function(button){ + Ext4.Msg.wait("Loading..."); + this.hide(); + + var casesFilterArray = this.getCasesFilterArray(); + var obsFilterArray = this.getBaseFilterArray(); + obsFilterArray.push(LABKEY.Filter.create('caseCategory', this.caseCategory, LABKEY.Filter.Types.EQUAL)); + var includeOpen = this.down('#includeOpen') ? this.down('#includeOpen').getValue() : false; + if (includeOpen){ + obsFilterArray.push(LABKEY.Filter.create('caseIsOpen', true, LABKEY.Filter.Types.EQUAL)); + } + else { + obsFilterArray.push(LABKEY.Filter.create('caseIsActive', true, LABKEY.Filter.Types.EQUAL)); + } + + //find distinct animals matching criteria + var multi = new LABKEY.MultiRequest(); + + multi.add(LABKEY.Query.selectRows, { + requiredVersion: 9.1, + schemaName: 'study', + queryName: 'latestObservationsForCase', + columns: 'Id,date,category,area,observation,remark,caseid', + filterArray: obsFilterArray, + scope: this, + success: function(results){ + this.obsResults = results; + }, + failure: LDK.Utils.getErrorCallback() + }); + + multi.add(LABKEY.Query.selectRows, { + requiredVersion: 9.1, + schemaName: 'study', + queryName: 'cases', + sort: 'Id/curLocation/location,Id,remark,allProblemCategories', + columns: 'Id,objectid,remark,allProblemCategories', + filterArray: casesFilterArray, + scope: this, + success: function(results){ + this.casesResults = results; + }, + failure: LDK.Utils.getErrorCallback() + }); + + multi.send(this.onSuccess, this); + }, + + //@Override. this is to skip the duplicate case check + addRecords: function(records){ + this.doAddRecords(records); + }, + + //@Override. this is to skip the duplicate case check + doAddRecords: function(records){ + this.processObservations(records); + }, + + //apply previous observations, or inser a blank obs record. + processObservations: function(caseRecords){ + //find all distinct IDs with cases. + var distinctCaseIds = []; + if (caseRecords && caseRecords.length){ + Ext4.Array.forEach(caseRecords, function(cr){ + if (distinctCaseIds.indexOf(cr.get('caseid') == -1)){ + distinctCaseIds.push(cr.get('caseid')); + } + }, this); + } + + var previousObsMap = {}; + if (this.obsResults && this.obsResults.rows && this.obsResults.rows.length){ + Ext4.Array.forEach(this.obsResults.rows, function(sr){ + //reset variable + var newobservation = ''; + var newremark = ''; + var row = new LDK.SelectRowsRow(sr); + newobservation = row.getValue('category'); + newremark = row.getValue('remark'); + + //note: this has been changed to ensure 1 row per case + var key = row.getValue('caseid'); + if (!previousObsMap[key]) + previousObsMap[key] = []; + + previousObsMap[key].push({ + Id: row.getValue('Id'), + date: this.recordData.date, + performedby: this.recordData.performedby, + caseid: row.getValue('caseid'), + category: row.getValue('category'), + area: row.getValue('area'), + allProblemCategories:row.getValue('allProblemCategories'), + remark: row.getValue('remark') + }); + if (newobservation == 'Alopecia Score' && (newremark == null || newremark == '')) { + previousObsMap[key].push({ + Id: row.getValue('Id'), + date: this.recordData.date, + performedby: this.recordData.performedby, + caseid: row.getValue('caseid'), + category: 'Alopecia Regrowth', + area: row.getValue('area'), + allProblemCategories:row.getValue('allProblemCategories') + + }); + + } + }, this); + } + + var obsRecords = []; + var obsStore = this.targetStore.storeCollection.getClientStoreByName('Clinical Observations'); + LDK.Assert.assertNotEmpty('Unable to find Clinical Observations store', obsStore); + + var treatmentRecords = []; + var treatmentStore = this.targetStore.storeCollection.getClientStoreByName('Drug Administration'); + LDK.Assert.assertNotEmpty('Unable to find Drug Administration store', treatmentStore); + + Ext4.Array.forEach(caseRecords, function(cr){ + if (previousObsMap[cr.get('caseid')]){ + Ext4.Array.forEach(previousObsMap[cr.get('caseid')], function(r){ + r = Ext4.apply(r, { + 'Id/curLocation/location': cr.get('Id/curLocation/location') + }); + + obsRecords.push(obsStore.createModel(r)); + }, this); + } + else { + obsRecords.push(obsStore.createModel({ + 'Id/curLocation/location': cr.get('Id/curLocation/location'), + Id: cr.get('Id'), + date: this.recordData.date, + performedby: this.recordData.performedby, + caseid: cr.get('caseid') + })); + } + + treatmentRecords.push(treatmentStore.createModel({ + Id: cr.get('Id'), + caseid: cr.get('caseid'), + date: this.recordData.date, + performedby: this.recordData.performedby + })); + }, this); + + if (obsRecords.length){ + obsStore.add(obsRecords); + } + + if (treatmentRecords.length){ + treatmentStore.add(treatmentRecords); + } + + Ext4.Msg.hide(); + this.close(); + } +}); + +EHR.DataEntryUtils.registerGridButton('ADDBEHAVIORCASESAMENDED', function(config){ + return Ext4.Object.merge({ + text: 'Add Open Cases', + tooltip: 'Click to automatically add animals with open cases', + handler: function(btn){ + var grid = btn.up('gridpanel'); + if(!grid.store || !grid.store.hasLoaded()){ + console.log('no store or store hasnt loaded'); + return; + } + + var cellEditing = grid.getPlugin('cellediting'); + if(cellEditing) + cellEditing.completeEdit(); + + Ext4.create('ONPRC_EHR.window.AddBehaviorCasesWindow', { + targetStore: grid.store + }).show(); + } + }, config); +}); diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BehaviorRoundsObservationsFormSection.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BehaviorRoundsObservationsFormSection.java index 399a70462..e082cf1fa 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BehaviorRoundsObservationsFormSection.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BehaviorRoundsObservationsFormSection.java @@ -34,14 +34,14 @@ public BehaviorRoundsObservationsFormSection() addClientDependency(ClientDependency.supplierFromPath("ehr/window/AddClinicalCasesWindow.js")); addClientDependency(ClientDependency.supplierFromPath("ehr/window/AddSurgicalCasesWindow.js")); - addClientDependency(ClientDependency.supplierFromPath("ehr/window/AddBehaviorCasesWindow.js")); + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/window/AddBehaviorCasesWindow.js")); } @Override public List getTbarButtons() { List defaultButtons = super.getTbarButtons(); - defaultButtons.add(0, "ADDBEHAVIORCASES"); + defaultButtons.add(0, "ADDBEHAVIORCASESAMENDED"); return defaultButtons; } diff --git a/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest.java b/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest.java index b6682a733..e8c58b0ef 100644 --- a/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest.java +++ b/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest.java @@ -1840,7 +1840,7 @@ public void testBehaviorRounds() throws Exception //just load all behavior cases waitAndClick(Ext4Helper.Locators.windowButton("Add Open Behavior Cases", "Submit")); waitForElementToDisappear(caseWindow); - obsGrid.waitForRowCount(1); + obsGrid.waitForRowCount(2); Assert.assertEquals("Alopecia Score", obsGrid.getFieldValue(1, "category")); Assert.assertEquals("Id field should not be editable.", "on", obsGrid.getCell(1, "Id") .findElement(getDriver()).findElement(By.tagName("div")).getDomAttribute("unselectable")); From 82541553407a7e116044f22faf63f2b44268da27 Mon Sep 17 00:00:00 2001 From: Ohsudev <76500320+Ohsudev@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:22:26 -0700 Subject: [PATCH 3/4] Modified Birth input form so that dam is now labelled as "Observed Dam" (#1738) Modified Birth Entry form so that the Dam data is labelled as "Observed Dam" Co-authored-by: blasar --- onprc_ehr/resources/web/onprc_ehr/model/sources/Birth_Entry.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/onprc_ehr/resources/web/onprc_ehr/model/sources/Birth_Entry.js b/onprc_ehr/resources/web/onprc_ehr/model/sources/Birth_Entry.js index 270f4b92e..2badfec3d 100644 --- a/onprc_ehr/resources/web/onprc_ehr/model/sources/Birth_Entry.js +++ b/onprc_ehr/resources/web/onprc_ehr/model/sources/Birth_Entry.js @@ -17,7 +17,8 @@ EHR.model.DataModelManager.registerMetadata('Birth_Entry', { fixed: true, width: 180 } - } + }, + dam: {header: 'Observed Dam'} } } }); From 8701db26191998306a5341a132118596a110bdbb Mon Sep 17 00:00:00 2001 From: Ohsudev <76500320+Ohsudev@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:22:57 -0700 Subject: [PATCH 4/4] 26.3 fb room layout project (#1736) Modified "Cage Room Layout" to include "Unavailable Location" cage type as an option. --------- Co-authored-by: blasar --- .../queries/ehr_lookups/connectedCages.sql | 6 +-- .../web/onprc_ehr/panel/RoomLayoutPanel.js | 51 ++++++++++++------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/onprc_ehr/resources/queries/ehr_lookups/connectedCages.sql b/onprc_ehr/resources/queries/ehr_lookups/connectedCages.sql index 11b12359e..b80930159 100644 --- a/onprc_ehr/resources/queries/ehr_lookups/connectedCages.sql +++ b/onprc_ehr/resources/queries/ehr_lookups/connectedCages.sql @@ -54,11 +54,11 @@ FROM ehr_lookups.cage c --for the next 2 horizontal joins, use the highest effective row, determined above --find the highest cage with a non-separating divider -LEFT JOIN ehr_lookups.cage joined ON (joined.cage_type != 'No Cage' and c.status.value != 'Unavailable' and c.room = joined.room and c.cagePosition.row = joined.cagePosition.row and joined.divider.countAsSeparate = false and c.cagePosition.columnIdx > joined.cagePosition.columnIdx) +LEFT JOIN ehr_lookups.cage joined ON ((joined.cage_type != 'No Cage' or joined.cage_type != 'Unavailable Location') and c.status.value != 'Unavailable' and c.room = joined.room and c.cagePosition.row = joined.cagePosition.row and joined.divider.countAsSeparate = false and c.cagePosition.columnIdx > joined.cagePosition.columnIdx) --find the highest cage with a separating divider -LEFT JOIN ehr_lookups.cage sep ON (sep.cage_type != 'No Cage' and c.room = sep.room and c.cagePosition.row = sep.cagePosition.row and sep.divider.countAsSeparate = true and c.cagePosition.columnIdx > sep.cagePosition.columnIdx) +LEFT JOIN ehr_lookups.cage sep ON ((sep.cage_type != 'No Cage' or sep.cage_type != 'Unavailable Location') and c.room = sep.room and c.cagePosition.row = sep.cagePosition.row and sep.divider.countAsSeparate = true and c.cagePosition.columnIdx > sep.cagePosition.columnIdx) -WHERE c.cage_type != 'No Cage' +WHERE (c.cage_type != 'No Cage' or c.cage_type != 'Unavailable Location') GROUP BY c.room, c.cagePosition.row, c.cage, c.cagePosition.columnIdx, c.divider, c.divider.countAsSeparate, c.cage_type \ No newline at end of file diff --git a/onprc_ehr/resources/web/onprc_ehr/panel/RoomLayoutPanel.js b/onprc_ehr/resources/web/onprc_ehr/panel/RoomLayoutPanel.js index b331c6582..e8cf1f826 100644 --- a/onprc_ehr/resources/web/onprc_ehr/panel/RoomLayoutPanel.js +++ b/onprc_ehr/resources/web/onprc_ehr/panel/RoomLayoutPanel.js @@ -184,8 +184,8 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { var rooms = Ext4.Object.getKeys(roomMap).sort(); var dividerWidth = 3; - var height = 75; - var cageWidth = 60; + var height = 115; + var cageWidth = 78; //Modified: 5-27-2026 var hasCages = false; Ext4.each(rooms, function(room, roomIdx){ @@ -285,7 +285,7 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { } var animalItems = []; - if (animals.length > 8){ //Modified: 7-5-2018 R.Blasa + if (animals.length > 8) { //Modified: 7-5-2018 R.Blasa animalItems.push({ html: '' + animals.length + ' animals', border: false, @@ -297,7 +297,7 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { else if (animals.length){ Ext4.each(animals, function(animal){ animalItems.push({ - html: '' + animal + '' + (config.animalMap[animal] ? ': ' + Ext4.util.Format.round(config.animalMap[animal].getValue('Id/mostRecentWeight/mostRecentWeight'), 1) : '') + '', + html: '' + animal + '' + (config.animalMap[animal] ? ': ' + Ext4.util.Format.round(config.animalMap[animal].getValue('Id/mostRecentWeight/mostRecentWeight'), 1) : '') + '', animal: animal, border: false, bodyStyle: { @@ -342,30 +342,32 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { var prevIsSeparate = prevDividerInfo.countAsSeparate; var prevAnimals = prevCage.get('totalAnimals/animals'); - if (!prevIsSeparate && !Ext4.isEmpty(cageAnimals)) + if (!prevIsSeparate && !Ext4.isEmpty(cageAnimals) && cageType != 'Unavailable Location') bgColor = 'red'; - if (prevIsSeparate && Ext4.isEmpty(cageAnimals)) + if (prevIsSeparate && Ext4.isEmpty(cageAnimals) && cageType != 'Unavailable Location') bgColor = emptyCageColor; - if (!prevIsSeparate && Ext4.isEmpty(cageAnimals) && Ext4.isEmpty(prevAnimals)) + if (!prevIsSeparate && Ext4.isEmpty(cageAnimals) && Ext4.isEmpty(prevAnimals) && cageType != 'Unavailable Location') bgColor = emptyCageColor; if (cageType == 'No Cage'){ - if (!Ext4.isEmpty(cageAnimals)) + if (!Ext4.isEmpty(cageAnimals) && cageType != 'Unavailable Location') bgColor = 'red'; else { //NOTE: this used to use no color. i'm not sure why bgColor = 'grey'; } } - else if (status == 'Unavailable') - { + else if (cageType == 'Unavailable Location') { + bgColor = 'white'; + } + else if (status == 'Unavailable') { bgColor = 'yellow'; } else if (colorcage == 'Transfer Pending') { - if (!Ext4.isEmpty(cageAnimals)) + if (!Ext4.isEmpty(cageAnimals) && cageType != 'Unavailable Location') bgColor = ''; else bgColor = 'orange'; @@ -373,7 +375,7 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { } else if (colorcage == 'Held for Colony') { - if (!Ext4.isEmpty(cageAnimals)) + if (!Ext4.isEmpty(cageAnimals) && cageType != 'Unavailable Location') bgColor = ''; else bgColor = '#54daff'; @@ -381,25 +383,32 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { } else if (colorcage == 'Empty') { - if (Ext4.isEmpty(cageAnimals)) + if (Ext4.isEmpty(cageAnimals) && cageType != 'Unavailable Location') bgColor = emptyCageColor; } } else { //flag cage if empty - if (Ext4.isEmpty(row.get('totalAnimals/animals'))){ + if (Ext4.isEmpty(row.get('totalAnimals/animals')) && cageType != 'Unavailable Location'){ bgColor = emptyCageColor; } //also if no cage present if (cageType == 'No Cage'){ - if (!Ext4.isEmpty(cageAnimals)) + if (!Ext4.isEmpty(cageAnimals) && cageType != 'Unavailable Location') bgColor = 'red'; else bgColor = 'grey'; } else if (status == 'Unavailable') + { + if (bgColor = emptyCageColor) { + + bgColor = 'white'; + } + } + else if (status == 'Unavailable' && cageType != 'Unavailable Location') { bgColor = 'yellow'; } @@ -421,7 +430,7 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { } else if (colorcage == 'Empty') { - if (Ext4.isEmpty(cageAnimals)) + if (Ext4.isEmpty(cageAnimals) && cageType != 'Unavailable Location') bgColor = emptyCageColor; } @@ -430,6 +439,13 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { var type = row.get('cage_type'); var cageType = config.cageTypeMap[row.get('cage_type')] || {}; var suffix = cageType.abbreviation || ''; + var html_string = ''; + if (type == 'No Cage') + html_string = 'No Cage'; + + if (type == 'Unavailable Location') + html_string = 'Unavailable Location'; + rowItems.push({ border: false, style: { @@ -453,7 +469,7 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { //Modified: 4-8-2020 R.Blasa Contains symbol representing divider types items: [{ - html: row.get('cage_type') == 'No Cage' ? 'No Cage' : ('' + ri + colIdx + '' + (cageType.sqft ? ' (' + (cageType.sqft / cageType.cageslots)+ suffix + ')' : '') + (dividerInfo.displaychar ? ' [' + (dividerInfo.displaychar) + ']' : '') + ''), + html: type == html_string ? html_string : ('' + ri + colIdx + '' + (cageType.sqft ? ' (' + (cageType.sqft / cageType.cageslots)+ suffix + ')' : '') + (dividerInfo.displaychar ? ' [' + (dividerInfo.displaychar) + ']' : '') + ''), bodyStyle: { 'background-color': 'transparent' }, @@ -634,6 +650,7 @@ Ext4.define('ONPRC.panel.RoomLayoutPanel', { Ext4.apply(this, { border: false, itemId: 'roomLayoutPanel', + width: 1450, defaults: { border: false },