45d1de556d616c78e4d4fe6765f1a3c70ad297d7
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / cat / vandelay / vandelay.service.ts
1 import {Injectable, EventEmitter} from '@angular/core';
2 import {Observable} from 'rxjs/Observable';
3 import {tap} from 'rxjs/operators/tap';
4 import {map} from 'rxjs/operators/map';
5 import {HttpClient} from '@angular/common/http';
6 import {saveAs} from 'file-saver/FileSaver';
7 import {IdlService, IdlObject} from '@eg/core/idl.service';
8 import {OrgService} from '@eg/core/org.service';
9 import {NetService} from '@eg/core/net.service';
10 import {AuthService} from '@eg/core/auth.service';
11 import {PcrudService} from '@eg/core/pcrud.service';
12 import {PermService} from '@eg/core/perm.service';
13 import {EventService} from '@eg/core/event.service';
14 import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
15
16 export const VANDELAY_EXPORT_PATH = '/exporter';
17 export const VANDELAY_UPLOAD_PATH = '/vandelay-upload';
18
19 export class VandelayImportSelection {
20     recordIds: number[];
21     queue: IdlObject;
22     importQueue: boolean; // import the whole queue
23     overlayMap: {[qrId: number]: /* breId */ number};
24
25     constructor() {
26        this.recordIds = [];
27        this.overlayMap = {};
28     }
29 }
30
31 @Injectable()
32 export class VandelayService {
33
34     allQueues: {[qtype: string]: IdlObject[]};
35     activeQueues: {[qtype: string]: IdlObject[]}; 
36     attrDefs: {[atype: string]: IdlObject[]};
37     bibSources: IdlObject[];
38     bibBuckets: IdlObject[];
39     copyStatuses: IdlObject[];
40     matchSets: {[stype: string]: IdlObject[]};
41     importItemAttrDefs: IdlObject[];
42     bibTrashGroups: IdlObject[];
43     mergeProfiles: IdlObject[];
44
45     // Used for tracking records between the queue page and
46     // the import page.  Fields managed externally.
47     importSelection: VandelayImportSelection;
48
49     // Track the last grid offset in the queue page so we
50     // can return the user to the same page of data after
51     // going to the matches page.
52     queuePageOffset: number;
53
54     constructor(
55         private http: HttpClient,
56         private idl: IdlService,
57         private org: OrgService,
58         private evt: EventService,
59         private net: NetService,
60         private auth: AuthService,
61         private pcrud: PcrudService,
62         private perm: PermService
63     ) {
64         this.attrDefs = {};
65         this.activeQueues = {};
66         this.allQueues = {};
67         this.matchSets = {};
68         this.importSelection = null;
69         this.queuePageOffset = 0;
70     }
71
72     getAttrDefs(dtype: string): Promise<IdlObject[]> {
73         if (this.attrDefs[dtype]) {
74             return Promise.resolve(this.attrDefs[dtype]);
75         }
76         const cls = (dtype === 'bib') ? 'vqbrad' : 'vqarad';
77         const orderBy = {};
78         orderBy[cls] = 'id'
79         return this.pcrud.retrieveAll(cls, 
80             {order_by: orderBy}, {atomic: true}).toPromise()
81         .then(list => {
82             this.attrDefs[dtype] = list;
83             return list;
84         });
85     }
86
87     getMergeProfiles(): Promise<IdlObject[]> {
88         if (this.mergeProfiles) {
89             return Promise.resolve(this.mergeProfiles);
90         }
91
92         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
93         return this.pcrud.search('vmp', 
94             {owner: owners}, {order_by: {vmp: ['name']}}, {atomic: true})
95         .toPromise().then(profiles => {
96             this.mergeProfiles = profiles;
97             return profiles;
98         });
99     }
100
101     // Returns a promise resolved with the list of queues.
102     // Also emits the onQueueListUpdate event so listeners
103     // can detect queue content changes.
104     getAllQueues(qtype: string): Promise<IdlObject[]> {
105         if (this.allQueues[qtype]) {
106             return Promise.resolve(this.allQueues[qtype]);
107         } else {
108             this.allQueues[qtype] = [];
109         }
110
111         // could be a big list, invoke in streaming mode
112         return this.net.request(
113             'open-ils.vandelay',
114             `open-ils.vandelay.${qtype}_queue.owner.retrieve`,
115             this.auth.token()
116         ).pipe(tap(
117             queue => this.allQueues[qtype].push(queue)
118         )).toPromise().then(() => this.allQueues[qtype]);
119     }
120
121
122     // Returns a promise resolved with the list of queues.
123     // Also emits the onQueueListUpdate event so listeners
124     // can detect queue content changes.
125     getActiveQueues(qtype: string): Promise<IdlObject[]> {
126         if (this.activeQueues[qtype]) {
127             return Promise.resolve(this.activeQueues[qtype]);
128         } else {
129             this.activeQueues[qtype] = [];
130         }
131
132         // could be a big list, invoke in streaming mode
133         return this.net.request(
134             'open-ils.vandelay',
135             `open-ils.vandelay.${qtype}_queue.owner.retrieve`,
136             this.auth.token(), null, {complete: 'f'}
137         ).pipe(tap(
138             queue => this.activeQueues[qtype].push(queue)
139         )).toPromise().then(() => this.activeQueues[qtype]);
140     }
141
142     getBibSources(): Promise<IdlObject[]> {
143         if (this.bibSources) {
144             return Promise.resolve(this.bibSources);
145         }
146
147         return this.pcrud.retrieveAll('cbs', 
148           {order_by: {cbs: 'id'}}, 
149           {atomic: true}
150         ).toPromise().then(sources => {
151             this.bibSources = sources;
152             return sources;
153         });
154     }
155
156     getItemImportDefs(): Promise<IdlObject[]> {
157         if (this.importItemAttrDefs) {
158             return Promise.resolve(this.importItemAttrDefs);
159         }
160
161         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
162         return this.pcrud.search('viiad', {owner: owners}, {}, {atomic: true})
163         .toPromise().then(defs => {
164             this.importItemAttrDefs = defs;
165             return defs;
166         });
167     }
168
169     // todo: differentiate between biblio and authority a la queue api
170     getMatchSets(mtype: string): Promise<IdlObject[]> {
171     
172         const mstype = mtype.match(/bib/) ? 'biblio' : 'authority';
173
174         if (this.matchSets[mtype]) {
175             return Promise.resolve(this.matchSets[mtype]);
176         } else {
177             this.matchSets[mtype] = [];
178         }
179
180         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
181
182         return this.pcrud.search('vms', 
183             {owner: owners, mtype: mstype}, {}, {atomic: true})
184         .toPromise().then(sets => {
185             this.matchSets[mtype] = sets;
186             return sets;
187         });
188     }
189
190     getBibBuckets(): Promise<IdlObject[]> {
191         if (this.bibBuckets) {
192             return Promise.resolve(this.bibBuckets);
193         }
194
195         const bkts = [];
196         return this.net.request(
197             'open-ils.actor', 
198             'open-ils.actor.container.retrieve_by_class',
199             this.auth.token(), this.auth.user().id(), 'biblio', 'staff_client'
200         //).pipe(tap(bkt => bkts.push(bkt))).toPromise().then(() => bkts);
201         ).toPromise().then(bkts => {
202             this.bibBuckets = bkts;
203             return bkts;
204         });
205     }
206
207     getCopyStatuses(): Promise<any> {
208         if (this.copyStatuses) {
209             return Promise.resolve(this.copyStatuses);
210         }
211         return this.pcrud.retrieveAll('ccs', {}, {atomic: true})
212         .toPromise().then(stats => {
213             this.copyStatuses = stats;
214             return stats;
215         });
216     }
217
218     getBibTrashGroups(): Promise<any> {
219         if (this.bibTrashGroups) {
220             return Promise.resolve(this.bibTrashGroups);
221         }
222
223         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
224
225         return this.pcrud.search('vibtg', 
226             {always_apply : 'f', owner: owners}, 
227             {vibtg : ['label']},
228             {atomic: true}
229         ).toPromise().then(groups => {
230             this.bibTrashGroups = groups;
231             return groups;
232         });
233     }
234
235
236     // Create a queue and return the ID of the new queue via promise.
237     createQueue(
238         queueName: string, 
239         recordType: string, 
240         importDefId: number, 
241         matchSet: number, 
242         matchBucket: number): Promise<number> {
243
244         const method = `open-ils.vandelay.${recordType}_queue.create`;
245
246         let qType = recordType;
247         if (recordType.match(/bib_acq/)) {
248             let qType = 'acq';
249         }
250
251         return new Promise((resolve, reject) => {
252             this.net.request(
253                 'open-ils.vandelay', method, 
254                 this.auth.token(), queueName, null, qType, 
255                 matchSet, importDefId, matchBucket
256             ).subscribe(queue => {
257                 const e = this.evt.parse(queue);
258                 if (e) { 
259                     alert(e);
260                     reject(e);
261                 } else {
262                     resolve(queue.id());
263                 }
264             });
265         });
266     }
267
268     getQueuedRecords(queueId: number, queueType: string, 
269       options?: any, limitToMatches?: boolean): Observable<any> {
270
271         const qtype = queueType.match(/bib/) ? 'bib' : 'auth';
272
273         let method = 
274           `open-ils.vandelay.${qtype}_queue.records.retrieve`;
275
276         if (limitToMatches) {
277             method = 
278               `open-ils.vandelay.${qtype}_queue.records.matches.retrieve`;
279         }
280
281         return this.net.request('open-ils.vandelay', 
282             method, this.auth.token(), queueId, options);
283     }
284
285     // Download a queue as a MARC file.
286     exportQueue(queue: IdlObject, nonImported?: boolean) {
287
288         const etype = queue.queue_type().match(/auth/) ? 'auth' : 'bib';
289
290         let url = 
291           `${VANDELAY_EXPORT_PATH}?type=${etype}&queueid=${queue.id()}`
292
293         let saveName = queue.name();
294            
295         if (nonImported) {
296             url += '&nonimported=1';
297             saveName += '_nonimported';
298         }
299
300         saveName += '.mrc';
301
302         this.http.get(url, {responseType: 'text'}).subscribe(
303             data => {
304                 saveAs(
305                     new Blob([data], {type: 'application/octet-stream'}),
306                     saveName
307                 );
308             },
309             err  => {
310                 console.error(err);
311             }
312         );
313     }
314
315     // Poll every 2 seconds for session tracker updates so long 
316     // as the session tracker is active.
317     // Returns an Observable of tracker objects.
318     pollSessionTracker(id: number): Observable<IdlObject> {
319         return new Observable(observer => {
320             this.getNextSessionTracker(id, observer);
321         });
322     }
323
324     getNextSessionTracker(id: number, observer: any) {
325
326                 // No need for this to be an authoritative call.
327         // It will complete eventually regardless.
328         this.pcrud.retrieve('vst', id).subscribe(
329             tracker => {
330                 if (tracker && tracker.state() === 'active') {
331                     observer.next(tracker);
332                     setTimeout(() => 
333                         this.getNextSessionTracker(id, observer), 2000);
334                 } else {
335                     console.debug(
336                         `Vandelay session tracker ${id} is ${tracker.state()}`);
337                     observer.complete();
338                 }
339             }
340         );
341     }
342 }
343