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';
8 type OrgNodeOrId = number | IdlObject;
11 canHaveUsers?: boolean;
12 canHaveVolumes?: boolean;
13 opacVisible?: boolean;
18 interface OrgSettingsBatch {
22 @Injectable({providedIn: 'root'})
23 export class OrgService {
25 private orgList: IdlObject[] = [];
26 private orgTree: IdlObject; // root node + children
27 private orgMap: {[id: number]: IdlObject} = {};
28 private settingsCache: OrgSettingsBatch = {};
30 private orgTypeMap: {[id: number]: IdlObject} = {};
31 private orgTypeList: IdlObject[] = [];
34 private net: NetService,
35 private auth: AuthService,
36 private pcrud: PcrudService
39 get(nodeOrId: OrgNodeOrId): IdlObject {
40 if (typeof nodeOrId === 'object') {
43 return this.orgMap[nodeOrId];
50 typeList(): IdlObject[] {
51 return this.orgTypeList;
54 typeMap(): {[id: number]: IdlObject} {
55 return this.orgTypeMap;
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.
63 filterList(filter: OrgFilter, asId?: boolean): any[] {
65 this.list().forEach(org => {
67 const chu = filter.canHaveUsers;
68 if (chu && !this.canHaveUsers(org)) { return; }
69 if (chu === false && this.canHaveUsers(org)) { return; }
71 const chv = filter.canHaveVolumes;
72 if (chv && !this.canHaveVolumes(org)) { return; }
73 if (chv === false && this.canHaveVolumes(org)) { return; }
75 const ov = filter.opacVisible;
76 if (ov && !this.opacVisible(org)) { return; }
77 if (ov === false && this.opacVisible(org)) { return; }
79 if (filter.inList && !filter.inList.includes(org.id())) {
83 if (filter.notInList && filter.notInList.includes(org.id())) {
87 // All filter tests passed. Add it to the list
88 list.push(asId ? org.id() : org);
100 return this.orgList[0];
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())) ) {
112 return nodes.map(n => n.id());
117 // tests that a node can have users
118 canHaveUsers(nodeOrId): boolean {
119 return this.get(nodeOrId).ou_type().can_have_users() === 't';
122 // tests that a node can have volumes
123 canHaveVolumes(nodeOrId): boolean {
127 .can_have_vols() === 't';
130 opacVisible(nodeOrId): boolean {
131 return this.get(nodeOrId).opac_visible() === 't';
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 []; }
139 const descend = (n) => {
141 n.children().forEach(descend);
145 return nodes.map(n => n.id());
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));
155 return list.map(n => n.id());
160 sortTree(sortField?: string, node?: IdlObject): void {
161 if (!sortField) { sortField = 'shortname'; }
162 if (!node) { node = this.orgTree; }
164 node.children.sort((a, b) => {
165 return a[sortField]() < b[sortField]() ? -1 : 1;
168 node.children.forEach(n => this.sortTree(n));
171 absorbTree(node?: IdlObject): void {
176 this.orgTypeMap = {};
178 this.orgMap[node.id()] = node;
179 this.orgList.push(node);
181 this.orgTypeMap[node.ou_type().id()] = node.ou_type();
182 if (!this.orgTypeList.filter(t => t.id() === node.ou_type().id())[0]) {
183 this.orgTypeList.push(node.ou_type());
186 node.children().forEach(c => this.absorbTree(c));
190 * Grabs all of the org units from the server, chops them up into
191 * various shapes, then returns an "all done" promise.
193 fetchOrgs(): Promise<void> {
194 return this.pcrud.search('aou', {parent_ou : null},
195 {flesh : -1, flesh_fields : {aou : ['children', 'ou_type']}},
197 ).toPromise().then(tree => {
205 * Populate 'target' with settings from cache where available.
206 * Return the list of settings /not/ pulled from cache.
208 private settingsFromCache(names: string[], target: any) {
209 const cacheKeys = Object.keys(this.settingsCache);
211 cacheKeys.forEach(key => {
212 const matchIdx = names.indexOf(key);
214 target[key] = this.settingsCache[key];
215 names.splice(matchIdx, 1);
223 * Fetch org settings from the network.
224 * 'auth' is null for anonymous lookup.
226 private settingsFromNet(orgId: number,
227 names: string[], auth?: string): Promise<any> {
230 return new Promise((resolve, reject) => {
233 'open-ils.actor.ou_setting.ancestor_default.batch',
237 Object.keys(blob).forEach(key => {
238 const val = blob[key]; // null or hash
239 settings[key] = val ? val.value : null;
252 settings(name: string | string[],
253 orgId?: number, anonymous?: boolean): Promise<OrgSettingsBatch> {
255 let names = [].concat(name);
257 let auth: string = null;
258 let useCache = false;
260 if (this.auth.user()) {
262 useCache = Number(orgId) === Number(this.auth.user().ws_ou());
264 orgId = this.auth.user().ws_ou();
268 // avoid passing auth token when anonymous is requested.
270 auth = this.auth.token();
273 } else if (!anonymous) {
274 console.warn('Attempt to fetch org setting(s)',
275 name, 'in non-anonymous mode without an authtoken');
276 return Promise.resolve({});
280 names = this.settingsFromCache(names, settings);
283 // All requested settings found in cache (or name list is empty)
284 if (names.length === 0) {
285 return Promise.resolve(settings);
288 return this.settingsFromNet(orgId, names, auth)
291 Object.keys(sets).forEach(key => {
292 this.settingsCache[key] = sets[key];