diff --git a/onprc_ehr/resources/queries/ehr_lookups/Incision_score_onprc.sql b/onprc_ehr/resources/queries/ehr_lookups/Incision_score_onprc.sql new file mode 100644 index 000000000..67ff090e0 --- /dev/null +++ b/onprc_ehr/resources/queries/ehr_lookups/Incision_score_onprc.sql @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2013 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. + */ + + +-- Created: 12-24-2024 R. Blasa + +SELECT * FROM ehr_lookups.incision_score WHERE date_disabled is null \ No newline at end of file diff --git a/onprc_ehr/resources/queries/study/clinical_observations.query.xml b/onprc_ehr/resources/queries/study/clinical_observations.query.xml new file mode 100644 index 000000000..6ccb5c3f1 --- /dev/null +++ b/onprc_ehr/resources/queries/study/clinical_observations.query.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + Category + + + Area + + ehr_lookups + observation_areas + value + + + + + Observation/Score + + + + study + encountersParent + objectid + + + + true + + + true + + + true + + + Code + true + + + Inflammation + true + + + Bruising + true + + + Other + true + + + Type + true + + + false + Remark + + + false + Observation Score Remark + + + true + Performed By + + +
+
+
+
\ No newline at end of file diff --git a/onprc_ehr/resources/queries/study/latestObservationsForCase.sql b/onprc_ehr/resources/queries/study/latestObservationsForCase.sql index 243372b65..049b6f506 100644 --- a/onprc_ehr/resources/queries/study/latestObservationsForCase.sql +++ b/onprc_ehr/resources/queries/study/latestObservationsForCase.sql @@ -24,7 +24,11 @@ SELECT c.category as caseCategory, c.isActive as caseIsActive, c.isOpen as caseIsOpen, - o.taskid + o.taskid, + o.inflammation, + o.bruising, + o.obs_remark + FROM study.clinical_observations o JOIN study.cases c ON (c.objectid = o.caseid) diff --git a/onprc_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml b/onprc_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml index df144a565..ed10987e7 100644 --- a/onprc_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml +++ b/onprc_ehr/resources/referenceStudy/study/datasets/datasets_metadata.xml @@ -840,6 +840,21 @@ integer + + varchar + + + varchar + + + varchar + + + varchar + + + varchar + Clinical Observations diff --git a/onprc_ehr/resources/scripts/onprc_ehr/onprc_triggers.js b/onprc_ehr/resources/scripts/onprc_ehr/onprc_triggers.js index ff0d25cfe..492eaa408 100644 --- a/onprc_ehr/resources/scripts/onprc_ehr/onprc_triggers.js +++ b/onprc_ehr/resources/scripts/onprc_ehr/onprc_triggers.js @@ -474,6 +474,31 @@ exports.init = function(EHR){ EHR.Server.Utils.addError(scriptErrors, 'category', msg, 'WARN'); } } + }); + + //Added 10-7-2024 Blasa + EHR.Server.TriggerManager.registerHandlerForQuery(EHR.Server.TriggerManager.Events.BEFORE_UPSERT, 'study', 'clinical_observations', function(helper, scriptErrors, row, oldRow) { + + if (row.Id && row.category != 'Incision' && (row.inflammation || row.bruising || row.other)) + { + var msg = ''; + if (row.Id && row.category != 'Incision' && row.type == 'surgery' && row.inflammation && row.inflammation != null) { + msg = row.category + ': was an invalid entry onto the Inflammation input field, only Incision entries are allowed'; + EHR.Server.Utils.addError(scriptErrors, 'category', msg, 'ERROR'); + } + if (row.Id && row.category != 'Incision' && row.type == 'surgery' && row.bruising && row.bruising != null) { + msg = row.category + ': was an invalid entry onto the Bruising input field, only Incision entries are allowed'; + EHR.Server.Utils.addError(scriptErrors, 'category', msg, 'ERROR'); + } + if (row.Id && row.category != 'Incision' && row.type == 'surgery' && row.other && row.other != null) { + msg = row.category + ': was an invalid entry onto the Other input field, only Incision entries are allowed'; + EHR.Server.Utils.addError(scriptErrors, 'category', msg, 'ERROR'); + } + + + + } + }); diff --git a/onprc_ehr/resources/views/procedureDetails.html b/onprc_ehr/resources/views/procedureDetails.html index 038278798..6d9f9a26c 100644 --- a/onprc_ehr/resources/views/procedureDetails.html +++ b/onprc_ehr/resources/views/procedureDetails.html @@ -30,6 +30,10 @@ tag: 'div', style: 'padding-bottom: 10px;', id: 'procedureFlags_' + webpart.wrapperDivId + },{ + tag: 'div', + style: 'padding-bottom: 10px;', + id: 'procedureObservations_' + webpart.wrapperDivId }]; var el = Ext4.get(webpart.wrapperDivId); @@ -101,6 +105,13 @@ queryName: 'procedure_default_codes', filters: [LABKEY.Filter.create('procedureId', procedureId, LABKEY.Filter.Types.EQUAL)] }).render('procedureCodes_' + webpart.wrapperDivId); + + LDK.Utils.getReadOnlyQWP({ + title: 'Default Observations', + schemaName: 'onprc_ehr', + queryName: 'procedure_default_observations', + filters: [LABKEY.Filter.create('procedureId', procedureId, LABKEY.Filter.Types.EQUAL)] + }).render('procedureObservations_' + webpart.wrapperDivId); }); \ No newline at end of file diff --git a/onprc_ehr/resources/web/onprc_ehr/form/field/SurgeryEntryField.js b/onprc_ehr/resources/web/onprc_ehr/form/field/SurgeryEntryField.js new file mode 100644 index 000000000..3bcff3996 --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/form/field/SurgeryEntryField.js @@ -0,0 +1,119 @@ + +/* + * Copyright (c) 2013-2019 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +/** + * This field is used to display EHR projects. It contains a custom template for the combo list which displays both the project and protocol. + * It also listens for participantchange events and will display only the set of allowable projects for the selected animal. + * + * @cfg includeDefaultProjects defaults to true + */ + +//Created: 8-15-2024 R. Blasa + +Ext4.define('onprc_ehr.form.field.SurgeryExceptionField', { + extend: 'Ext.form.field.ComboBox', + alias: 'widget.onprc_surgeryexceptionfield', + + + expandToFitContent: true, + caseSensitive: false, + anyMatch: true, + typeAhead: true, + + initComponent: function(){ + Ext4.apply(this, { + displayField:'value', + valueField: 'value', + defaultValue: '0 - None', + queryMode: 'local', + store: Ext4.create('LABKEY.ext4.data.Store', { + schemaName: 'sla', + queryName: 'Reference_Data', + columns: 'value', + sort: 'sort_order', + filterArray: [ + LABKEY.Filter.create('enddate', null, LABKEY.Filter.Types.ISBLANK), + LABKEY.Filter.create('ColumnName', 'Surgicalobservationexception', LABKEY.Filter.Types.EQUAL)], + autoLoad: true + }) + }); + + this.callParent(arguments); + + + + } +}); + +Ext4.define('onprc_ehr.form.field.SurgeryScoreField', { + extend: 'Ext.form.field.ComboBox', + alias: 'widget.onprc_surgeryscorefield', + + + expandToFitContent: true, + caseSensitive: false, + anyMatch: true, + typeAhead: true, + + initComponent: function(){ + Ext4.apply(this, { + displayField:'value', + valueField: 'value', + queryMode: 'local', + defaultValue:'Normal', + store: Ext4.create('LABKEY.ext4.data.Store', { + schemaName: 'sla', + queryName: 'Reference_Data', + columns: 'value', + sort: 'sort_order', + filterArray: [ + LABKEY.Filter.create('enddate', null, LABKEY.Filter.Types.ISBLANK), + LABKEY.Filter.create('ColumnName', 'Surgicalobservationscore', LABKEY.Filter.Types.EQUAL)], + autoLoad: true + }) + }); + + this.callParent(arguments); + + + + } +}); + +Ext4.define('onprc_ehr.form.field.SurgeryOtherField', { + extend: 'Ext.form.field.ComboBox', + alias: 'widget.onprc_surgeryotherfield', + + + expandToFitContent: true, + caseSensitive: false, + anyMatch: true, + typeAhead: true, + + initComponent: function(){ + Ext4.apply(this, { + displayField:'value', + valueField: 'value', + queryMode: 'local', + store: Ext4.create('LABKEY.ext4.data.Store', { + schemaName: 'sla', + queryName: 'Reference_Data', + columns: 'value', + defaultValue:'0 - None', + sort: 'sort_order', + filterArray: [ + LABKEY.Filter.create('enddate', null, LABKEY.Filter.Types.ISBLANK), + LABKEY.Filter.create('ColumnName', 'Surgicalobservationother', LABKEY.Filter.Types.EQUAL)], + autoLoad: true + }) + }); + + this.callParent(arguments); + + + + } +}); diff --git a/onprc_ehr/resources/web/onprc_ehr/grid/ObservationsRowEditorGridPanel.js b/onprc_ehr/resources/web/onprc_ehr/grid/ObservationsRowEditorGridPanel.js new file mode 100644 index 000000000..ed45bfd35 --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/grid/ObservationsRowEditorGridPanel.js @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2014-2019 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +/** + * This is used within the RowEditor in the clinical rounds form + * + * @cfg observationFilterArray + * + */ +Ext4.define('ONPRC_EHR.grid.ObservationsRowEditorGridPanel', { + extend: 'Ext.grid.Panel', + alias: 'widget.onprc_ehr-observationsroweditorgridpanel', + + initComponent: function(){ + Ext4.apply(this, { + columns: this.getColumns(), + boundRecord: null, + boundRecordId: null, + selModel: { + mode: 'MULTI' + }, + plugins: [{ + ptype: 'clinicalobservationscellediting', + pluginId: 'cellediting', + clicksToEdit: 1 + }], + dockedItems: [{ + xtype: 'toolbar', + position: 'top', + items: [{ + text: 'Add', + scope: this, + handler: function(btn){ + var rec = this.createModel(); + if (!rec) + return; + + this.store.add(rec); + this.getPlugin('cellediting').startEdit(rec, 0); + } + },{ + text: 'Remove', + scope: this, + handler: function(btn){ + var recs = this.getSelectionModel().getSelection(); + this.store.remove(recs); + } + }] + }] + }); + + this.callParent(); + + this.mon(this.remarkStore, 'update', this.onRecordUpdate, this); + }, + + createModel: function(data){ + var form = this.up('window').down('ehr-formpanel'); + var br = form.getRecord(); + LDK.Assert.assertNotEmpty('No bound record in ObservationsRowEditorGridPanel', br); + if (!br){ + Ext4.Msg.alert('Error', 'Unable to find record'); + return; + } + + LDK.Assert.assertNotEmpty('No animal Id in ObservationsRowEditorGridPanel', br.get('Id')); + if (!br.get('Id')){ + Ext4.Msg.alert('Error', 'No animal Id provided'); + return; + } + + return this.store.createModel(Ext4.apply({ + Id: br.get('Id'), + date: new Date(), + caseid: br.get('caseid') + }, data)); + }, + + getColumns: function(){ + return [{ + header: 'Category', + dataIndex: 'category', + editable: true, + renderer: function(value, cellMetaData, record){ + if (Ext4.isEmpty(value)){ + cellMetaData.tdCls = 'labkey-grid-cell-invalid'; + } + + return value; + }, + editor: { + xtype: 'labkey-combo', + editable: true, + displayField: 'value', + valueField: 'value', + forceSelection: true, + queryMode: 'local', + anyMatch: true, + store: { + type: 'labkey-store', + schemaName: 'ehr', + queryName: 'observation_types', + filterArray: this.observationFilterArray, + columns: 'value,editorconfig', + autoLoad: true + } + } + },{ + header: 'Area', + width: 200, + editable: true, + dataIndex: 'area', + editor: { + xtype: 'combobox', + displayField: 'value', + valueField: 'value', + forceSelection: true, + queryMode: 'local', + anyMatch: true, + value: 'All', + store: { + type: 'labkey-store', + schemaName:'ehr_lookups', + queryName: 'observation_areas', + columns: 'value', + defaultValue:'0 - None', + sort: 'value', + filterArray: [ + LABKEY.Filter.create('date_disabled', null, LABKEY.Filter.Types.ISBLANK)], + autoLoad: true + } + } + },{ + header: 'Observation/Score', + width: 200, + editable: true, + dataIndex: 'observation', + sort: 'sort_order', + renderer: function(value, cellMetaData, record){ + if (Ext4.isEmpty(value) && ['Vet Attention'].indexOf(record.get('category')) == -1){ + cellMetaData.tdCls = 'labkey-grid-cell-invalid'; + } + + return value; + }, + editor: { + xtype: 'textfield' + } + + },{ + header: 'Obs Remarks', + width: 230, + dataIndex: 'obs_remark', + editor: { + xtype: 'textarea', + // width:580, + height:40 + } + },{ + header: 'Inflammation', + width: 130, + dataIndex: 'inflammation', + editor: { + xtype: 'onprc_surgeryexceptionfield', + forceSelection: true + } + },{ + + header: 'Bruising', + width: 130, + dataIndex: 'bruising', + editor: { + xtype: 'onprc_surgeryexceptionfield', + forceSelection: true + } + + },{ + header: 'Other', + width: 200, + editable: true, + dataIndex: 'other', + editor: { + xtype: 'checkcombo', + displayField: 'value', + valueField: 'value', + queryMode: 'local', + store: { + type: 'labkey-store', + schemaName:'sla', + queryName: 'Reference_Data', + columns: 'value', + defaultValue:'0 - None', + sort: 'sort_order', + filterArray: [ + LABKEY.Filter.create('enddate', null, LABKEY.Filter.Types.ISBLANK), + LABKEY.Filter.create('ColumnName', 'Surgicalobservationother', LABKEY.Filter.Types.EQUAL)], + autoLoad: true + }, + listeners: { + expand: function (combo) { + // Convert comma-separated string to an array and set value + const currentValue = combo.getValue(); + if (Array.isArray(currentValue) && currentValue.length > 0 && typeof currentValue[0] === 'string') { + const valueArray = currentValue[0].split(','); + combo.setValue(valueArray); + } + } + } + } + + },{ + header: 'Remarks', + width: 200, + editable: true, + dataIndex: 'remark', + editor: { + xtype: 'textarea', + width: 200, + height: 100 + } + }] + }, + + onRecordUpdate: function(store, rec){ + if (rec === this.boundRecord){ + var newId = rec.get('Id'); + var newDate = rec.get('date'); + + if (rec.get('Id') != this.boundRecordId){ + this.store.each(function(r){ + //update any record from the bound animal + if (r.get('Id') === this.boundRecordId){ + r.set({ + Id: newId, + date: newDate + }); + } + }, this); + } + } + }, + + loadRecord: function(rec){ + var id = rec.get('Id'); + + this.boundRecord = rec; + this.boundRecordId = rec.get('Id'); + + this.store.clearFilter(); + this.store.filter('Id', id); + } +}); \ No newline at end of file diff --git a/onprc_ehr/resources/web/onprc_ehr/grid/SurgicalRoundsRemarksGridPanel.js b/onprc_ehr/resources/web/onprc_ehr/grid/SurgicalRoundsRemarksGridPanel.js new file mode 100644 index 000000000..28ac291a8 --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/grid/SurgicalRoundsRemarksGridPanel.js @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2014-2019 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +/** + * Created to allow a custom row editor plugin and column that summarize observations + */ +Ext4.define('ONPRC_EHR.grid.SurgicalRoundsRemarksGridPanel', { + extend: 'EHR.grid.Panel', + alias: 'widget.onprc_ehr-surgroundsremarksgridpanel', + + + initComponent: function(){ + this.callParent(arguments); + + this.obsStore = this.store.storeCollection.getClientStoreByName('Clinical Observations'); + LDK.Assert.assertNotEmpty('Unable to find clinical observations store', this.obsStore); + + this.mon(this.obsStore, 'update', this.onObsStoreChange, this, {buffer: 400}); + this.mon(this.obsStore, 'remove', this.onObsStoreChange, this, {buffer: 400}); + this.mon(this.obsStore, 'add', this.onObsStoreChange, this, {buffer: 400}); + }, + + onObsStoreChange: function(store, rec){ + console.log('refresh remark grid'); + this.getView().refresh(); + }, + + getRowEditorPlugin: function(){ + if (this.rowEditorPlugin) + return this.rowEditorPlugin; + + this.rowEditorPlugin = Ext4.create('ONPRC_EHR.plugin.SurgicalRemarksRowEditor', { + cmp: this + }); + + return this.rowEditorPlugin; + }, + + configureColumns: function(){ + this.callParent(arguments); + + this.columns.push({ + name: 'observations', + header: 'Observations', + width: 700, + renderer: function(value, cellMetaData, record, rowIndex, colIndex, store){ + if (!this.obsStore){ + this.obsStore = store.storeCollection.getClientStoreByName('Clinical Observations'); + } + LDK.Assert.assertNotEmpty('Unable to find clinical observations store', this.obsStore); + + if (this.obsStore){ + var id = record.get('Id'); + var caseid = record.get('caseid'); + var date = record.get('date') ? Ext4.util.Format.date(record.get('date'),LABKEY.extDefaultDateFormat) : null; + var data = this.obsStore.snapshot || this.obsStore.data; + + var lines = []; + data.each(function(r){ + var rowDate = r.get('date') ? Ext4.util.Format.date(r.get('date'), LABKEY.extDefaultDateFormat) : null; + if (id !== r.get('Id') || rowDate !== date || caseid != r.get('caseid')){ + return; + } + + var line = ''; + var prefix = ''; + var suffix = ''; + + if (r.get('category')){ + line += r.get('category') + ': '; + + if (r.get('category') == 'Vet Attention'){ + prefix = ''; + suffix = ''; + } + else if (r.get('category') == 'Reviewed'){ + prefix = ''; + suffix = ''; + } + } + + if (!Ext4.isEmpty(r.get('observation'))){ + line += r.get('observation'); + } + + if (!Ext4.isEmpty(r.get('inflammation'))){ + line += ';' + r.get('inflammation') + ' Inflammation'; + } + + if (!Ext4.isEmpty(r.get('bruising'))){ + line += ';' +r.get('bruising') + ' Bruising'; + } + if (!Ext4.isEmpty(r.get('other'))){ + line += ';' + r.get('other') + ' Other'; + } + + if (r.get('remark')){ + line += ';' + r.get('remark') + ' Remark'; + } + + if (!r.get('remark') && Ext4.isEmpty(r.get('observation'))){ + if (['Vet Attention', 'Reviewed'].indexOf(r.get('category')) == -1) + line += '  '; + } + + if (line){ + lines.push(prefix + line + suffix); + } + }, this); + + return lines.join('
'); + } + + return ''; + } + }); + } +}); \ No newline at end of file diff --git a/onprc_ehr/resources/web/onprc_ehr/icons/PrimeSlideImage.jpg b/onprc_ehr/resources/web/onprc_ehr/icons/PrimeSlideImage.jpg deleted file mode 100644 index a86684b0b..000000000 Binary files a/onprc_ehr/resources/web/onprc_ehr/icons/PrimeSlideImage.jpg and /dev/null differ diff --git a/onprc_ehr/resources/web/onprc_ehr/model/sources/SurgicalRounds.js b/onprc_ehr/resources/web/onprc_ehr/model/sources/SurgicalRounds.js new file mode 100644 index 000000000..d15c66c66 --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/model/sources/SurgicalRounds.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013-2019 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +EHR.model.DataModelManager.registerMetadata('SurgicalRoundsExt', { + allQueries: { + Id: { + editable: false, + columnConfig: { + editable: false + } + } + }, + byQuery: { + 'study.clinremarks': { + category: { + defaultValue: 'Surgery', + hidden: true + }, + hx: { + hidden: true + }, + s: { + hidden: true + }, + o: { + hidden: true + }, + a: { + hidden: true + }, + p: { + hidden: true + }, + p2: { + hidden: true + } + }, + + 'study.clinical_observations': { + type: { + defaultValue: 'surgery', + hidden: true + } + }, + 'study.blood': { + reason: { + defaultValue: 'Clinical' + } + } + } +}); \ No newline at end of file diff --git a/onprc_ehr/resources/web/onprc_ehr/plugin/SurgicalRemarksRowEditor.js b/onprc_ehr/resources/web/onprc_ehr/plugin/SurgicalRemarksRowEditor.js new file mode 100644 index 000000000..7679c104d --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/plugin/SurgicalRemarksRowEditor.js @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2013-2019 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +Ext4.define('ONPRC_EHR.plugin.SurgicalRemarksRowEditor', { + extend: 'EHR.plugin.RowEditor', + + getObservationPanelCfg: function(){ + var store = this.cmp.dataEntryPanel.storeCollection.getClientStoreByName('Clinical Observations'); + LDK.Assert.assertNotEmpty('Observations store not found', store); + + return { + xtype: 'onprc_ehr-observationsroweditorgridpanel', + itemId: 'observationsPanel', + remarkStore: this.cmp.store, + width: 1500, + store: store + }; + }, + + getDetailsPanelCfg: function(){ + return { + xtype: 'ehr-animaldetailsextendedpanel', + itemId: 'detailsPanel' + } + }, + + onWindowClose: function(){ + this.callParent(arguments); + this.getEditorWindow().down('#observationsPanel').store.clearFilter(); + + }, + + getFormPanelCfg: function(){ + var ret = this.callParent(arguments); + ret.maxFieldWidth = 1500; + + return ret; + }, + + getWindowCfg: function(){ + var ret = this.callParent(arguments); + + var formCfg = ret.items[0].items[1]; + //NOTE: added to avoid splitting form into 2 columns + formCfg.maxItemsPerCol = 100; + ret.items[0].items[1] = { + xtype: 'panel', + layout: 'column', + defaults: { + border: false + }, + border: false, + items: [formCfg, this.getObservationPanelCfg()] + }; + + ret.width = 1150; + return ret; + }, + + getWindowButtons: function(){ + var buttons = this.callParent(arguments); + + buttons.unshift({ + text: 'Mark Reviewed', + handler: function(btn){ + var win = btn.up('window'); + var form = win.down('#formPanel'); + var record = form.getBoundRecord(); + if (!record){ + return; + } + + var obsStore = record.store.storeCollection.getClientStoreByName('Clinical Observations'); + LDK.Assert.assertNotEmpty('Unable to find clinical_observations store in ClinicalRemarksRowEditor', obsStore); + LDK.Assert.assertNotEmpty('No caseid in bound record in ClinicalRemarksRowEditor', form.getRecord().get('caseid')); + + obsStore.add(obsStore.createModel({ + Id: form.getRecord().get('Id'), + date: new Date(), + caseid: form.getRecord().get('caseid'), + category: 'Reviewed', + area: 'N/A', + observation: LABKEY.Security.currentUser.displayName + })); + }, + scope: this + }); + + return buttons; + }, + + loadRecord: function(record){ + this.callParent(arguments); + this.getEditorWindow().down('#observationsPanel').loadRecord(record); + } +}); \ No newline at end of file diff --git a/onprc_ehr/resources/web/onprc_ehr/window/AddSurgicalCasesWindow.js b/onprc_ehr/resources/web/onprc_ehr/window/AddSurgicalCasesWindow.js new file mode 100644 index 000000000..c3c6ac202 --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/window/AddSurgicalCasesWindow.js @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2013-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.AddSurgicalCasesWindow', { + extend: 'EHR.window.AddClinicalCasesWindow', + caseCategory: 'Surgery', + templateName: 'Surgical Rounds', + + allowNoSelection: true, + allowReviewAnimals: false, + showAssignedVetCombo: false, + caseDisplayField: 'remark', + caseEmptyText: 'No description available', + defaultRemark: 'Surgical rounds performed.', + + initComponent: function(){ + this.callParent(arguments); + + this.obsStore = this.targetStore.storeCollection.getClientStoreByName('Clinical Observations'); + LDK.Assert.assertNotEmpty('Unable to find targetStore in AddSurgicalCasesWindow', this.obsStore); + }, + + 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)); + 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,inflammation,bruising,other,remark,caseid,obs_remark,type,dsrowid', + filterArray: obsFilterArray, + sort: 'dsrowid', + 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/room_sortValue,Id/curLocation/cage_sortValue,Id,remark', + columns: 'Id,objectid,remark,Id/curLocation/location', + filterArray: casesFilterArray, + scope: this, + success: function(results){ + this.casesResults = results; + }, + failure: LDK.Utils.getErrorCallback() + }); + + multi.send(this.onSuccess, this); + }, + + onSuccess: function(){ + if (!this.casesResults || !this.casesResults.rows || !this.casesResults.rows.length){ + Ext4.Msg.hide(); + Ext4.Msg.alert('', 'No active cases were found' + (this.down('#excludeToday').getValue() ? ', excluding those reviewed today.' : '.')); + return; + } + + LDK.Assert.assertNotEmpty('Unable to find targetStore in AddSurgicalCasesWindow', this.targetStore); + + var records = []; + var idMap = {}; + this.caseRecordMap = {}; + this.recordData = { + performedby: this.down('#performedBy').getValue(), + date: this.down('#date').getValue() + }; + + Ext4.Array.each(this.casesResults.rows, function(sr){ + var row = new LDK.SelectRowsRow(sr); + idMap[row.getValue('Id')] = row; + this.caseRecordMap[row.getValue('objectid')] = row; + + var obj = { + Id: row.getValue('Id'), + date: this.recordData.date, + category: this.caseCategory, + s: null, + o: null, + a: null, + p: null, + caseid: row.getValue('objectid'), + remark: this.defaultRemark, + performedby: this.recordData.performedby, + 'Id/curLocation/location': row.getValue('Id/curLocation/location') + }; + + records.push(this.targetStore.createModel(obj)); + }, this); + + //check for dupes + this.addRecords(records); + }, + + doAddRecords: function(records){ + var toAdd = this.checkForExistingCases(records); + this.targetStore.add(toAdd); + if (toAdd.length){ + this.processObservations(records); + } + else { + this.onComplete(); + } + }, + + processObservations: function(records){ + var previousObsMap = {}; + if (this.obsResults && this.obsResults.rows && this.obsResults.rows.length){ + Ext4.Array.forEach(this.obsResults.rows, function(sr){ + var row = new LDK.SelectRowsRow(sr); + + var caseId = row.getValue('caseid'); + if (!previousObsMap[caseId]) + previousObsMap[caseId] = []; + + previousObsMap[caseId].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'), + observation: row.getValue('observation'), + inflammation: row.getValue('inflammation'), + bruising: row.getValue('bruising'), + other: row.getValue('other'), + remark: row.getValue('remark'), + obs_remark: row.getValue('obs_remark') + }); + }, this); + } + + var toAdd = []; + var recordsNeedingTemplate = []; + Ext4.Array.forEach(records, function(rec){ + var caseId = rec.get('caseid'); + if (previousObsMap[caseId]){ + Ext4.Array.forEach(previousObsMap[caseId], function(obsRec){ + toAdd.push(this.obsStore.createModel(obsRec)); + }, this); + } + else { + console.log('no existing obs'); + recordsNeedingTemplate.push(rec) + } + }, this); + + if (toAdd.length){ + this.obsStore.add(toAdd); + } + + if (recordsNeedingTemplate.length){ + this.applyObsTemplate(recordsNeedingTemplate); + } + else { + Ext4.Msg.hide(); + this.close(); + } + } +}); + +EHR.DataEntryUtils.registerGridButton('ADDSURGICALCASEST', 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.AddSurgicalCasesWindow', { + targetStore: grid.store + }).show(); + } + }, config); +}); diff --git a/onprc_ehr/resources/web/onprc_ehr/window/OpenSurgeryCasesWindow.js b/onprc_ehr/resources/web/onprc_ehr/window/OpenSurgeryCasesWindow.js new file mode 100644 index 000000000..39548c39e --- /dev/null +++ b/onprc_ehr/resources/web/onprc_ehr/window/OpenSurgeryCasesWindow.js @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2014-2019 LabKey Corporation + * + * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + */ +/** + * @cfg sourceStore + */ +Ext4.define('ONPRC_EHR.window.OpenSurgeryCasesWindow', { + extend: 'Ext.window.Window', + minWidth: 600, + minHeight: 200, + + initComponent: function(){ + Ext4.apply(this, { + modal: true, + closeAction: 'destroy', + title: 'Open Cases', + bodyStyle: 'padding: 5px;', + items: [{ + html: 'Loading...', + border: false + }], + buttons: [{ + text: 'Open Selected Cases', + scope: this, + handler: this.onSubmit + },{ + text: 'Close', + handler: function(btn){ + btn.up('window').close(); + } + }] + }); + + this.callParent(arguments); + + this.getDemographics(); + }, + + getDemographics: function(){ + var ids = []; + this.sourceStore.each(function(rec){ + if (!rec.get('Id')){ + return; + } + + ids.push(rec.get('Id')); + }, this); + + ids = Ext4.unique(ids); + EHR.DemographicsCache.getDemographics(ids, this.onLoad, this); + }, + + onLoad: function(ids, demographicsMap){ + var animalMap = {}; + var columns = 6; + + this.sourceStore.each(function(rec, recIdx){ + if (!rec.get('Id') || !rec.get('procedureid')){ + return; + } + + animalMap[rec.get('Id')] = animalMap[rec.get('Id')] || { + encounterRecords: [], + procedureRecords: [], + procedureNames: [], + maxFollowup: 0 + }; + + var procedureStore = EHR.DataEntryUtils.getProceduresStore(); + var procedureRecIdx = procedureStore.findExact('rowid', rec.get('procedureid')); + LDK.Assert.assertTrue('Unknown procedure id: ' + rec.get('procedureid'), procedureRecIdx != -1); + if (procedureRecIdx == -1){ + return; + } + + var procedureRec = procedureStore.getAt(procedureRecIdx); + + animalMap[rec.get('Id')].procedureRecords.push(procedureRec); + animalMap[rec.get('Id')].procedureNames.push(procedureRec.get('name')); + + if (procedureRec.get('followupDays') > animalMap[rec.get('Id')].maxFollowup) + animalMap[rec.get('Id')].maxFollowup = procedureRec.get('followupDays'); + + animalMap[rec.get('Id')].encounterRecords.push(rec); + }, this); + + var toAdd = []; + var rowIdx = 0; + for (var id in animalMap){ + var obj = animalMap[id]; + obj.procedureNames = Ext4.unique(obj.procedureNames); + var ar = demographicsMap[id]; + rowIdx++; + + toAdd.push({ + xtype: 'displayfield', + value: id, + fieldName: 'Id', + rowIdx: rowIdx + }); + + if (!ar || ar.getCalculatedStatus() != 'Alive'){ + toAdd.push({ + xtype: 'displayfield', + value: 'Unknown or non-living animal Id, cannot open case', + colspan: columns - 1, + rowIdx: rowIdx + }); + + continue; + } + + toAdd.push({ + xtype: 'displayfield', + value: obj.procedureNames.join(', '), + width: 200, + rowIdx: rowIdx + }); + + toAdd.push({ + xtype: 'displayfield', + value: obj.maxFollowup + }); + + var hasSurgCase = false; + var caseRec = null; + if (ar.getActiveCases()){ + Ext4.Array.forEach(ar.getActiveCases(), function(rec){ + if (rec.category == 'Surgery'){ + hasSurgCase = true; + caseRec = rec; + } + }, this); + } + + toAdd.push({ + xtype: 'textarea', + fieldName: 'remark', + height: 75, + width: 250, + rowIdx: rowIdx, + value: caseRec ? ((caseRec.remark ? (caseRec.remark + '\n' + Ext4.Date.format(new Date(), LABKEY.extDefaultDateFormat) + ': ') : '') + obj.procedureNames.join(', ')) : 'Open Sx Case: ' + obj.procedureNames.join(', ') + }); + + toAdd.push({ + xtype: 'displayfield', + value: hasSurgCase ? 'Y' : 'N', + rowIdx: rowIdx, + fieldName: 'caseId', + caseId: caseRec ? caseRec.lsid : null + }); + + toAdd.push({ + xtype: 'checkbox', + fieldName: 'exclude', + encounterRecords: obj.encounterRecords, + checked: (obj.maxFollowup === 0), + rowIdx: rowIdx + }); + } + + this.removeAll(); + + if (toAdd.length){ + toAdd = [{ + html: 'Id' + },{ + html: 'Procedure(s)' + },{ + html: 'Followup Days', + width: 80 + },{ + html: 'Case Description' + },{ + html: 'Has Existing Case?', + width: 80 + },{ + html: 'Skip Opening?', + width: 70 + }].concat(toAdd); + + this.add({ + defaults: { + style: 'margin-right: 5px;', + border: false + }, + border: false, + layout: { + type: 'table', + columns: columns + }, + items: toAdd + }); + } + else { + this.add({ + html: 'No IDs To Show' + }); + } + }, + + onSubmit: function(){ + var recordMap = {}; + var caseRecordsToInsert = []; + var caseRecordsToUpdate = []; + + var success = true; + var cbs = this.query('checkbox'); + Ext4.Array.forEach(cbs, function(cb){ + if (!cb.getValue()){ + var id = this.query('field[rowIdx=' + cb.rowIdx + '][fieldName=Id]')[0].getValue(); + var remark = this.query('field[rowIdx=' + cb.rowIdx + '][fieldName=remark]')[0].getValue(); + var existingCase = this.query('component[rowIdx=' + cb.rowIdx + '][fieldName=caseId]')[0].caseId; + recordMap[id] = recordMap[id] || []; + recordMap[id] = recordMap[id].concat(cb.encounterRecords); + + if (!remark){ + Ext4.Msg.alert('Error', 'Must Enter A Remark For All Cases'); + success = false; + return; + } + + if (existingCase){ + caseRecordsToUpdate.push({ + lsid: existingCase, + remark: remark + }); + } + else { + caseRecordsToInsert.push({ + Id: id, + date: new Date(), + remark: remark, + category: 'Surgery', + performedby: LABKEY.Security.currentUser.displayName + }); + } + } + }, this); + + if (!success){ + return; + } + + if (caseRecordsToInsert.length || caseRecordsToUpdate.length){ + this.hide(); + Ext4.Msg.wait('Loading...'); + var multi = new LABKEY.MultiRequest(); + + if (caseRecordsToInsert.length){ + multi.add(LABKEY.Query.insertRows, { + schemaName: 'study', + queryName: 'cases', + rows: caseRecordsToInsert, + scope: this, + failure: LDK.Utils.getErrorCallback(), + success: function(results){ + if (!results || !results.rows){ + return; + } + + Ext4.Array.forEach(results.rows, function(row){ + if (row.Id && row.objectid){ + var records = recordMap[row.Id]; + Ext4.Array.forEach(records, function(rec){ + console.log('updating procedure with caseid'); + rec.set('caseid', row.objectid); + }, this); + + this.caseUpdateStores.forEach(function(store){ + if (store.getFields().get('caseid') && store.getFields().get('Id')){ + store.each((rec) => { + if (rec.get('Id') == row.Id){ + rec.set('caseid', row.objectid); + } + }, this); + } + }, this); + } + }, this); + } + }); + } + + if (caseRecordsToUpdate.length){ + multi.add(LABKEY.Query.updateRows, { + schemaName: 'study', + queryName: 'cases', + rows: caseRecordsToUpdate, + scope: this, + failure: LDK.Utils.getErrorCallback(), + success: function(results){ + if (!results || !results.rows){ + return; + } + + Ext4.Array.forEach(results.rows, function(row){ + if (row.Id && row.objectid){ + var records = recordMap[row.Id]; + Ext4.Array.forEach(records, function(rec){ + console.log('updating procedure with caseid') + rec.set('caseid', row.objectid); + }, this); + + this.caseUpdateStores.forEach(function(store){ + if (store.getFields().get('caseid') && store.getFields().get('Id')){ + store.each((rec) => { + if (rec.get('Id') == row.Id){ + rec.set('caseid', row.objectid); + } + }, this); + } + }, this); + } + }, this); + } + }); + } + + multi.send(function(){ + this.close(); + Ext4.Msg.hide(); + Ext4.Msg.alert('Success', 'Surgical cases opened'); + EHR.DemographicsCache.clearCache(Ext4.Object.getKeys(recordMap)); + }, this); + } + else { + this.close(); + Ext4.Msg.alert('Complete', 'There are no cases to open'); + } + } +}); + +EHR.DataEntryUtils.registerDataEntryFormButton('OPENSURGERYCASEST', { + text: 'Open Cases', + name: 'openSurgeryCase', + successURL: LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.buildURL('ehr', 'enterData.view'), + disabled: true, + itemId: 'openSurgeryCases', + requiredPermissions: [ + [EHR.QCStates.COMPLETED, 'update', [{schemaName: 'study', queryName: 'Cases'}]] + ], + handler: function(btn){ + var panel = btn.up('ehr-dataentrypanel'); + LDK.Assert.assertNotEmpty('Unable to find dataentrypanel from OPENSURGERYCASES button', panel); + + //find id + var clientStore = panel.storeCollection.getClientStoreByName('encounters') || panel.storeCollection.getClientStoreByName('Clinical Encounters'); + LDK.Assert.assertNotEmpty('Unable to find clientStore from OPENSURGERYCASES button', clientStore); + + if (!clientStore.getCount()){ + Ext4.Msg.alert('Error', 'No Surgeries Entered'); + return; + } + + Ext4.create('ONPRC_EHR.window.OpenSurgeryCasesWindow', { + sourceStore: clientStore, + caseUpdateStores: panel.storeCollection.clientStores.items, + }).show(); + } +}); diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SingleSurgeryFormType.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SingleSurgeryFormType.java index fad3ed827..acaa26bd9 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SingleSurgeryFormType.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SingleSurgeryFormType.java @@ -53,6 +53,7 @@ public SingleSurgeryFormType(DataEntryFormContext ctx, Module owner) new EncounterChildFormSection("ehr", "encounter_summaries", "Narrative", true), new EncounterMedicationsFormSection("study", "Drug Administration", "Medications/Treatments Given", true), new EncounterMedicationsFormSection("study", "Treatment Orders", "Medication/Treatment Orders", false), + new ClinicalObservationsFormSection(EHRService.FORM_SECTION_LOCATION.Tabs), new EncounterChildFormSection("study", "weight", "Weight", false, "EHR.data.WeightClientStore", Arrays.asList(ClientDependency.supplierFromPath("ehr/data/WeightClientStore.js")), null), //Added by Kollil on 3/6/2025. Refer to tkt # 12124 new ClinicalObservationsFormSection(EHRService.FORM_SECTION_LOCATION.Tabs, true), @@ -66,8 +67,13 @@ public SingleSurgeryFormType(DataEntryFormContext ctx, Module owner) } addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/model/sources/Surgery.js")); - addClientDependency(ClientDependency.supplierFromPath("ehr/window/OpenSurgeryCasesWindow.js")); + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/window/OpenSurgeryCasesWindow.js")); addClientDependency(ClientDependency.supplierFromPath("ehr/panel/SurgeryDataEntryPanel.js")); + +// Added: 8-27-2024 r. Blasa + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/form/field/SurgeryEntryField.js")); + + setDisplayReviewRequired(true); setJavascriptClass("EHR.panel.SurgeryDataEntryPanel"); @@ -82,7 +88,7 @@ public SingleSurgeryFormType(DataEntryFormContext ctx, Module owner) protected List getButtonConfigs() { List ret = super.getButtonConfigs(); - ret.add("OPENSURGERYCASES"); + ret.add("OPENSURGERYCASEST"); return ret; } diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgeryFormType.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgeryFormType.java index 8ecb930b3..ccf8220b9 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgeryFormType.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgeryFormType.java @@ -57,6 +57,7 @@ public SurgeryFormType(DataEntryFormContext ctx, Module owner) new ClinicalObservationsFormSection(EHRService.FORM_SECTION_LOCATION.Tabs, true), new EncounterMedicationsFormSection("study", "Drug Administration", "Medications/Treatments Given", true), new EncounterMedicationsFormSection("study", "Treatment Orders", "Medication/Treatment Orders", false), + new ClinicalObservationsFormSection(EHRService.FORM_SECTION_LOCATION.Tabs), new BloodDrawFormSection(false, EHRService.FORM_SECTION_LOCATION.Tabs), new EncounterChildFormSection("ehr", "snomed_tags", "Diagnostic Codes", true) )); @@ -68,11 +69,15 @@ public SurgeryFormType(DataEntryFormContext ctx, Module owner) // Modified: 7-4-2024 R.Blasa addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/model/sources/Surgery.js")); - addClientDependency(ClientDependency.supplierFromPath("ehr/window/OpenSurgeryCasesWindow.js")); +// Modified: 1-10-2025 R. Blasa + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/window/OpenSurgeryCasesWindow.js")); + addClientDependency(ClientDependency.supplierFromPath("ehr/panel/SurgeryDataEntryPanel.js")); setDisplayReviewRequired(true); setJavascriptClass("EHR.panel.SurgeryDataEntryPanel"); + + for (FormSection s : this.getFormSections()) { s.addConfigSource("Encounter"); @@ -92,7 +97,7 @@ public SurgeryFormType(DataEntryFormContext ctx, Module owner) protected List getButtonConfigs() { List ret = super.getButtonConfigs(); - ret.add("OPENSURGERYCASES"); + ret.add("OPENSURGERYCASEST"); return ret; } diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalAmendedRemarksFormSection.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalAmendedRemarksFormSection.java new file mode 100644 index 000000000..079f5956c --- /dev/null +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalAmendedRemarksFormSection.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2013-2014 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.json.JSONObject; +import org.labkey.api.ehr.EHRService; +import org.labkey.api.ehr.dataentry.DataEntryFormContext; +import org.labkey.api.ehr.dataentry.SimpleFormSection; +import org.labkey.api.view.template.ClientDependency; + +import java.util.List; + +// Created: 8-1-2024 R. Blasa +public class SurgicalAmendedRemarksFormSection extends SimpleFormSection +{ + public SurgicalAmendedRemarksFormSection(String label, EHRService.FORM_SECTION_LOCATION location) + { + super("study", "Clinical Remarks", label, "onprc_ehr-surgroundsremarksgridpanel", location); + addClientDependency(ClientDependency.supplierFromPath("ehr/plugin/ClinicalObservationsCellEditing.js")); + addClientDependency(ClientDependency.supplierFromPath("ehr/panel/ClinicalRemarkPanel.js")); + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/grid/SurgicalRoundsRemarksGridPanel.js")); + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/grid/ObservationsRowEditorGridPanel.js")); //Modified +// MOdified: 8-1-2024 so that contents reset as ehr control types + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/plugin/SurgicalRemarksRowEditor.js")); //edited + + addClientDependency(ClientDependency.supplierFromPath("ehr/data/ClinicalObservationsClientStore.js")); + addClientDependency(ClientDependency.supplierFromPath("ehr/buttons/roundsButtons.js")); + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/form/field/SurgeryEntryField.js")); + + + setTemplateMode(TEMPLATE_MODE.NONE); + } + + @Override + public List getTbarButtons() + { + List defaultButtons = super.getTbarButtons(); + defaultButtons.remove("COPYFROMSECTION"); + defaultButtons.remove("ADDRECORD"); + defaultButtons.remove("ADDANIMALS"); + + if (defaultButtons.contains("DELETERECORD")) + { + int idx = defaultButtons.indexOf("DELETERECORD"); + defaultButtons.remove("DELETERECORD"); + defaultButtons.add(idx, "ROUNDSDELETE"); + } + + defaultButtons.add("MARK_ROUNDS_REVIEWED"); + + return defaultButtons; + } + + @Override + public List getTbarMoreActionButtons() + { + List defaultButtons = super.getTbarMoreActionButtons(); + defaultButtons.remove("DUPLICATE"); + + return defaultButtons; + } + + @Override + public JSONObject toJSON(DataEntryFormContext ctx, boolean includeFormElements) + { + JSONObject ret = super.toJSON(ctx, includeFormElements); + + return ret; + } + + @Override + protected String getServerSort() + { + return "Id/curLocation/room,Id/curLocation/cage,Id"; + } +} diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalRoundsFormType.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalRoundsFormType.java index 9dd6206d3..a11855bdb 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalRoundsFormType.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalRoundsFormType.java @@ -50,15 +50,15 @@ public SurgicalRoundsFormType(DataEntryFormContext ctx, Module owner) for (FormSection s : this.getFormSections()) { - s.addConfigSource("SurgicalRounds"); + s.addConfigSource("SurgicalRoundsExt"); if (s instanceof ClinicalObservationsFormSection) { ((ClinicalObservationsFormSection)s).setHidden(true); } } - - addClientDependency(ClientDependency.supplierFromPath("ehr/model/sources/SurgicalRounds.js")); +// Modified: 1-1-2025 R. Blasa + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/model/sources/SurgicalRounds.js")); setDisplayReviewRequired(true); } diff --git a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalRoundsRemarksFormSection.java b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalRoundsRemarksFormSection.java index 68608d813..bdb2b92df 100644 --- a/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalRoundsRemarksFormSection.java +++ b/onprc_ehr/src/org/labkey/onprc_ehr/dataentry/SurgicalRoundsRemarksFormSection.java @@ -26,7 +26,7 @@ * Date: 7/7/13 * Time: 10:36 AM */ -public class SurgicalRoundsRemarksFormSection extends RoundsRemarksFormSection +public class SurgicalRoundsRemarksFormSection extends SurgicalAmendedRemarksFormSection { public SurgicalRoundsRemarksFormSection() { @@ -39,9 +39,13 @@ public SurgicalRoundsRemarksFormSection(EHRService.FORM_SECTION_LOCATION locatio setConfigSources(Collections.singletonList("Task")); addClientDependency(ClientDependency.supplierFromPath("ehr/window/AddClinicalCasesWindow.js")); - addClientDependency(ClientDependency.supplierFromPath("ehr/window/AddSurgicalCasesWindow.js")); addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/window/BulkChangeCasesWindow.js")); + +// Modified: 7-26-2024 R. Blasa + addClientDependency(ClientDependency.supplierFromPath("onprc_ehr/window/AddSurgicalCasesWindow.js")); + + _showLocation = true; } @@ -49,9 +53,12 @@ public SurgicalRoundsRemarksFormSection(EHRService.FORM_SECTION_LOCATION locatio public List getTbarButtons() { List defaultButtons = super.getTbarButtons(); - defaultButtons.add(0, "ADDSURGICALCASES"); defaultButtons.add("BULK_CHANGE_CASES"); +// Added: 7-26-2024 R. Blasa + defaultButtons.add(0, "ADDSURGICALCASEST"); + + return defaultButtons; } } diff --git a/onprc_ssu/resources/views/begin.html b/onprc_ssu/resources/views/begin.html index c82d9b5d5..201d9e72c 100644 --- a/onprc_ssu/resources/views/begin.html +++ b/onprc_ssu/resources/views/begin.html @@ -30,9 +30,15 @@ },{ name: 'Surgeries Entered', url: LABKEY.ActionURL.buildURL('ehr', 'enterData', ctx['EHRStudyContainer']) - },{ + }] + },{ + header: 'Notifications', + items: [{ name: 'Surgery Notification', url: LABKEY.ActionURL.buildURL('ldk', 'runNotification', null, {key: 'org.labkey.onprc_ssu.notification.SSU_Notification'}) + },{ + name: 'Surgery Rounds Notification', + url: LABKEY.ActionURL.buildURL('ldk', 'runNotification', null, {key: 'org.labkey.onprc_ssu.notification.SSU_RoundsNotification'}) }] },{ header: 'Reports', diff --git a/onprc_ssu/src/org/labkey/onprc_ssu/ONPRC_SSUModule.java b/onprc_ssu/src/org/labkey/onprc_ssu/ONPRC_SSUModule.java index 99b6a6825..af12e2959 100644 --- a/onprc_ssu/src/org/labkey/onprc_ssu/ONPRC_SSUModule.java +++ b/onprc_ssu/src/org/labkey/onprc_ssu/ONPRC_SSUModule.java @@ -23,6 +23,7 @@ import org.labkey.api.ldk.notification.NotificationService; import org.labkey.api.module.ModuleContext; import org.labkey.onprc_ssu.notification.SSU_Notification; +import org.labkey.onprc_ssu.notification.SSU_RoundsNotification; import java.util.Collection; import java.util.Collections; @@ -61,6 +62,8 @@ protected void init() protected void doStartupAfterSpringConfig(ModuleContext moduleContext) { NotificationService.get().registerNotification(new SSU_Notification(this)); + //Added by Kollil on 8/23/24 + NotificationService.get().registerNotification(new SSU_RoundsNotification(this)); } @Override diff --git a/onprc_ssu/src/org/labkey/onprc_ssu/notification/SSU_RoundsNotification.java b/onprc_ssu/src/org/labkey/onprc_ssu/notification/SSU_RoundsNotification.java new file mode 100644 index 000000000..b8c93c1b3 --- /dev/null +++ b/onprc_ssu/src/org/labkey/onprc_ssu/notification/SSU_RoundsNotification.java @@ -0,0 +1,191 @@ +package org.labkey.onprc_ssu.notification; + +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.CompareType; +import org.labkey.api.data.Container; +import org.labkey.api.data.Results; +import org.labkey.api.data.ResultsImpl; +import org.labkey.api.data.Selector; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.Sort; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; +import org.labkey.api.ehr.EHRService; +import org.labkey.api.ldk.notification.AbstractNotification; +import org.labkey.api.module.Module; +import org.labkey.api.query.DetailsURL; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QueryService; +import org.labkey.api.security.User; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.settings.AppProps; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.onprc_ssu.ONPRC_SSUSchema; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created by: Kollil + * Date: 8/23/24 + */ +public class SSU_RoundsNotification extends AbstractNotification +{ + public SSU_RoundsNotification(Module owner) + { + super(owner); + } + + @Override + public String getName() + { + return "Surgery Rounds Notification"; + } + + @Override + public String getEmailSubject(Container c) + { + return "Surgery Rounds Notification: " + getDateTimeFormat(c).format(new Date()); + } + + @Override + public String getCronString() + { + return "0 0 13 * * ?"; + } + + @Override + public String getCategory() + { + return "SSU"; + } + + @Override + public String getScheduleDescription() + { + return "every day at 1pm"; + } + + @Override + public String getDescription() + { + return "The report is designed to provide a summary of surgeries performed today and alert for any surgeries missing the post-op meds."; + } + + @Override + public String getMessageBodyHTML(Container c, User u) + { + StringBuilder msg = new StringBuilder(); + //Find today's date + Date now = new Date(); + + msg.append("This email contains any surgery rounds not marked as completed. It was run on: " + getDateFormat(c).format(now) + " at " + _timeFormat.format(now) + "

"); + + Container ehrContainer = EHRService.get().getEHRStudyContainer(c); + if (ehrContainer != null && ehrContainer.hasPermission(u, ReadPermission.class)) + { + animalsWithoutRoundsToday(ehrContainer, u, msg); + } + return msg.toString(); + } + + private String safeAppend(Results rs, String fieldKey, String emptyText) throws SQLException + { + String ret = rs.getString(FieldKey.fromString(fieldKey)); + return ret == null ? emptyText : ret; + } + + protected String getParticipantURL(Container c, String id) + { + return AppProps.getInstance().getBaseServerUrl() + AppProps.getInstance().getContextPath() + "/ehr" + c.getPath() + "/participantView.view?participantId=" + id; + } + + protected void animalsWithoutRoundsToday(final Container ehrContainer, User u, final StringBuilder msg) + { + //Get observed count + SimpleFilter filter2 = new SimpleFilter(FieldKey.fromString("daysSinceLastRounds"), 0, CompareType.EQUAL); + filter2.addCondition(FieldKey.fromString("isActive"), true, CompareType.EQUAL); + filter2.addCondition(FieldKey.fromString("category"), "Surgery", CompareType.EQUAL); + filter2.addCondition(FieldKey.fromString("Id/demographics/calculated_status"), "Alive", CompareType.EQUAL); + + TableInfo ti2 = QueryService.get().getUserSchema(u, ehrContainer, "study").getTable("cases"); + Set keys2 = new HashSet<>(); + keys2.add(FieldKey.fromString("Id")); + keys2.add(FieldKey.fromString("Id/curLocation/room")); + keys2.add(FieldKey.fromString("Id/curLocation/cage")); + keys2.add(FieldKey.fromString("daysSinceLastRounds")); + keys2.add(FieldKey.fromString("date")); + keys2.add(FieldKey.fromString("remark")); + + final Map cols2 = QueryService.get().getColumns(ti2, keys2); + + TableSelector ts2 = new TableSelector(ti2, cols2.values(), filter2, new Sort("Id/curLocation/room_sortValue,Id/curLocation/cage_sortValue")); + long observed_count = ts2.getRowCount(); + + //Get total count + SimpleFilter filter = new SimpleFilter(FieldKey.fromString("daysSinceLastRounds"), 0, CompareType.GTE); + filter.addCondition(FieldKey.fromString("isActive"), true, CompareType.EQUAL); + filter.addCondition(FieldKey.fromString("category"), "Surgery", CompareType.EQUAL); + filter.addCondition(FieldKey.fromString("Id/demographics/calculated_status"), "Alive", CompareType.EQUAL); + + TableInfo ti = QueryService.get().getUserSchema(u, ehrContainer, "study").getTable("cases"); + Set keys = new HashSet<>(); + keys.add(FieldKey.fromString("Id")); + keys.add(FieldKey.fromString("Id/curLocation/room")); + keys.add(FieldKey.fromString("Id/curLocation/cage")); + keys.add(FieldKey.fromString("daysSinceLastRounds")); + keys.add(FieldKey.fromString("date")); + keys.add(FieldKey.fromString("remark")); + + final Map cols = QueryService.get().getColumns(ti, keys); + + TableSelector ts = new TableSelector(ti, cols.values(), filter, new Sort("Id/curLocation/room_sortValue,Id/curLocation/cage_sortValue")); + long total_count = ts.getRowCount(); + + if (observed_count <= total_count) + { + msg.append("Rounds: " + observed_count + " of " + total_count + " active surgical cases that have obs entered today.
"); + msg.append("

Click here to view them
\n"); + msg.append("


\n"); + } + if (total_count == 0) + { + msg.append("Rounds: " + observed_count + " of " + observed_count + " active surgical cases that have obs entered today.
"); + msg.append("

Click here to view them
\n"); + msg.append("


\n"); + } + + msg.append(""); + msg.append(""); + ts.forEach(new Selector.ForEachBlock() + { + public void exec(ResultSet object) throws SQLException + { + Results rs = new ResultsImpl(object, cols); + String url = getParticipantURL(ehrContainer, rs.getString("Id")); + //Get the status + int status = rs.getInt("daysSinceLastRounds"); + //If not "completed", highlight the record with yellow color + if (status > 0) { + msg.append(""); + } + msg.append(""); + msg.append(""); + msg.append(""); + msg.append(""); + msg.append(""); + msg.append(""); + msg.append(""); + } + }); + msg.append("
IdRoomCageDate OpenedDescriptionDays Since Last Rounds
" + rs.getString(FieldKey.fromString("Id")) + "" + safeAppend(rs, "Id/curLocation/room", "None") + "" + safeAppend(rs, "Id/curLocation/cage", "") + "" + safeAppend(rs, "date", "") + "" + safeAppend(rs, "remark", "") + "" + safeAppend(rs, "daysSinceLastRounds", "") + "
"); + msg.append("
\n"); + } +} \ No newline at end of file