lp1839341 Port Org Setting Editor UI
[evergreen-equinox.git] / Open-ILS / src / eg2 / src / app / staff / admin / local / org-unit-settings / org-unit-settings.component.ts
1 import {Component, OnInit, Input, ViewChild, ViewEncapsulation
2     } from '@angular/core';
3 import {Router} from '@angular/router';
4 import {Observable, Observer, of} from 'rxjs';
5 import {map} from 'rxjs/operators';
6 import {Pager} from '@eg/share/util/pager';
7 import {IdlObject, IdlService} from '@eg/core/idl.service';
8 import {OrgService} from '@eg/core/org.service';
9 import {PcrudService} from '@eg/core/pcrud.service';
10 import {AuthService} from '@eg/core/auth.service';
11 import {NetService} from '@eg/core/net.service';
12 import {GridDataSource} from '@eg/share/grid/grid';
13 import {GridComponent} from '@eg/share/grid/grid.component';
14 import {GridToolbarCheckboxComponent
15     } from '@eg/share/grid/grid-toolbar-checkbox.component';
16 import {StoreService} from '@eg/core/store.service';
17 import {ServerStoreService} from '@eg/core/server-store.service';
18 import {ToastService} from '@eg/share/toast/toast.service';
19
20 import {EditOuSettingDialogComponent
21     } from '@eg/staff/admin/local/org-unit-settings/edit-org-unit-setting-dialog.component';
22 import {OuSettingHistoryDialogComponent
23     } from '@eg/staff/admin/local/org-unit-settings/org-unit-setting-history-dialog.component';
24 import {OuSettingJsonDialogComponent
25     } from '@eg/staff/admin/local/org-unit-settings/org-unit-setting-json-dialog.component';
26
27 export class OrgUnitSetting {
28     name: string;
29     label: string;
30     grp: string;
31     description: string;
32     value: any;
33     value_str: any;
34     dataType: string;
35     fmClass: string;
36     _idlOptions: IdlObject[];
37     _org_unit: IdlObject;
38     context: string;
39     view_perm: string;
40     _history: any[];
41 }
42
43 @Component({
44     templateUrl: './org-unit-settings.component.html'
45 })
46
47 export class OrgUnitSettingsComponent {
48
49     contextOrg: IdlObject;
50
51     initDone = false;
52     gridDataSource: GridDataSource;
53     gridTemplateContext: any;
54     prevFilter: string;
55     currentHistory: any[];
56     currentOptions: any[];
57     jsonFieldData: {};
58     @ViewChild('orgUnitSettingsGrid', { static:true }) orgUnitSettingsGrid: GridComponent;
59
60     @ViewChild('editOuSettingDialog', { static:true })
61         private editOuSettingDialog: EditOuSettingDialogComponent;
62     @ViewChild('orgUnitSettingHistoryDialog', { static:true })
63         private orgUnitSettingHistoryDialog: OuSettingHistoryDialogComponent;
64     @ViewChild('ouSettingJsonDialog', { static:true })
65         private ouSettingJsonDialog: OuSettingJsonDialogComponent;
66
67     refreshSettings: boolean;
68     renderFromPrefs: boolean;
69
70     settingTypeArr: any[];
71
72     @Input() filterString: string;
73
74     constructor(
75         private router: Router,
76         private org: OrgService,
77         private idl: IdlService,
78         private pcrud: PcrudService,
79         private auth: AuthService,
80         private store: ServerStoreService,
81         private localStore: StoreService,
82         private toast: ToastService,
83         private net: NetService,
84     ) {
85         this.gridDataSource = new GridDataSource();
86         this.refreshSettings = true;
87         this.renderFromPrefs = true;
88
89         this.contextOrg = this.org.get(this.auth.user().ws_ou());
90     }
91
92     ngOnInit() {
93         this.initDone = true;
94         this.settingTypeArr = [];
95         this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
96             return this.fetchSettingTypes(pager);
97         };
98     }
99
100     fetchSettingTypes(pager: Pager): Observable<any> {
101         return new Observable<any>(observer => {
102             this.pcrud.retrieveAll('coust', {flesh: 3, flesh_fields: {
103                 'coust': ['grp', 'view_perm']
104             }},
105             { authoritative: true }).subscribe(
106                 settingTypes => this.allocateSettingTypes(settingTypes),
107                 err => {},
108                 ()  => {
109                     this.refreshSettings = false;
110                     this.mergeSettingValues().then(
111                         ok => {
112                             this.flattenSettings(observer);
113                         }
114                     );
115                 }
116             );
117         });
118     }
119
120     mergeSettingValues(): Promise<any> {
121         const settingNames = this.settingTypeArr.map(setting => setting.name);
122         return new Promise((resolve, reject) => {
123             this.net.request(
124                 'open-ils.actor',
125                 'open-ils.actor.ou_setting.ancestor_default.batch',
126                  this.contextOrg.id(), settingNames, this.auth.token()
127             ).subscribe(
128                 blob => {
129                     let settingVals = Object.keys(blob).map(key => {
130                         return {'name': key, 'setting': blob[key]}
131                     });
132                     settingVals.forEach(key => {
133                         if (key.setting) {
134                             let settingsObj = this.settingTypeArr.filter(
135                                 setting => setting.name == key.name
136                             )[0];
137                             settingsObj.value = key.setting.value;
138                             settingsObj.value_str = settingsObj.value;
139                             if (settingsObj.dataType == 'link' && (key.setting.value || key.setting.value == 0)) {
140                                 this.fetchLinkedField(settingsObj.fmClass, key.setting.value, settingsObj.value_str).then(res => {
141                                     settingsObj.value_str = res;
142                                 });
143                             }
144                             settingsObj._org_unit = this.org.get(key.setting.org);
145                             settingsObj.context = settingsObj._org_unit.shortname();
146                         }
147                     });
148                     resolve(this.settingTypeArr);
149                 },
150                 err => reject(err)
151             );
152         });
153     }
154
155     fetchLinkedField(fmClass, id, val) {
156         return new Promise((resolve, reject) => {
157             return this.pcrud.retrieve(fmClass, id).subscribe(linkedField => {
158                 val = linkedField.name();
159                 resolve(val);
160             });
161         });
162     }
163
164     fetchHistory(setting): Promise<any> {
165         let name = setting.name;
166         return new Promise((resolve, reject) => {
167             this.net.request(
168                 'open-ils.actor',
169                 'open-ils.actor.org_unit.settings.history.retrieve',
170                 this.auth.token(), name, this.contextOrg.id()
171             ).subscribe(res=> {
172                 this.currentHistory = [];
173                 if (!Array.isArray(res)) {
174                     res = [res];
175                 }
176                 res.forEach(log => {
177                     log.org = this.org.get(log.org);
178                     log.new_value_str = log.new_value;
179                     log.original_value_str = log.original_value;
180                     if (setting.dataType == "link") {
181                         if (log.new_value) {
182                             this.fetchLinkedField(setting.fmClass, parseInt(log.new_value), log.new_value_str).then(val => {
183                                 log.new_value_str = val;
184                             });
185                         }
186                         if (log.original_value) {
187                             this.fetchLinkedField(setting.fmClass, parseInt(log.original_value), log.original_value_str).then(val => {
188                                 log.original_value_str = val;
189                             });
190                         }
191                     }
192                     if (log.new_value_str) log.new_value_str = log.new_value_str.replace(/^"(.*)"$/, '$1');
193                     if (log.original_value_str) log.original_value_str = log.original_value_str.replace(/^"(.*)"$/, '$1');
194                 });
195                 this.currentHistory = res;
196                 this.currentHistory.sort((a, b) => {
197                     return a.date_applied < b.date_applied ? 1 : -1;
198                 });
199
200                 resolve(this.currentHistory);
201             }, err=>{reject(err);});
202         });
203     }
204
205     allocateSettingTypes(coust: IdlObject) {
206         let entry = new OrgUnitSetting();
207         entry.name = coust.name();
208         entry.label = coust.label();
209         entry.dataType = coust.datatype();
210         if (coust.fm_class()) entry.fmClass = coust.fm_class();
211         if (coust.description()) entry.description = coust.description();
212         // For some reason some setting types don't have a grp, should look into this...
213         if (coust.grp()) entry.grp = coust.grp().label();
214         if (coust.view_perm()) 
215             entry.view_perm = coust.view_perm().code();
216
217         this.settingTypeArr.push(entry);
218     }
219
220     flattenSettings(observer: Observer<any>) {
221         this.gridDataSource.data = this.settingTypeArr;
222         observer.complete();
223     }
224
225     contextOrgChanged(org: IdlObject) {
226         this.updateGrid(org);
227     }
228
229     applyFilter(clear?: boolean) {
230         if (clear) this.filterString = '';
231         this.updateGrid(this.contextOrg);
232     }
233     
234     updateSetting(obj, entry) {
235         this.net.request(
236             'open-ils.actor',
237             'open-ils.actor.org_unit.settings.update',
238             this.auth.token(), obj.context.id(), obj.setting
239         ).toPromise().then(res=> {
240             this.toast.success(entry.label + " Updated.");
241             if (!obj.setting[entry.name]) {
242                 let settingsObj = this.settingTypeArr.filter(
243                     setting => setting.name == entry.name
244                 )[0];
245                 settingsObj.value = null;
246                 settingsObj._org_unit = null;
247                 settingsObj.context = null;
248             }
249             this.mergeSettingValues();
250         },
251         err => {
252             this.toast.danger(entry.label + " failed to update: " + err.desc);
253         });
254     }
255
256     showEditSettingValueDialog(entry: OrgUnitSetting) {
257         this.editOuSettingDialog.entry = entry;
258         this.editOuSettingDialog.entryValue = entry.value;
259         this.editOuSettingDialog.entryContext = entry._org_unit || this.contextOrg;
260         this.editOuSettingDialog.open({size: 'lg'}).subscribe(
261             res => {
262                 this.updateSetting(res, entry);
263             }
264         );
265     }
266
267     showHistoryDialog(entry: OrgUnitSetting) {
268         if (entry) {
269             this.fetchHistory(entry).then(
270                 fetched => {
271                     this.orgUnitSettingHistoryDialog.history = this.currentHistory;
272                     this.orgUnitSettingHistoryDialog.gridDataSource.data = this.currentHistory;
273                     this.orgUnitSettingHistoryDialog.entry = entry;
274                     this.orgUnitSettingHistoryDialog.open({size: 'lg'}).subscribe(res => {
275                         if (res.revert) {
276                             this.updateSetting(res, entry);
277                         }
278                     });
279                 }
280             )
281         }
282     }
283
284     showJsonDialog(isExport: boolean) {
285         this.ouSettingJsonDialog.isExport = isExport;
286         this.ouSettingJsonDialog.jsonData = "";
287         if (isExport) {
288             this.ouSettingJsonDialog.jsonData = "{";
289             this.gridDataSource.data.forEach(entry => {
290                 this.ouSettingJsonDialog.jsonData +=
291                     "\"" + entry.name + "\": {\"org\": \"" +
292                     this.contextOrg.id() + "\", \"value\": ";
293                 if (entry.value) {
294                     this.ouSettingJsonDialog.jsonData += "\"" + entry.value + "\"";
295                 } else {
296                     this.ouSettingJsonDialog.jsonData += "null";
297                 }
298                 this.ouSettingJsonDialog.jsonData += "}";
299                 if (this.gridDataSource.data.indexOf(entry) != (this.gridDataSource.data.length - 1))
300                     this.ouSettingJsonDialog.jsonData += ",";
301             });
302             this.ouSettingJsonDialog.jsonData += "}";
303         }
304
305         this.ouSettingJsonDialog.open({size: 'lg'}).subscribe(res => {
306             if (res.apply && res.jsonData) {
307                 let jsonSettings = JSON.parse(res.jsonData);
308                 Object.entries(jsonSettings).forEach((fields) => {
309                     let entry = this.settingTypeArr.find(x => x.name == fields[0]);
310                     let obj = {setting: {}, context: {}};
311                     let val = this.parseValType(fields[1]['value'], entry.dataType);
312                     obj.setting[fields[0]] = val;
313                     obj.context = this.org.get(fields[1]['org']);
314                     this.updateSetting(obj, entry);
315                 });
316             }
317         });
318     }
319
320     parseValType(value, dataType) {
321         if (dataType == "integer" || "currency" || "link") {
322             return Number(value);
323         } else if (dataType == "bool") {
324             return (value === 'true');
325         } else {
326             return value;
327         }
328     }
329     
330     filterCoust() {
331         if (this.filterString != this.prevFilter) {
332             this.prevFilter = this.filterString;
333             if (this.filterString) {
334                 this.gridDataSource.data = [];
335                 let tempGrid = this.settingTypeArr;
336                 tempGrid.forEach(row => {
337                     let containsString =
338                          row.name.includes(this.filterString) ||
339                          row.label.includes(this.filterString) ||
340                          (row.grp && row.grp.includes(this.filterString)) ||
341                          (row.description && row.description.includes(this.filterString));
342                     if (containsString) {
343                         this.gridDataSource.data.push(row);
344                     }
345                 });
346             } else {
347                 this.gridDataSource.data = this.settingTypeArr;
348             }
349         }
350     }
351
352     updateGrid(org) {
353         if (this.contextOrg != org) {
354             this.contextOrg = org;
355             this.refreshSettings = true;
356         }
357
358         if (this.filterString != this.prevFilter) {
359             this.refreshSettings = true;
360         }
361
362         if (this.refreshSettings) { 
363             this.mergeSettingValues().then(
364                 res => this.filterCoust()
365             );
366         }
367     }
368 }