Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions onprc_ehr/resources/web/onprc_ehr/model/sources/CaseMgmt.js
Original file line number Diff line number Diff line change
@@ -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 = 'A case has been opened in this form for this animal. All records in the form will be collected toward that case. Cannot change animal Id.';
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to gray out the disabled Id? so that it doesn't look like it is available for updating - just a little better visually/user experience, imo.

};
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);
}
}
}
}
}
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ EHR.model.DataModelManager.registerMetadata('ClinicalReport_ONPRC', {
hidden: false,
allowBlank: false
},
remark: {
hidden: false
},
p2: {
formEditorConfig: {
xtype: 'ehr-plantextarea'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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
Expand Down
139 changes: 139 additions & 0 deletions onprc_ehr/test/src/org/labkey/test/tests/onprc_ehr/ONPRC_EHRTest2.java
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Loading