LP1913807 Staff catalog shows preferred lib holdings counts
[evergreen-equinox.git] / Open-ILS / src / eg2 / src / app / share / catalog / bib-record.service.ts
index e9fbb61..c64357d 100644 (file)
@@ -1,6 +1,6 @@
 import {Injectable} from '@angular/core';
 import {Observable, from} from 'rxjs';
-import {mergeMap, map} from 'rxjs/operators';
+import {mergeMap, map, tap} from 'rxjs/operators';
 import {OrgService} from '@eg/core/org.service';
 import {UnapiService} from '@eg/share/catalog/unapi.service';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
@@ -17,74 +17,49 @@ export const NAMESPACE_MAPS = {
 export const HOLDINGS_XPATH =
     '/holdings:holdings/holdings:counts/holdings:count';
 
+interface EResourceUrl {
+    href: string;
+    note: string;
+    label: string;
+}
+
+export interface HoldingsSummary {
+    org_unit: number;
+    depth: number;
+    unshadow: number;
+    count: number;
+    available: number;
+    transcendant: number;
+}
 
 export class BibRecordSummary {
     id: number; // == record.id() for convenience
+    metabibId: number; // If present, this is a metabib summary
+    metabibRecords: number[]; // all constituent bib records
     orgId: number;
     orgDepth: number;
     record: IdlObject;
     display: any;
     attributes: any;
-    holdingsSummary: any;
+    holdingsSummary: HoldingsSummary[];
+    prefOuHoldingsSummary: HoldingsSummary[];
     holdCount: number;
     bibCallNumber: string;
+    firstCallNumber: string;
     net: NetService;
+    displayHighlights: {[name: string]: string | string[]} = {};
+    eResourceUrls: EResourceUrl[] = [];
+    copies: any[];
 
-    constructor(record: IdlObject, orgId: number, orgDepth: number) {
-        this.id = record.id();
+    constructor(record: IdlObject, orgId: number, orgDepth?: number) {
+        this.id = Number(record.id());
         this.record = record;
         this.orgId = orgId;
         this.orgDepth = orgDepth;
         this.display = {};
         this.attributes = {};
         this.bibCallNumber = null;
-    }
-
-    ingest() {
-        this.compileDisplayFields();
-        this.compileRecordAttrs();
-
-        // Normalize some data for JS consistency
-        this.record.creator(Number(this.record.creator()));
-        this.record.editor(Number(this.record.editor()));
-    }
-
-    compileDisplayFields() {
-        this.record.flat_display_entries().forEach(entry => {
-            if (entry.multi() === 't') {
-                if (this.display[entry.name()]) {
-                    this.display[entry.name()].push(entry.value());
-                } else {
-                    this.display[entry.name()] = [entry.value()];
-                }
-            } else {
-                this.display[entry.name()] = entry.value();
-            }
-        });
-    }
-
-    compileRecordAttrs() {
-        // Any attr can be multi-valued.
-        this.record.mattrs().forEach(attr => {
-            if (this.attributes[attr.attr()]) {
-                this.attributes[attr.attr()].push(attr.value());
-            } else {
-                this.attributes[attr.attr()] = [attr.value()];
-            }
-        });
-    }
-
-    // Get -> Set -> Return bib hold count
-    getHoldCount(): Promise<number> {
-
-        if (Number.isInteger(this.holdCount)) {
-            return Promise.resolve(this.holdCount);
-        }
-
-        return this.net.request(
-            'open-ils.circ',
-            'open-ils.circ.bre.holds.count', this.id
-        ).toPromise().then(count => this.holdCount = count);
+        this.metabibRecords = [];
     }
 
     // Get -> Set -> Return bib-level call number
@@ -94,13 +69,10 @@ export class BibRecordSummary {
             return Promise.resolve(this.bibCallNumber);
         }
 
-        // TODO labelClass = cat.default_classification_scheme YAOUS
-        const labelClass = 1;
-
         return this.net.request(
             'open-ils.cat',
             'open-ils.cat.biblio.record.marc_cn.retrieve',
-            this.id, labelClass
+            this.id, null, this.orgId
         ).toPromise().then(cnArray => {
             if (cnArray && cnArray.length > 0) {
                 const key1 = Object.keys(cnArray[0])[0];
@@ -130,117 +102,62 @@ export class BibRecordService {
         this.userCache = {};
     }
 
-    // Avoid fetching the MARC blob by specifying which fields on the
-    // bre to select.  Note that fleshed fields are explicitly selected.
-    fetchableBreFields(): string[] {
-        return this.idl.classes.bre.fields
-            .filter(f => !f.virtual && f.name !== 'marc')
-            .map(f => f.name);
+    getBibSummary(id: number,
+        orgId?: number, isStaff?: boolean): Observable<BibRecordSummary> {
+        return this.getBibSummaries([id], orgId, isStaff);
     }
 
-    // Note when multiple IDs are provided, responses are emitted in order
-    // of receipt, not necessarily in the requested ID order.
-    getBibSummary(bibIds: number | number[],
-        orgId?: number, orgDepth?: number): Observable<BibRecordSummary> {
+    getBibSummaries(bibIds: number[], orgId?: number,
+        isStaff?: boolean, options?: any): Observable<BibRecordSummary> {
 
-        const ids = [].concat(bibIds);
+        if (bibIds.length === 0) { return from([]); }
+        if (!orgId) { orgId = this.org.root().id(); }
 
-        if (ids.length === 0) {
-            return from([]);
-        }
+        let method = 'open-ils.search.biblio.record.catalog_summary';
+        if (isStaff) { method += '.staff'; }
 
-        return this.pcrud.search('bre', {id: ids},
-            {   flesh: 1,
-                flesh_fields: {bre: ['flat_display_entries', 'mattrs']},
-                select: {bre : this.fetchableBreFields()}
-            },
-            {anonymous: true} // skip unneccesary auth
-        ).pipe(mergeMap(bib => {
-            const summary = new BibRecordSummary(bib, orgId, orgDepth);
+        return this.net.request('open-ils.search', method, orgId, bibIds, options)
+        .pipe(map(bibSummary => {
+            const summary = new BibRecordSummary(bibSummary.record, orgId);
             summary.net = this.net; // inject
-            summary.ingest();
-            return this.getHoldingsSummary(bib.id(), orgId, orgDepth)
-            .then(holdingsSummary => {
-                summary.holdingsSummary = holdingsSummary;
-                return summary;
-            });
+            summary.display = bibSummary.display;
+            summary.attributes = bibSummary.attributes;
+            summary.holdCount = bibSummary.hold_count;
+            summary.holdingsSummary = bibSummary.copy_counts;
+            summary.eResourceUrls = bibSummary.urls;
+            summary.copies = bibSummary.copies;
+            summary.firstCallNumber = bibSummary.first_call_number;
+            summary.prefOuHoldingsSummary = bibSummary.pref_ou_copy_counts;
+
+            return summary;
         }));
     }
 
-    // Flesh the creator and editor fields.
-    // Handling this separately lets us pull from the cache and
-    // avoids the requirement that the main bib query use a staff
-    // (VIEW_USER) auth token.
-    fleshBibUsers(records: IdlObject[]): Promise<void> {
-
-        const search = [];
-
-        records.forEach(rec => {
-            ['creator', 'editor'].forEach(field => {
-                const id = rec[field]();
-                if (Number.isInteger(id)) {
-                    if (this.userCache[id]) {
-                        rec[field](this.userCache[id]);
-                    } else if (!search.includes(id)) {
-                        search.push(id);
-                    }
-                }
-            });
-        });
-
-        if (search.length === 0) {
-            return Promise.resolve();
-        }
+    getMetabibSummaries(metabibIds: number[],
+        orgId?: number, isStaff?: boolean, options?: any): Observable<BibRecordSummary> {
 
-        return this.pcrud.search('au', {id: search})
-        .pipe(map(user => {
-            this.userCache[user.id()] = user;
-            records.forEach(rec => {
-                if (user.id() === rec.creator()) {
-                    rec.creator(user);
-                }
-                if (user.id() === rec.editor()) {
-                    rec.editor(user);
-                }
-            });
-        })).toPromise();
-    }
+        if (metabibIds.length === 0) { return from([]); }
+        if (!orgId) { orgId = this.org.root().id(); }
 
-    getHoldingsSummary(recordId: number,
-        orgId: number, orgDepth: number): Promise<any> {
-
-        const holdingsSummary = [];
-
-        return this.unapi.getAsXmlDocument({
-            target: 'bre',
-            id: recordId,
-            extras: '{holdings_xml}',
-            format: 'holdings_xml',
-            orgId: orgId,
-            depth: orgDepth
-        }).then(xmlDoc => {
-
-            // namespace resolver
-            const resolver: any = (prefix: string): string => {
-                return NAMESPACE_MAPS[prefix] || null;
-            };
-
-            // Extract the holdings data from the unapi xml doc
-            const result = xmlDoc.evaluate(HOLDINGS_XPATH,
-                xmlDoc, resolver, XPathResult.ANY_TYPE, null);
-
-            let node;
-            while (node = result.iterateNext()) {
-                const counts = {type : node.getAttribute('type')};
-                ['depth', 'org_unit', 'transcendant',
-                    'available', 'count', 'unshadow'].forEach(field => {
-                    counts[field] = Number(node.getAttribute(field));
-                });
-                holdingsSummary.push(counts);
-            }
+        let method = 'open-ils.search.biblio.metabib.catalog_summary';
+        if (isStaff) { method += '.staff'; }
 
-            return holdingsSummary;
-        });
+        return this.net.request('open-ils.search', method, orgId, metabibIds, options)
+        .pipe(map(metabibSummary => {
+            const summary = new BibRecordSummary(metabibSummary.record, orgId);
+            summary.net = this.net; // inject
+            summary.metabibId = Number(metabibSummary.metabib_id);
+            summary.metabibRecords = metabibSummary.metabib_records;
+            summary.display = metabibSummary.display;
+            summary.attributes = metabibSummary.attributes;
+            summary.holdCount = metabibSummary.hold_count;
+            summary.holdingsSummary = metabibSummary.copy_counts;
+            summary.copies = metabibSummary.copies;
+            summary.firstCallNumber = metabibSummary.first_call_number;
+            summary.prefOuHoldingsSummary = metabibSummary.pref_ou_copy_counts;
+
+            return summary;
+        }));
     }
 }