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..1fb129544 --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/window/AddBehaviorCasesWindow.js @@ -0,0 +1,202 @@ +/* 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'), + //dont copy value + //observation: row.getValue('observation'), + 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') + //dont copy value + //observation: row.getValue('observation'), + //remark: row.getValue('remark') + }); + + } + }, 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 bdf812dd0..b6682a733 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 @@ -72,6 +72,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.function.Function; import static org.junit.Assert.assertEquals; @@ -557,6 +558,29 @@ public void testArrivalApi() throws Exception Assert.assertEquals(1, demographicsSelect.execute(getApiHelper().getConnection(), getContainerPath()).getRowCount().intValue()); } + @Test + public void testSubmitButtonsDisabledDuringValidation() throws Exception + { + List allIds = createTemporaryValidationAnimals(30); + + try + { + log("Bulk adding animals in treatment orders for temporary test animals"); + _helper.goToTaskForm("Medications/Diet", false); + Ext4GridRef treatmentGrid = _helper.getExt4GridForFormSection("Medication/Treatment Orders"); + addBatchIdsToGrid(treatmentGrid, allIds); + + assertActionsDisabledDuringValidation(); + + treatmentGrid.waitForRowCount(allIds.size()); + _helper.discardForm(); + } + finally + { + deleteTemporaryValidationAnimals(allIds); + } + } + @Test public void testCustomActions() throws Exception { @@ -1103,8 +1127,9 @@ public void testExamEntry() throws Exception Assert.assertEquals(remark, bloodGrid.getFieldValue(3, "remark")); Assert.assertEquals(remark, bloodGrid.getFieldValue(4, "remark")); - waitAndClickAndWait(_helper.getDataEntryButton("Save & Close")); - waitForElement(Locator.tagWithText("a", "Enter New Data")); + waitForDataEntryButtonEnabled("Save & Close", WAIT_FOR_PAGE * 2); + waitAndClick(_helper.getDataEntryButton("Save & Close")); + waitForElement(Locator.tagWithText("a", "Enter New Data"), WAIT_FOR_PAGE * 2); } @Test @@ -1488,6 +1513,9 @@ public void testPathology() //test SNOMED codes _ext4Helper.clickExt4Tab("Histologic Findings"); Ext4GridRef histologyGrid = _helper.getExt4GridForFormSection("Histologic Findings"); + // The custom "Add Record" handler returns early until the tab's grid store finishes loading. + waitFor(() -> (Boolean)histologyGrid.getFnEval("return !!this.store && (!this.store.hasLoaded || this.store.hasLoaded());"), + "Histologic Findings grid store did not finish loading", WAIT_FOR_JAVASCRIPT); _helper.addRecordToGrid(histologyGrid, "Add Record"); scrollIntoView(histologyGrid.getCell(1,7), true); waitAndClick(histologyGrid.getCell(1, 7)); @@ -1566,7 +1594,9 @@ public void testPathology() waitForElementToDisappear(deathWindow, 20000); //saving can take longer than default 10 seconds waitForElementToDisappear(Locator.tagContainingText("div", "Saving Changes...").notHidden()); - waitAndClickAndWait(_helper.getDataEntryButton("Save & Close")); + waitForDataEntryButtonEnabled("Save & Close", WAIT_FOR_PAGE * 2); + waitAndClick(_helper.getDataEntryButton("Save & Close")); + waitForElement(Locator.tagWithText("a", "Enter New Data"), WAIT_FOR_PAGE * 2); //make new necropsy, copy from previous _helper.goToTaskForm("Necropsy", false); @@ -1971,6 +2001,107 @@ private void setNecropsyFormElement(String id, String value) assertEquals(value, getFormElement(loc)); } + private void addBatchIdsToGrid(Ext4GridRef grid, List ids) + { + grid.clickTbarButton("Add Batch"); + waitForElement(Ext4Helper.Locators.window("Choose Animals")); + Ext4FieldRef.getForLabel(this, "Id(s)").setValue(StringUtils.join(ids, ";")); + + waitAndClick(Ext4Helper.Locators.window("Choose Animals").append(Ext4Helper.Locators.ext4Button("Submit"))); + grid.waitForRowCount(ids.size()); + } + + private void assertActionsDisabledDuringValidation() + { + List buttonTexts = Arrays.asList("Save Draft", "Save & Close", "Submit For Review", "Submit Final"); + List menuItemTexts = Arrays.asList("Submit And Reload", "Force Submit"); + Locator.XPathLocator validationIndicator = Locator.tagContainingText("span", "Validating...").notHidden(); + waitFor(() -> !validationIndicator.findElements(getDriver()).isEmpty(), + "Validation indicator never appeared", WAIT_FOR_PAGE); + + for (String buttonText : buttonTexts) + { + List buttons = _ext4Helper.componentQuery("button[text='" + buttonText + "']", Ext4CmpRef.class); + if (!buttons.isEmpty()) + { + waitFor(() -> Boolean.TRUE.equals(buttons.get(0).getEval("isDisabled() == arguments[0]", true)), + buttonText + " did not become disabled during validation", WAIT_FOR_PAGE); + } + } + + waitAndClick(_helper.getDataEntryButton("More Actions")); + waitForElement(Ext4Helper.Locators.menu().notHidden()); + for (String menuItemText : menuItemTexts) + { + waitForElement(Ext4Helper.Locators.menuItemDisabled(menuItemText).notHidden()); + } + waitAndClick(_helper.getDataEntryButton("More Actions")); + waitForElementToDisappear(Ext4Helper.Locators.menu().notHidden()); + + waitFor(() -> validationIndicator.findElements(getDriver()).isEmpty(), + "Validation indicator did not disappear", WAIT_FOR_PAGE * 2); + waitForText(WAIT_FOR_PAGE * 2, "WARN"); + waitForText(WAIT_FOR_PAGE * 2, "ERROR"); + } + + private List createTemporaryValidationAnimals(int count) throws Exception + { + String seed = Long.toString(System.currentTimeMillis()); + seed = seed.substring(Math.max(0, seed.length() - 6)); + + String[] species = {"Rhesus", "Cynomolgus", "Marmoset"}; + String[] fields = {"Id", "Species", "Birth", "Gender", "date", "calculated_status", "objectid"}; + Object[][] data = new Object[count][]; + List ids = new ArrayList<>(); + + for (int i = 0; i < count; i++) + { + String id = "VAL" + seed + String.format("%02d", i + 1); + ids.add(id); + data[i] = new Object[]{ + id, + species[i % species.length], + new Date().toString(), + i % 2 == 0 ? getMale() : getFemale(), + new Date(), + "Alive", + UUID.randomUUID().toString() + }; + } + + getApiHelper().deleteAllRecords("study", "demographics", new Filter("Id", StringUtils.join(ids, ";"), Filter.Operator.IN)); + getApiHelper().doSaveRows(DATA_ADMIN.getEmail(), + getApiHelper().prepareInsertCommand("study", "demographics", "lsid", fields, data), + getExtraContext()); + cacheIds(ids); + + return ids; + } + + private void deleteTemporaryValidationAnimals(List ids) throws Exception + { + if (ids.isEmpty()) + { + return; + } + + getApiHelper().deleteAllRecords("study", "demographics", new Filter("Id", StringUtils.join(ids, ";"), Filter.Operator.IN)); + } + + private void waitForDataEntryButtonEnabled(String buttonText, int timeout) + { + waitFor(() -> { + List buttons = _ext4Helper.componentQuery("button[text='" + buttonText + "']", Ext4CmpRef.class); + if (buttons.isEmpty()) + { + return false; + } + + return Boolean.TRUE.equals(buttons.get(0).getEval("isDisabled() == arguments[0]", false)); + }, + "Button did not become enabled: " + buttonText, timeout); + } + @Override protected String getAnimalHistoryPath() {