diff --git a/ONPRC_EHR_ComplianceDB/resources/queries/EmployeeData/ComplianceProcedureRecentTests/.qview.xml b/ONPRC_EHR_ComplianceDB/resources/queries/EmployeeData/ComplianceProcedureRecentTests/.qview.xml new file mode 100644 index 000000000..c2b48d9ac --- /dev/null +++ b/ONPRC_EHR_ComplianceDB/resources/queries/EmployeeData/ComplianceProcedureRecentTests/.qview.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/onprc_ehr/resources/queries/study/birth.query.xml b/onprc_ehr/resources/queries/study/birth.query.xml index 727fb0953..478366b3c 100644 --- a/onprc_ehr/resources/queries/study/birth.query.xml +++ b/onprc_ehr/resources/queries/study/birth.query.xml @@ -51,13 +51,7 @@ - - Dam - - - - - + Birth Record Dam /ehr/participantView.view?participantId=${dam} true diff --git a/onprc_ehr/resources/queries/study/demographicsParents.query.xml b/onprc_ehr/resources/queries/study/demographicsParents.query.xml index e3ef57f38..57a19f315 100644 --- a/onprc_ehr/resources/queries/study/demographicsParents.query.xml +++ b/onprc_ehr/resources/queries/study/demographicsParents.query.xml @@ -31,6 +31,17 @@ id + + Surrogate Dam + + study + animal + id + + + + Foster Dam Type + Genetic Dam @@ -39,9 +50,17 @@ id + + Genetic Dam Type + Number of Parents Known + + + Observed Dam + + diff --git a/onprc_ehr/resources/queries/study/demographicsParents.sql b/onprc_ehr/resources/queries/study/demographicsParents.sql index 91f7029d2..f282b8bd2 100644 --- a/onprc_ehr/resources/queries/study/demographicsParents.sql +++ b/onprc_ehr/resources/queries/study/demographicsParents.sql @@ -16,7 +16,7 @@ SELECT d.id, - coalesce(p2.parent, b.dam) as dam, + coalesce(p2.parent, b.dam, p5.parent) as dam, CASE WHEN p2.parent IS NOT NULL THEN p2.method WHEN b.dam IS NOT NULL THEN 'Observed' @@ -29,26 +29,31 @@ SELECT WHEN b.sire IS NOT NULL THEN 'Observed' ELSE null END as sireType, - p3.parent as fosterMom, - p3.method as fosterType, + p2.parent as geneticdam, + p2.method as geneticdamtype, + p3.parent as fostermom, + p3.method as fostertype, + p4.parent as surrogatedam, + coalesce(b.dam,p5.parent) as observeddam, (CASE WHEN p3.parent IS NOT NULL THEN 1 ELSE 0 END + + CASE WHEN p4.parent IS NOT NULL THEN 1 ELSE 0 END + CASE WHEN coalesce(p2.parent, b.dam) IS NOT NULL THEN 1 ELSE 0 END + CASE WHEN coalesce(p1.parent, b.sire) IS NOT NULL THEN 1 ELSE 0 END) as numParents, - greatest(d.modified, p1.modified, p2.modified, p3.modified, b.modified) as modified + greatest(d.modified, p1.modified, p2.modified, p3.modified, p4.modified,p5.modified, b.modified) as modified FROM study.demographics d LEFT JOIN ( select p1.id, min(p1.method) as method, max(p1.parent) as parent, max(p1.modified) as modified FROM study.parentage p1 - WHERE (p1.method = 'Genetic' OR p1.method = 'Provisional Genetic') AND p1.relationship = 'Sire' AND p1.enddate IS NULL + WHERE p1.method in ('Genetic','Provisional Genetic') AND p1.relationship = 'Sire' AND p1.enddate IS NULL GROUP BY p1.Id ) p1 ON (d.Id = p1.id) LEFT JOIN ( select p2.id, min(p2.method) as method, max(p2.parent) as parent, max(p2.modified) as modified FROM study.parentage p2 - WHERE (p2.method = 'Genetic' OR p2.method = 'Provisional Genetic') AND p2.relationship = 'Dam' AND p2.enddate IS NULL + WHERE p2.method in ('Genetic','Provisional Genetic') AND p2.relationship = 'Dam' AND p2.enddate IS NULL GROUP BY p2.Id ) p2 ON (d.Id = p2.id) @@ -58,5 +63,19 @@ LEFT JOIN ( WHERE p3.relationship = 'Foster Dam' AND p3.enddate IS NULL GROUP BY p3.Id ) p3 ON (d.Id = p3.id) +LEFT JOIN ( + select p4.id, min(p4.method) as method, max(p4.parent) as parent, max(p4.modified) as modified + FROM study.parentage p4 + WHERE p4.relationship = 'Surrogate Dam' AND p4.enddate IS NULL + GROUP BY p4.Id +) p4 ON (d.Id = p4.id) + +LEFT JOIN ( + select p5.id, min(p5.method) as method, max(p5.parent) as parent, max(p5.modified) as modified + FROM study.parentage p5 + WHERE p5.relationship = 'Observed' AND p5.enddate IS NULL + GROUP BY p5.Id +) p5 ON (d.Id = p5.id) + LEFT JOIN study.birth b ON (b.id = d.id) diff --git a/onprc_ehr/resources/queries/study/demographicsParents/.qview.xml b/onprc_ehr/resources/queries/study/demographicsParents/.qview.xml index 01e76b9af..c55f9e699 100644 --- a/onprc_ehr/resources/queries/study/demographicsParents/.qview.xml +++ b/onprc_ehr/resources/queries/study/demographicsParents/.qview.xml @@ -5,9 +5,13 @@ + + - + + + diff --git a/onprc_ehr/resources/web/onprc_ehr/model/sources/CaseMgmt.js b/onprc_ehr/resources/web/onprc_ehr/model/sources/CaseMgmt.js new file mode 100644 index 000000000..7a9eb120b --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/model/sources/CaseMgmt.js @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +EHR.model.DataModelManager.registerMetadata('CaseMgmt', { + byQuery: { + 'study.clinremarks': { + Id: { + columnConfig: { + getEditor: function(rec){ + if (rec && rec.get('caseid')){ + return false; + } + return { + xtype: 'ehr-animalfield', + dataIndex: 'Id' + }; + } + }, + formEditorConfig: { + listeners: { + afterrender: function(field){ + var TOOLTIP = 'Refresh the form to enter data for a different animal.'; + var syncDisabledStyle = function(readOnly){ + var inputEl = field.inputEl; + if (inputEl){ + inputEl.setStyle({ + 'background-color': readOnly ? '#f0f0f0' : '', + color: readOnly ? '#666666' : '', + cursor: readOnly ? 'not-allowed' : '' + }); + } + }; + var setTooltip = function(readOnly){ + var el = field.getEl(); + if (el){ + el.set({'data-qtip': readOnly ? TOOLTIP : ''}); + } + }; + var syncReadOnly = function(){ + var rec = EHR.DataEntryUtils.getBoundRecord(field); + var readOnly = !!(rec && rec.get('caseid')); + field.setReadOnly(readOnly); + syncDisabledStyle(readOnly); + setTooltip(readOnly); + }; + syncReadOnly(); + var formPanel = field.up('ehr-formpanel'); + if (formPanel){ + field.mon(formPanel, 'bindrecord', syncReadOnly, field, {buffer: 50}); + } + if (EHR.DemographicsCache){ + field.mon(EHR.DemographicsCache, 'casecreated', function(animalId){ + var rec = EHR.DataEntryUtils.getBoundRecord(field); + if (rec && rec.get('Id') === animalId){ + field.setReadOnly(true); + syncDisabledStyle(true); + setTooltip(true); + } + }, field); + } + } + } + } + } + } + } +}); diff --git a/onprc_ehr/resources/web/onprc_ehr/model/sources/ClinicalReport.js b/onprc_ehr/resources/web/onprc_ehr/model/sources/ClinicalReport.js index 5d2f3e1b5..2b4a9191d 100644 --- a/onprc_ehr/resources/web/onprc_ehr/model/sources/ClinicalReport.js +++ b/onprc_ehr/resources/web/onprc_ehr/model/sources/ClinicalReport.js @@ -27,9 +27,6 @@ EHR.model.DataModelManager.registerMetadata('ClinicalReport_ONPRC', { hidden: false, allowBlank: false }, - remark: { - hidden: false - }, p2: { formEditorConfig: { xtype: 'ehr-plantextarea' diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BehaviorExamFormType.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BehaviorExamFormType.java index 8f58945e1..843f5e11e 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BehaviorExamFormType.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/BehaviorExamFormType.java @@ -58,6 +58,9 @@ public BehaviorExamFormType(DataEntryFormContext ctx, Module owner) { s.addConfigSource("BehaviorDefaults"); + if (s.getName().equals("Clinical Remarks")) + s.addConfigSource("CaseMgmt"); + if (!s.getName().equals("Clinical Remarks")) s.addConfigSource("ClinicalReportChild"); @@ -78,6 +81,7 @@ public BehaviorExamFormType(DataEntryFormContext ctx, Module owner) addClientDependency(ClientDependency.supplierFromPath("ehr/model/sources/ClinicalReport.js")); addClientDependency(ClientDependency.supplierFromPath("ehr/panel/ExamDataEntryPanel.js")); addClientDependency(ClientDependency.supplierFromPath("ehr/model/sources/ClinicalReportChild.js")); + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/model/sources/CaseMgmt.js")); setJavascriptClass("EHR.panel.ExamDataEntryPanel"); // //Added: 12-18-2017 R.Blasa diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/CagemateClinicalReportFormType.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/CagemateClinicalReportFormType.java deleted file mode 100644 index 2040724e2..000000000 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/CagemateClinicalReportFormType.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2013-2016 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.onprc_ehr.dataentry; - -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.SimpleGridPanel; -import org.labkey.api.ehr.dataentry.TaskForm; -import org.labkey.api.ehr.dataentry.TaskFormSection; -import org.labkey.api.ehr.security.EHRClinicalEntryPermission; -import org.labkey.api.module.Module; -import org.labkey.api.view.template.ClientDependency; - -import java.util.Arrays; - -//Created: 1-31-2018 R.Blasa - -public class CagemateClinicalReportFormType extends TaskForm -{ - public static final String NAME = "Cagemate Soaps"; - - public CagemateClinicalReportFormType(DataEntryFormContext ctx, Module owner) - { - super(ctx, owner, NAME, "Cagemates Soaps", "Clinical", Arrays.asList( - new TaskFormSection(), - new AnimalDetailsFormSection(), - new SimpleGridPanel("study", "Clinical Remarks", "SOAPs") - - )); - - setStoreCollectionClass("EHR.data.ClinicalReportStoreCollection"); - addClientDependency(ClientDependency.supplierFromPath("ehr/data/ClinicalReportStoreCollection.js")); - - setTemplateMode(AbstractFormSection.TEMPLATE_MODE.NO_ID); - setDisplayReviewRequired(true); - - for (FormSection s : this.getFormSections()) - { - s.addConfigSource("ClinicalDefaults"); - //Added 6-4-2015 Blasa - s.addConfigSource("ClinicalProcedures"); - } - - addClientDependency(ClientDependency.supplierFromPath("ehr/model/sources/ClinicalDefaults.js")); - - - } - - - - @Override - protected boolean canInsert() - { - if (!getCtx().getContainer().hasPermission(getCtx().getUser(), EHRClinicalEntryPermission.class)) - return false; - - return super.canInsert(); - } - -} diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/ClinicalReportFormType.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/ClinicalReportFormType.java index fa7b11c04..9a6f1faf4 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/ClinicalReportFormType.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/ClinicalReportFormType.java @@ -76,6 +76,9 @@ public ClinicalReportFormType(DataEntryFormContext ctx, Module owner) // s.addConfigSource("ClinicalReport"); s.addConfigSource("ClinicalReport_ONPRC"); + if (s.getName().equals("Clinical Remarks")) + s.addConfigSource("CaseMgmt"); + if (!s.getName().equals("Clinical Remarks")) s.addConfigSource("ClinicalReportChild"); @@ -95,6 +98,7 @@ public ClinicalReportFormType(DataEntryFormContext ctx, Module owner) // Modified: 10-5-2017 R.Blasa reinstalled 2-12-21 addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/model/sources/ClinicalReport.js")); + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/model/sources/CaseMgmt.js")); // Added: 7-22-2025 R.Blasa diff --git a/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest2.java b/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest2.java index ee9e2602f..7b0c7fffb 100644 --- a/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest2.java +++ b/onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest2.java @@ -1449,6 +1449,145 @@ public void gridErrorsTest() //TODO: make sure fields turn red as expected } + /** + * Verifies the clinremarks Id field read-only + tooltip behavior wired in via the + * `CaseMgmt` metadata source (onprc_ehr/model/sources/CaseMgmt.js). The source is + * registered against both ClinicalReportFormType and BehaviorExamFormType, so this + * test exercises both forms through a shared helper. + */ + @Test + public void testClinremarksIdReadOnlyOnCaseCreated() throws Exception + { + goToEHRFolder(); + + // Verify case open and open & immediately close. Test Exams/Cases and BSU Exam forms. + verifyClinremarksIdLockOnCaseCreated("Exams/Cases", "Clinical", "Open Case", CaseEntryPath.MANAGE_CASES_LINK); + verifyClinremarksIdLockOnCaseCreated("BSU Exam", "Behavior", "Open & Immediately Close", CaseEntryPath.MANAGE_CASES_LINK); + + // Top and bottom buttons + verifyClinremarksIdLockOnCaseCreated("Exams/Cases", "Clinical", "Open Case", CaseEntryPath.OPEN_MANAGE_TOP); + verifyClinremarksIdLockOnCaseCreated("BSU Exam", "Behavior", "Open Case", CaseEntryPath.OPEN_MANAGE_BOTTOM); + } + + /** How the test reaches the "create new case" dialog from the data entry form. */ + private enum CaseEntryPath + { + /** [Manage Cases] link → "Open Case" split button → "Open <category> Case" menu item. */ + MANAGE_CASES_LINK, + /** "Open/Manage <category> Case" button in the Instructions panel at the top of the form. */ + OPEN_MANAGE_TOP, + /** "Open/Manage <category> Case" button in the form's docked footer toolbar. */ + OPEN_MANAGE_BOTTOM + } + + private void verifyClinremarksIdLockOnCaseCreated(String formLinkLabel, String caseCategory, String openButtonLabel, CaseEntryPath entryPath) + { + final String expectedTooltip = "Refresh the form to enter data for a different animal."; + + log("Verifying clinremarks Id read-only on casecreated for form: " + formLinkLabel + " via '" + openButtonLabel + "' (entry: " + entryPath + ")"); + _helper.goToTaskForm(formLinkLabel, false); + _ext4Helper.clickExt4Tab("SOAP"); + + Ext4FieldRef idField = _helper.getExt4FieldForFormSection("SOAP", "Id"); + Assert.assertNotNull("Could not locate Id field in SOAP section of form: " + formLinkLabel, idField); + + idField.setValue(SUBJECTS[0]); + + // Wait for the AnimalDetailsPanel to display the Id + Ext4FieldRef detailsId = _ext4Helper.queryOne("displayfield[name=animalId]", Ext4FieldRef.class); + Assert.assertNotNull("AnimalDetailsPanel not rendered (form: " + formLinkLabel + ")", detailsId); + waitFor(() -> SUBJECTS[0].equals(String.valueOf(detailsId.getValue())), + "AnimalDetailsPanel did not display Id " + SUBJECTS[0] + " (form: " + formLinkLabel + ")", + WAIT_FOR_JAVASCRIPT); + + Assert.assertNotEquals("Id field should be editable before any case is created (form: " + formLinkLabel + ")", + Boolean.TRUE, idField.getEval("readOnly")); + Object preQtip = idField.getEval("getEl().dom.getAttribute('data-qtip')"); + assertEquals("Id field should have no tooltip before any case is created (form: " + formLinkLabel + ")", + "", preQtip == null ? "" : preQtip.toString()); + + // Open a real case from the data entry form so ManageCasesPanel fires the real casecreated event + createCaseFromForm(SUBJECTS[0], caseCategory, openButtonLabel, entryPath); + + waitFor(() -> Boolean.TRUE.equals(idField.getEval("readOnly")), + "Id field did not become read-only after case was opened (form: " + formLinkLabel + ")", + WAIT_FOR_JAVASCRIPT); + Object postQtip = idField.getEval("getEl().dom.getAttribute('data-qtip')"); + assertEquals("Id field should carry the lock tooltip after case is opened (form: " + formLinkLabel + ")", + expectedTooltip, postQtip == null ? "" : postQtip.toString()); + + _helper.discardForm(); + } + + /** + * Opens a case for {@code animalId} from the data entry form via the given {@code entryPath}, then + * closes the resulting Manage Cases window. {@code openButtonLabel} is one of the OpenCaseWindow submit + * buttons: "Open Case" (just open) or "Open & Immediately Close" (open and immediately close permanently). + */ + private void createCaseFromForm(String animalId, String caseCategory, String openButtonLabel, CaseEntryPath entryPath) + { + Locator.XPathLocator manageCasesWindow = Ext4Helper.Locators.window("Manage Cases: " + animalId); + triggerCreateCaseDialog(manageCasesWindow, caseCategory, entryPath); + + // ManageCasesPanel asks "Open New" vs "Edit Existing" first if an active case of this category + // already exists for the animal (ManageCasesPanel.js: showCreateWindow). Dismiss with "Open New". + Locator.XPathLocator existingCaseDialog = Ext4Helper.Locators.window("Open Case"); + if (Boolean.TRUE.equals(waitFor(() -> isElementPresent(existingCaseDialog), 2000))) + { + waitAndClick(existingCaseDialog.append(Ext4Helper.Locators.ext4ButtonEnabled("Open New"))); + waitForElementToDisappear(existingCaseDialog); + } + + Locator.XPathLocator openCaseWindow = Ext4Helper.Locators.window("Open Case: " + animalId); + waitForElement(openCaseWindow); + + if ("Clinical".equals(caseCategory)) + { + Ext4ComboRef vetField = Ext4ComboRef.getForLabel(this, "Assigned Vet"); + vetField.waitForStoreLoad(); + // Pick whichever vet the store has rather than hardcoding a display name; the test only needs the field populated + vetField.eval("setValue(arguments[0])", vetField.getFnEval("return this.store.getAt(0).get(this.valueField)")); + Ext4FieldRef.getForLabel(this, "Problem").setValue("Behavioral"); + } + else if ("Behavior".equals(caseCategory)) + { + Ext4FieldRef.getForLabel(this, "Subcategory").setValue("Alopecia"); + } + + waitAndClick(openCaseWindow.append(Ext4Helper.Locators.ext4ButtonEnabled(openButtonLabel))); + if ("Open & Immediately Close".equals(openButtonLabel)) + { + waitAndClick(Ext4Helper.Locators.menuItem("Close Permanently").notHidden()); + } + waitForElementToDisappear(openCaseWindow); + + waitAndClick(manageCasesWindow.append(Ext4Helper.Locators.ext4ButtonEnabled("Close"))); + waitForElementToDisappear(manageCasesWindow); + } + + private void triggerCreateCaseDialog(Locator.XPathLocator manageCasesWindow, String caseCategory, CaseEntryPath entryPath) + { + switch (entryPath) + { + case MANAGE_CASES_LINK -> { + waitAndClick(Locator.linkWithText("[Manage Cases]")); + waitForElement(manageCasesWindow); + waitAndClick(manageCasesWindow.append(Ext4Helper.Locators.ext4ButtonEnabled("Open Case"))); + waitAndClick(Ext4Helper.Locators.menuItem("Open " + caseCategory + " Case").notHidden()); + } + case OPEN_MANAGE_TOP -> { + Locator.XPathLocator instructionsPanel = Locator.tagWithClass("div", "x4-panel").withChild( + Locator.tagWithClass("div", "x4-panel-header").withDescendant( + Locator.tagWithClass("span", "x4-panel-header-text").withText("Instructions"))); + waitAndClick(instructionsPanel.append(Ext4Helper.Locators.ext4ButtonEnabled("Open/Manage " + caseCategory + " Case"))); + } + case OPEN_MANAGE_BOTTOM -> { + Locator.XPathLocator footerToolbar = Locator.tagWithClass("div", "x4-toolbar-footer"); + waitAndClick(footerToolbar.append(Ext4Helper.Locators.ext4ButtonEnabled("Open/Manage " + caseCategory + " Case"))); + } + } + } + @Override protected String getAnimalHistoryPath() {