Skip to content

Commit fe6a2c4

Browse files
Merge 25.3 to develop
2 parents 7806be4 + 7aaed6e commit fe6a2c4

File tree

4 files changed

+270
-7
lines changed

4 files changed

+270
-7
lines changed

ehr/resources/web/ehr/navMenu.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,19 @@ Ext4.define('EHR.NavMenu', {
107107
item = this.renderer(tmp.items[j])
108108
}
109109
else {
110-
//NOTE: this is the default renderer
111-
item = {
112-
//Creates links for the navigation panel
113-
html: '<a href="'+tmp.items[j].url+'">'+tmp.items[j].name+'</a>'
110+
if (tmp.items[j].target){
111+
item = {
112+
//Creates links for the navigation panel to a target
113+
html: '<a target="'+tmp.items[j].target+'" href="'+tmp.items[j].url+'">'+tmp.items[j].name+'</a>'
114+
}
115+
}else{
116+
item = {
117+
//Creates links for the navigation panel
118+
html: '<a href="'+tmp.items[j].url+'">'+tmp.items[j].name+'</a>'
119+
}
114120
}
121+
//NOTE: this is the default renderer
122+
115123
}
116124
section.add(item)
117125
}

ehr/resources/web/ehr/window/AddAnimalsWindow.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Ext4.define('EHR.window.AddAnimalsWindow', {
1717
MAX_ANIMALS: 350,
1818

1919
bulkEditCheckDisabled: false,
20+
upperCaseAnimalId: false,
2021

2122
initComponent: function(){
2223
Ext4.apply(this, {
@@ -170,7 +171,7 @@ Ext4.define('EHR.window.AddAnimalsWindow', {
170171

171172
var records = [];
172173
Ext4.Array.forEach(subjectList, function(s){
173-
var model = Ext4.isObject(s) ? s : {Id: s};
174+
var model = Ext4.isObject(s) ? s : { Id: this.upperCaseAnimalId ? s.toUpperCase() : s };
174175
if (date) {
175176
model.date = date;
176177
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*
2+
* Copyright (c) 2018-2025 LabKey Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
/**
7+
* @cfg targetStore
8+
*/
9+
Ext4.define('EHR.window.FormBulkAddWindow', {
10+
extend: 'Ext.window.Window',
11+
12+
modal: true,
13+
closeAction: 'destroy',
14+
title: 'Bulk Add Data',
15+
bodyStyle: 'padding: 5px;',
16+
width: 1000,
17+
defaults: {
18+
border: false
19+
},
20+
21+
fieldConfigs: [],
22+
fieldNames: [],
23+
requiredFieldConfigs: [],
24+
requiredFieldNames: [],
25+
upperCaseAnimalId: false,
26+
27+
initComponent: function(){
28+
const section = this.targetStore?.sectionCfg;
29+
if (!section) {
30+
Ext4.Msg.alert('Error', 'Unable to find form section. Check that the form is configured correctly.');
31+
return;
32+
}
33+
const allConfigs = EHR.model.DefaultClientModel.getFieldConfigs(section.fieldConfigs, section.configSources, this.extraMetaData);
34+
this.fieldConfigs = allConfigs.filter((f) => {
35+
return !f.hidden && !f.isHidden && f.name.toLowerCase() !== 'taskid' && f.name.toLowerCase() !== "qcstate";
36+
});
37+
if (!this.fieldConfigs) {
38+
Ext4.Msg.alert('Error', 'Unable to find fields in target store. Check that the form is configured correctly.');
39+
return;
40+
}
41+
42+
this.fieldNames = this.fieldConfigs.map((f) => {
43+
const alias = f.importAliases?.[0]; // If the import alias matches the label, use that in the template
44+
if (alias?.toLowerCase() === f.label?.toLowerCase()) {
45+
return alias;
46+
}
47+
return f.name;
48+
});
49+
50+
this.requiredFieldConfigs = this.fieldConfigs.filter((f) => {
51+
return f.required;
52+
});
53+
54+
this.requiredFieldNames = this.requiredFieldConfigs.map((f) => {
55+
return f.name;
56+
});
57+
58+
this.items = [{
59+
html : 'This allows you to import data using a simple Excel or TSV file. To import, cut/paste the contents of the Excel or TSV file (Ctl + A is a good way to select all) into the box below and hit submit. The limit for import is 250 rows.',
60+
style: 'padding-bottom: 10px;'
61+
},{
62+
xtype: 'ldk-linkbutton',
63+
text: '[Download Template]',
64+
scope: this,
65+
linkTarget: '_blank',
66+
handler: function(){
67+
LABKEY.Utils.convertToExcel({
68+
fileName: this.targetStore.sectionCfg.label + '.xlsx',
69+
sheets: [{
70+
name: 'Requests',
71+
data: [
72+
this.fieldNames
73+
]
74+
}]
75+
});
76+
}
77+
},{
78+
xtype: 'textarea',
79+
width: 970,
80+
height: 400,
81+
itemId: 'textField'
82+
}];
83+
84+
this.buttons = [{
85+
text: 'Submit',
86+
scope: this,
87+
handler: this.onSubmit
88+
},{
89+
text: 'Cancel',
90+
handler: function(btn){
91+
btn.up('window').close();
92+
}
93+
}];
94+
95+
this.projectStore = EHR.DataEntryUtils.getProjectStore();
96+
97+
this.callParent(arguments);
98+
},
99+
100+
onSubmit: function(){
101+
const text = this.down('#textField').getValue();
102+
if (!text){
103+
Ext4.Msg.alert('Error', 'Must paste the records into the text area');
104+
return;
105+
}
106+
107+
const parsed = LDK.Utils.CSVToArray(Ext4.String.trim(text), '\t');
108+
if (!parsed){
109+
Ext4.Msg.alert('Error', 'There was an error parsing the data.');
110+
return;
111+
}
112+
113+
if (parsed.length < 2){
114+
Ext4.Msg.alert('Error', 'There are not enough rows in the text, there was an error parsing the data.');
115+
return;
116+
}
117+
118+
this.doParse(parsed);
119+
},
120+
121+
doParse: function(parsed){
122+
const errors = [];
123+
const records = [];
124+
125+
//first get global values:
126+
Ext4.Msg.wait('Processing...');
127+
128+
const dataRowCount = parsed.length - 1;
129+
if (dataRowCount > 250) {
130+
errors.push('Row Count - ' + dataRowCount + ': Import maximum is 250 rows. Please split your import into multiple uploads and submit the form between each upload.');
131+
}
132+
else {
133+
134+
for (let i = 1; i < parsed.length; i++) {
135+
const row = parsed[i];
136+
if (!row || row.length < this.requiredFieldNames.length) {
137+
errors.push('Row ' + i + ': not enough items in row');
138+
continue;
139+
}
140+
141+
const newRow = this.processRow(parsed[0], row, errors, i);
142+
if (newRow) {
143+
records.push(this.targetStore.createModel(newRow));
144+
}
145+
}
146+
147+
Ext4.Msg.hide();
148+
}
149+
150+
if (errors.length){
151+
Ext4.Msg.alert('Error', 'There following errors were found:<p>' + errors.join('<br>'));
152+
return;
153+
}
154+
155+
if (records.length){
156+
this.targetStore.add(records);
157+
}
158+
159+
this.close();
160+
},
161+
162+
resolveLookup: function(field, value, errors){
163+
if (!field || !field.lookup)
164+
return value;
165+
166+
if (!field.lookup.store) {
167+
const storeId = LABKEY.ext4.Util.getLookupStoreId(field);
168+
169+
field.lookup.store = Ext4.StoreMgr.get(storeId);
170+
if (!field.lookup.store) {
171+
console.log('Unable to find lookup store for ' + storeId);
172+
173+
return value;
174+
}
175+
}
176+
177+
const lookupRecord = field.lookup.store.findRecord(field.lookup.displayColumn, value);
178+
if (lookupRecord) {
179+
return lookupRecord.data[field.lookup.keyColumn];
180+
}
181+
182+
return value;
183+
},
184+
185+
processRow: function(headers, row, errors, rowIdx){
186+
const obj = {
187+
Id: this.upperCaseAnimalId ? row[headers.indexOf('Id')].toUpperCase() : row[headers.indexOf('Id')],
188+
date: LDK.ConvertUtils.parseDate(row[headers.indexOf('date')]),
189+
project: this.resolveProjectByName(row[headers.indexOf('project')], errors, rowIdx)
190+
}
191+
192+
Ext4.each(this.fieldConfigs, function(field) {
193+
if (!obj[field.name]) {
194+
let index = headers.indexOf(field.name);
195+
if (index === -1 && field.importAliases?.[0]) {
196+
index = headers.indexOf(field.importAliases?.[0]);
197+
}
198+
if (index !== -1) {
199+
obj[field.name] = this.resolveLookup(field, row[index], errors);
200+
}
201+
}
202+
}, this);
203+
204+
if (!this.checkRequired(this.requiredFieldNames, obj, errors, rowIdx)){
205+
return obj;
206+
}
207+
},
208+
209+
checkRequired: function(fields, row, errors, rowIdx){
210+
let hasErrors = false, fieldName;
211+
212+
for (let i=0;i<fields.length;i++){
213+
fieldName = fields[i];
214+
if (Ext4.isEmpty(row[fieldName])){
215+
errors.push('Row ' + rowIdx + ': missing required field ' + fieldName);
216+
hasErrors = true;
217+
}
218+
}
219+
220+
return hasErrors;
221+
},
222+
223+
resolveProjectByName: function(projectName, errors, rowIdx){
224+
if (!projectName){
225+
return null;
226+
}
227+
228+
projectName = Ext4.String.leftPad(projectName, 4, '0');
229+
230+
const recIdx = this.projectStore.find('name', projectName);
231+
if (recIdx === -1){
232+
errors.push('Row ' + rowIdx + ': unknown project ' + projectName);
233+
return null;
234+
}
235+
236+
return this.projectStore.getAt(recIdx).get('project');
237+
}
238+
});
239+
240+
EHR.DataEntryUtils.registerGridButton('FORM_BULK_ADD', function(config){
241+
return Ext4.Object.merge({
242+
text: 'Add From File',
243+
tooltip: 'Click to bulk import records from an Excel or TSV file.',
244+
handler: function(btn){
245+
const grid = btn.up('grid');
246+
LDK.Assert.assertNotEmpty('Unable to find grid in FORM_BULK_ADD button', grid);
247+
248+
Ext4.create('EHR.window.FormBulkAddWindow', {
249+
targetStore: grid.store
250+
}).show();
251+
}
252+
});
253+
});

ehr/resources/web/ehr/window/SaveTemplateWindow.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ Ext4.define('EHR.window.SaveTemplateWindow', {
204204
var rows = [];
205205

206206
this.down('#theForm').items.each(function(tab){
207-
var selections = tab.down('#recordSelector').getValue().inputValue;
207+
var radioGroup = tab.down('#recordSelector');
208+
var selections = radioGroup.getValue()[radioGroup.down('[name]').name];
208209
var fields = tab.down('#fieldSelector').getValue().fields;
209210

210211
if (!fields.length)
@@ -220,7 +221,7 @@ Ext4.define('EHR.window.SaveTemplateWindow', {
220221

221222
var records = [];
222223
if (selections == 'selected'){
223-
records = this.grid.getSelectionModel().getSelections();
224+
records = this.targetGrid.getSelectionModel().getSelection();
224225

225226
if (!records.length){
226227
Ext4.Msg.hide();

0 commit comments

Comments
 (0)