]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/core/org.service.ts
LP1823981 Angular Permission Group Tree Admin UI
[Evergreen.git] / Open-ILS / src / eg2 / src / app / core / org.service.ts
1 import {Injectable} from '@angular/core';
2 import {Observable} from 'rxjs';
3 import {IdlObject, IdlService} from './idl.service';
4 import {NetService} from './net.service';
5 import {AuthService} from './auth.service';
6 import {PcrudService} from './pcrud.service';
7
8 type OrgNodeOrId = number | IdlObject;
9
10 interface OrgFilter {
11     canHaveUsers?: boolean;
12     canHaveVolumes?: boolean;
13     opacVisible?: boolean;
14     inList?: number[];
15     notInList?: number[];
16 }
17
18 interface OrgSettingsBatch {
19     [key: string]: any;
20 }
21
22 @Injectable({providedIn: 'root'})
23 export class OrgService {
24
25     private orgList: IdlObject[] = [];
26     private orgTree: IdlObject; // root node + children
27     private orgMap: {[id: number]: IdlObject} = {};
28     private settingsCache: OrgSettingsBatch = {};
29
30     constructor(
31         private net: NetService,
32         private auth: AuthService,
33         private pcrud: PcrudService
34     ) {}
35
36     get(nodeOrId: OrgNodeOrId): IdlObject {
37         if (typeof nodeOrId === 'object') {
38             return nodeOrId;
39         }
40         return this.orgMap[nodeOrId];
41     }
42
43     list(): IdlObject[] {
44         return this.orgList;
45     }
46
47     // Returns a list of org unit type objects
48     typeList(): IdlObject[] {
49         const types = [];
50         this.list().forEach(org => {
51             if ((types.filter(t => t.id() === org.ou_type().id())).length === 0) {
52                 types.push(org.ou_type());
53             }
54         });
55         return types;
56     }
57
58     /**
59      * Returns a list of org units that match the selected criteria.
60      * All filters must match for an org to be included in the result set.
61      * Unset filter options are ignored.
62      */
63     filterList(filter: OrgFilter, asId?: boolean): any[] {
64         const list = [];
65         this.list().forEach(org => {
66
67             const chu = filter.canHaveUsers;
68             if (chu && !this.canHaveUsers(org)) { return; }
69             if (chu === false && this.canHaveUsers(org)) { return; }
70
71             const chv = filter.canHaveVolumes;
72             if (chv && !this.canHaveVolumes(org)) { return; }
73             if (chv === false && this.canHaveVolumes(org)) { return; }
74
75             const ov = filter.opacVisible;
76             if (ov && !this.opacVisible(org)) { return; }
77             if (ov === false && this.opacVisible(org)) { return; }
78
79             if (filter.inList && !filter.inList.includes(org.id())) {
80                 return;
81             }
82
83             if (filter.notInList && filter.notInList.includes(org.id())) {
84                 return;
85             }
86
87             // All filter tests passed.  Add it to the list
88             list.push(asId ? org.id() : org);
89         });
90
91         return list;
92     }
93
94     tree(): IdlObject {
95         return this.orgTree;
96     }
97
98     // get the root OU
99     root(): IdlObject {
100         return this.orgList[0];
101     }
102
103     // list of org_unit objects or IDs for ancestors + me
104     ancestors(nodeOrId: OrgNodeOrId, asId?: boolean): any[] {
105         let node = this.get(nodeOrId);
106         if (!node) { return []; }
107         const nodes = [node];
108         while ( (node = this.get(node.parent_ou())) ) {
109             nodes.push(node);
110         }
111         if (asId) {
112             return nodes.map(n => n.id());
113         }
114         return nodes;
115     }
116
117     // tests that a node can have users
118     canHaveUsers(nodeOrId): boolean {
119         return this.get(nodeOrId).ou_type().can_have_users() === 't';
120     }
121
122     // tests that a node can have volumes
123     canHaveVolumes(nodeOrId): boolean {
124         return this
125             .get(nodeOrId)
126             .ou_type()
127             .can_have_vols() === 't';
128     }
129
130     opacVisible(nodeOrId): boolean {
131         return this.get(nodeOrId).opac_visible() === 't';
132     }
133
134     // list of org_unit objects  or IDs for me + descendants
135     descendants(nodeOrId: OrgNodeOrId, asId?: boolean): any[] {
136         const node = this.get(nodeOrId);
137         if (!node) { return []; }
138         const nodes = [];
139         const descend = (n) => {
140             nodes.push(n);
141             n.children().forEach(descend);
142         };
143         descend(node);
144         if (asId) {
145             return nodes.map(n => n.id());
146         }
147         return nodes;
148     }
149
150     // list of org_unit objects or IDs for ancestors + me + descendants
151     fullPath(nodeOrId: OrgNodeOrId, asId?: boolean): any[] {
152         const list = this.ancestors(nodeOrId, false).concat(
153           this.descendants(nodeOrId, false).slice(1));
154         if (asId) {
155             return list.map(n => n.id());
156         }
157         return list;
158     }
159
160     sortTree(sortField?: string, node?: IdlObject): void {
161         if (!sortField) { sortField = 'shortname'; }
162         if (!node) { node = this.orgTree; }
163         node.children(
164             node.children.sort((a, b) => {
165                 return a[sortField]() < b[sortField]() ? -1 : 1;
166             })
167         );
168         node.children.forEach(n => this.sortTree(n));
169     }
170
171     absorbTree(node?: IdlObject): void {
172         if (!node) {
173             node = this.orgTree;
174             this.orgMap = {};
175             this.orgList = [];
176         }
177         this.orgMap[node.id()] = node;
178         this.orgList.push(node);
179         node.children().forEach(c => this.absorbTree(c));
180     }
181
182     /**
183      * Grabs all of the org units from the server, chops them up into
184      * various shapes, then returns an "all done" promise.
185      */
186     fetchOrgs(): Promise<void> {
187         return this.pcrud.search('aou', {parent_ou : null},
188             {flesh : -1, flesh_fields : {aou : ['children', 'ou_type']}},
189             {anonymous : true}
190         ).toPromise().then(tree => {
191             // ingest tree, etc.
192             this.orgTree = tree;
193             this.absorbTree();
194         });
195     }
196
197     /**
198      * Populate 'target' with settings from cache where available.
199      * Return the list of settings /not/ pulled from cache.
200      */
201     private settingsFromCache(names: string[], target: any) {
202         const cacheKeys = Object.keys(this.settingsCache);
203
204         cacheKeys.forEach(key => {
205             const matchIdx = names.indexOf(key);
206             if (matchIdx > -1) {
207                 target[key] = this.settingsCache[key];
208                 names.splice(matchIdx, 1);
209             }
210         });
211
212         return names;
213     }
214
215     /**
216      * Fetch org settings from the network.
217      * 'auth' is null for anonymous lookup.
218      */
219     private settingsFromNet(orgId: number,
220         names: string[], auth?: string): Promise<any> {
221
222         const settings = {};
223         return new Promise((resolve, reject) => {
224             this.net.request(
225                 'open-ils.actor',
226                 'open-ils.actor.ou_setting.ancestor_default.batch',
227                 orgId, names, auth
228             ).subscribe(
229                 blob => {
230                     Object.keys(blob).forEach(key => {
231                         const val = blob[key]; // null or hash
232                         settings[key] = val ? val.value : null;
233                     });
234                     resolve(settings);
235                 },
236                 err => reject(err)
237             );
238         });
239     }
240
241
242     /**
243      *
244      */
245     settings(name: string | string[],
246         orgId?: number, anonymous?: boolean): Promise<OrgSettingsBatch> {
247
248         let names = [].concat(name);
249         const settings = {};
250         let auth: string = null;
251         let useCache = false;
252
253         if (this.auth.user()) {
254             if (orgId) {
255                 useCache = Number(orgId) === Number(this.auth.user().ws_ou());
256             } else {
257                 orgId = this.auth.user().ws_ou();
258                 useCache = true;
259             }
260
261             // avoid passing auth token when anonymous is requested.
262             if (!anonymous) {
263                 auth = this.auth.token();
264             }
265
266         } else if (!anonymous) {
267             console.warn('Attempt to fetch org setting(s)',
268                 name, 'in non-anonymous mode without an authtoken');
269             return Promise.resolve({});
270         }
271
272         if (useCache) {
273             names = this.settingsFromCache(names, settings);
274         }
275
276         // All requested settings found in cache (or name list is empty)
277         if (names.length === 0) {
278             return Promise.resolve(settings);
279         }
280
281         return this.settingsFromNet(orgId, names, auth)
282         .then(sets => {
283             if (useCache) {
284                 Object.keys(sets).forEach(key => {
285                     this.settingsCache[key] = sets[key];
286                 });
287             }
288             return sets;
289         });
290     }
291 }