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()
{