From 76e40f90f0ddc772d1247e54bc15922c642810ab Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 26 Jun 2018 17:26:48 -0400 Subject: [PATCH] LP#1775466 Catalog uses display fields; more extensible guts Signed-off-by: Bill Erickson --- Open-ILS/examples/fm_IDL.xml | 2 +- Open-ILS/src/eg2/src/app/core/idl.service.ts | 2 +- .../src/app/share/catalog/bib-record.service.ts | 269 +++++++++++++++++++++ .../src/app/share/catalog/catalog-url.service.ts | 9 +- .../eg2/src/app/share/catalog/catalog.service.ts | 158 ++---------- .../eg2/src/app/staff/catalog/catalog.module.ts | 2 + .../app/staff/catalog/record/record.component.html | 2 +- .../app/staff/catalog/record/record.component.ts | 22 +- .../app/staff/catalog/result/record.component.html | 53 ++-- .../app/staff/catalog/result/record.component.ts | 31 ++- .../staff/catalog/result/results.component.html | 6 +- .../app/staff/catalog/result/results.component.ts | 34 +-- .../app/staff/catalog/search-form.component.html | 6 +- .../src/app/staff/catalog/search-form.component.ts | 24 +- .../share/bib-summary/bib-summary.component.html | 18 +- .../share/bib-summary/bib-summary.component.ts | 45 +--- 16 files changed, 410 insertions(+), 273 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 790a3372e1..e9b2323360 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -3238,7 +3238,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + diff --git a/Open-ILS/src/eg2/src/app/core/idl.service.ts b/Open-ILS/src/eg2/src/app/core/idl.service.ts index 9ec6c6e7f6..3f9bbe8204 100644 --- a/Open-ILS/src/eg2/src/app/core/idl.service.ts +++ b/Open-ILS/src/eg2/src/app/core/idl.service.ts @@ -17,7 +17,7 @@ export interface IdlObject { @Injectable() export class IdlService { - classes = {}; // IDL class metadata + classes: any = {}; // IDL class metadata constructors = {}; // IDL instance generators /** diff --git a/Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts new file mode 100644 index 0000000000..cf080aadc9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts @@ -0,0 +1,269 @@ +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import {mergeMap} from 'rxjs/operators/mergeMap'; +import {from} from 'rxjs/observable/from'; +import {map} from 'rxjs/operators/map'; +import {OrgService} from '@eg/core/org.service'; +import {UnapiService} from '@eg/share/catalog/unapi.service'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {PcrudService} from '@eg/core/pcrud.service'; + +export const NAMESPACE_MAPS = { + 'mods': 'http://www.loc.gov/mods/v3', + 'biblio': 'http://open-ils.org/spec/biblio/v1', + 'holdings': 'http://open-ils.org/spec/holdings/v1', + 'indexing': 'http://open-ils.org/spec/indexing/v1' +}; + +export const HOLDINGS_XPATH = + '/holdings:holdings/holdings:counts/holdings:count'; + +@Injectable() +export class BibRecordService { + + // Cache of bib editor / creator objects + // Assumption is this list will be limited in size. + userCache: {[id: number]: IdlObject}; + + constructor( + private idl: IdlService, + private net: NetService, + private org: OrgService, + private unapi: UnapiService, + private pcrud: PcrudService + ) { + 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); + } + + // Note responses are returned upon receipt, not necessarily in + // request ID order. + getBibSummaryBatch(bibIds: number[], + orgId?: number, orgDepth?: number): Observable { + + if (bibIds.length === 0) { + return from([]); + } + + return this.pcrud.search('bre', {id: bibIds}, + { 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); + summary.net = this.net; // inject + summary.ingest(); + return this.getHoldingsSummary(bib.id(), orgId, orgDepth) + .then(holdingsSummary => { + summary.holdingsSummary = holdingsSummary; + return summary; + }); + })); + } + + getBibSummary(bibId: number, + orgId?: number, orgDepth?: number): Promise { + + return this.pcrud.retrieve('bre', bibId, + { 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); + summary.net = this.net; // inject + summary.ingest(); + return this.getHoldingsSummary(bib.id(), orgId, orgDepth) + .then(holdingsSummary => { + summary.holdingsSummary = holdingsSummary; + return summary; + }); + })).toPromise(); + } + + // 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 { + + 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(); + } + + 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(); + } + + getHoldingsSummary(recordId: number, + orgId: number, orgDepth: number): Promise { + + 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); + } + + return holdingsSummary; + }); + } +} + +export class BibRecordSummary { + + id: number; // == record.id() for convenience + orgId: number; + orgDepth: number; + record: IdlObject; + display: any; + attributes: any; + holdingsSummary: any; + holdCount: number; + bibCallNumber: string; + net: NetService; + + constructor(record: IdlObject, orgId: number, orgDepth: number) { + this.id = 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 { + + 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); + } + + // Get -> Set -> Return bib-level call number + getBibCallNumber(): Promise { + + if (this.bibCallNumber !== null) { + 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 + ).toPromise().then(cnArray => { + if (cnArray && cnArray.length > 0) { + const key1 = Object.keys(cnArray[0])[0]; + this.bibCallNumber = cnArray[0][key1]; + } else { + this.bibCallNumber = ''; + } + return this.bibCallNumber; + }); + } +} + + + diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts index 0974cb19ea..253e3aacdd 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts @@ -32,6 +32,8 @@ export class CatalogUrlService { offset: null }; + params.org = context.searchOrg.id(); + params.limit = context.pager.limit; if (context.pager.offset) { params.offset = context.pager.offset; @@ -46,6 +48,11 @@ export class CatalogUrlService { } }); + if (params.identQuery) { + // Ident queries (e.g. tcn search) discards all remaining filters + return params; + } + context.query.forEach((q, idx) => { ['query', 'fieldClass', 'joinOp', 'matchOp'].forEach(field => { // Propagate all array-based fields regardless of @@ -72,8 +79,6 @@ export class CatalogUrlService { })); }); - params.org = context.searchOrg.id(); - return params; } diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts index 6c1c6b9858..a5cfe1ef81 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts @@ -1,11 +1,17 @@ import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import {mergeMap} from 'rxjs/operators/mergeMap'; +import {map} from 'rxjs/operators/map'; import {OrgService} from '@eg/core/org.service'; import {UnapiService} from '@eg/share/catalog/unapi.service'; -import {IdlObject} from '@eg/core/idl.service'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; import {NetService} from '@eg/core/net.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {CatalogSearchContext, CatalogSearchState} from './search-context'; +import {BibRecordService, BibRecordSummary} from './bib-record.service'; +// CCVM's we care about in a catalog context +// Don't fetch them all because there are a lot. export const CATALOG_CCVM_FILTERS = [ 'item_type', 'item_form', @@ -15,30 +21,10 @@ export const CATALOG_CCVM_FILTERS = [ 'vr_format', 'bib_level', 'lit_form', - 'search_format' + 'search_format', + 'icon_format' ]; -const MODS_XPATH_AUTO = { - title : '/mods:mods/mods:titleInfo/mods:title', - author: '/mods:mods/mods:name/mods:namePart', - edition: '/mods:mods/mods:originInfo/mods:edition', - pubdate: '/mods:mods/mods:originInfo/mods:dateIssued', - genre: '/mods:mods/mods:genre' -}; - -const MODS_XPATH = { - extern: '/mods:mods/biblio:extern', - copyCounts: '/mods:mods/holdings:holdings/holdings:counts/holdings:count', - attributes: '/mods:mods/indexing:attributes/indexing:field' -}; - -const NAMESPACE_MAPS = { - 'mods': 'http://www.loc.gov/mods/v3', - 'biblio': 'http://open-ils.org/spec/biblio/v1', - 'holdings': 'http://open-ils.org/spec/holdings/v1', - 'indexing': 'http://open-ils.org/spec/indexing/v1' -}; - @Injectable() export class CatalogService { @@ -52,10 +38,12 @@ export class CatalogService { lastFacetKey: string; constructor( + private idl: IdlService, private net: NetService, private org: OrgService, private unapi: UnapiService, - private pcrud: PcrudService + private pcrud: PcrudService, + private bibService: BibRecordService ) {} search(ctx: CatalogSearchContext): Promise { @@ -99,28 +87,24 @@ export class CatalogService { result.ids.forEach((blob, idx) => ctx.addResultId(blob[0], idx)); } - fetchBibSummaries(ctx: CatalogSearchContext): Promise { - const promises = []; + // Appends records to the search result set as they arrive. + // Returns a void promise once all records have been retrieved + fetchBibSummaries(ctx: CatalogSearchContext): Promise { + const depth = ctx.global ? ctx.org.root().ou_type().depth() : ctx.searchOrg.ou_type().depth(); - ctx.currentResultIds().forEach((recId, idx) => { - promises.push( - this.getBibSummary(recId, ctx.searchOrg.id(), depth) - .then( - // idx maintains result sort order - summary => { - if (ctx.result.records) { - // May be reset when quickly navigating results. - ctx.result.records[idx] = summary; - } - } - ) - ); - }); - - return Promise.all(promises); + return this.bibService.getBibSummaryBatch( + ctx.currentResultIds(), ctx.searchOrg.id(), depth) + .pipe(map(summary => { + // Responses are not necessarily returned in request-ID order. + const idx = ctx.currentResultIds().indexOf(summary.record.id()); + if (ctx.result.records) { + // May be reset when quickly navigating results. + ctx.result.records[idx] = summary; + } + })).toPromise(); } fetchFacets(ctx: CatalogSearchContext): Promise { @@ -223,94 +207,4 @@ export class CatalogService { ); }); } - - - /** - * Bib record via UNAPI as mods (for now) with holdings summary - * and record attributes. - */ - getBibSummary(bibId: number, orgId?: number, depth?: number): Promise { - return new Promise((resolve, reject) => { - this.unapi.getAsXmlDocument({ - target: 'bre', - id: bibId, - extras: '{bre.extern,holdings_xml,mra}', - format: 'mods32', - orgId: orgId, - depth: depth - }).then(xmlDoc => { - const summary = this.translateBibSummary(xmlDoc); - summary.id = bibId; - resolve(summary); - }); - }); - } - - /** - * Probably don't want to require navigating the bare UNAPI - * blob in the template, plus that's quite a lot of stuff - * to sit in the scope / watch for changes. Translate the - * UNAPI content into a more digestable form. - * TODO: Add display field support - */ - translateBibSummary(xmlDoc: XMLDocument): any { // TODO: bib summary interface - - const response: any = { - copyCounts : [], - ccvms : {} - }; - - const resolver: any = (prefix: string): string => { - return NAMESPACE_MAPS[prefix] || null; - }; - - Object.keys(MODS_XPATH_AUTO).forEach(key => { - const res = xmlDoc.evaluate(MODS_XPATH_AUTO[key], xmlDoc, - resolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null); - - const modsNode = res.singleNodeValue; - if (modsNode) { - response[key] = modsNode.textContent; - } - }); - - let result = xmlDoc.evaluate(MODS_XPATH.extern, xmlDoc, - resolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null); - - let node: any = result.singleNodeValue; - if (node) { - const attrs = node.attributes; - for (let i = attrs.length - 1; i >= 0; i--) { - response[attrs[i].name] = attrs[i].value; - } - } - - result = xmlDoc.evaluate(MODS_XPATH.attributes, xmlDoc, - resolver, XPathResult.ANY_TYPE, null); - - while (node = result.iterateNext()) { - response.ccvms[node.getAttribute('name')] = { - code : node.textContent, - label : node.getAttribute('coded-value') - }; - } - - result = xmlDoc.evaluate(MODS_XPATH.copyCounts, xmlDoc, - resolver, XPathResult.ANY_TYPE, null); - - 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)); - }); - response.copyCounts.push(counts); - } - - response.creator = Number(response.creator); - response.editor = Number(response.editor); - - // console.log(response); - return response; - } } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts index 6f65fedd77..b2aa894322 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts @@ -3,6 +3,7 @@ import {StaffCommonModule} from '@eg/staff/common.module'; import {GridModule} from '@eg/share/grid/grid.module'; import {UnapiService} from '@eg/share/catalog/unapi.service'; import {CatalogRoutingModule} from './routing.module'; +import {BibRecordService} from '@eg/share/catalog/bib-record.service'; import {CatalogService} from '@eg/share/catalog/catalog.service'; import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service'; import {CatalogComponent} from './catalog.component'; @@ -44,6 +45,7 @@ import {HoldingsService} from '@eg/staff/share/holdings.service'; ], providers: [ UnapiService, + BibRecordService, CatalogService, CatalogUrlService, StaffCatalogService, diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html index 66fe2b9eb2..f2b1ee4854 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html @@ -15,7 +15,7 @@
- +
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts index 437b440fbc..2d51b0525e 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts @@ -5,6 +5,7 @@ import {PcrudService} from '@eg/core/pcrud.service'; import {IdlObject} from '@eg/core/idl.service'; import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context'; import {CatalogService} from '@eg/share/catalog/catalog.service'; +import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service'; import {StaffCatalogService} from '../catalog.service'; import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component'; @@ -16,7 +17,7 @@ export class RecordComponent implements OnInit { recordId: number; recordTab: string; - bibSummary: any; + summary: BibRecordSummary; searchContext: CatalogSearchContext; @ViewChild('recordTabs') recordTabs: NgbTabset; @@ -24,6 +25,7 @@ export class RecordComponent implements OnInit { private router: Router, private route: ActivatedRoute, private pcrud: PcrudService, + private bib: BibRecordService, private cat: CatalogService, private staffCat: StaffCatalogService ) {} @@ -62,27 +64,19 @@ export class RecordComponent implements OnInit { // Avoid re-fetching the same record summary during tab navigation. if (this.staffCat.currentDetailRecordSummary && this.recordId === this.staffCat.currentDetailRecordSummary.id) { - this.bibSummary = this.staffCat.currentDetailRecordSummary; + this.summary = this.staffCat.currentDetailRecordSummary; return; } - this.bibSummary = null; - this.cat.getBibSummary( + this.summary = null; + this.bib.getBibSummary( this.recordId, this.searchContext.searchOrg.id(), this.searchContext.searchOrg.ou_type().depth() ).then(summary => { - this.bibSummary = + this.summary = this.staffCat.currentDetailRecordSummary = summary; - this.pcrud.search('au', {id: [summary.creator, summary.editor]}) - .subscribe(user => { - if (user.id() === summary.creator) { - summary.creator = user; - } - if (user.id() === summary.editor) { - summary.editor = user; - } - }); + this.bib.fleshBibUsers([summary.record]); }); } } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html index d7b3945c53..d26e9a0cd8 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html @@ -9,9 +9,9 @@ @@ -31,26 +31,27 @@
- - - {{bibSummary.ccvms.icon_format.label}} + + + + {{iconFormatLabel(summary.attributes.icon_format[0])}} - {{bibSummary.edition}} - {{bibSummary.pubdate}} + {{summary.display.edition}} + {{summary.display.pubdate}}
+ *ngFor="let copyCount of summary.holdingsSummary; let copyIdx = index">
@@ -66,12 +67,12 @@
- TCN: {{bibSummary.tcn_value}} + TCN: {{summary.record.tcn_value()}}
- Holds: {{bibSummary.holdCount}} + Holds: {{summary.holdCount}}
@@ -79,26 +80,26 @@
- Created {{bibSummary.create_date | date:'shortDate'}} by + Created {{summary.create_date | date:'shortDate'}} by - - {{bibSummary.creator.usrname()}} + + {{summary.record.creator().usrname()}} - ... + ...
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts index 352eeb1ebd..bfcfd4572e 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts @@ -3,6 +3,7 @@ import {Router} from '@angular/router'; import {OrgService} from '@eg/core/org.service'; import {NetService} from '@eg/core/net.service'; import {CatalogService} from '@eg/share/catalog/catalog.service'; +import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service'; import {CatalogSearchContext} from '@eg/share/catalog/search-context'; import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service'; import {StaffCatalogService} from '../catalog.service'; @@ -14,13 +15,14 @@ import {StaffCatalogService} from '../catalog.service'; export class ResultRecordComponent implements OnInit { @Input() index: number; // 0-index display row - @Input() bibSummary: any; + @Input() summary: BibRecordSummary; searchContext: CatalogSearchContext; constructor( private router: Router, private org: OrgService, private net: NetService, + private bib: BibRecordService, private cat: CatalogService, private catUrl: CatalogUrlService, private staffCat: StaffCatalogService @@ -28,32 +30,35 @@ export class ResultRecordComponent implements OnInit { ngOnInit() { this.searchContext = this.staffCat.searchContext; - this.fleshHoldCount(); - } - - fleshHoldCount(): void { - this.net.request( - 'open-ils.circ', - 'open-ils.circ.bre.holds.count', this.bibSummary.id - ).subscribe(count => this.bibSummary.holdCount = count); + this.summary.getHoldCount(); } orgName(orgId: number): string { return this.org.get(orgId).shortname(); } + iconFormatLabel(code: string): string { + if (this.cat.ccvmMap) { + const ccvm = this.cat.ccvmMap.icon_format.filter( + format => format.code() === code)[0]; + if (ccvm) { + return ccvm.search_label(); + } + } + } + placeHold(): void { - alert('Placing hold on bib ' + this.bibSummary.id); + alert('Placing hold on bib ' + this.summary.id); } addToList(): void { - alert('Adding to list for bib ' + this.bibSummary.id); + alert('Adding to list for bib ' + this.summary.id); } - searchAuthor(bibSummary: any) { + searchAuthor(summary: any) { this.searchContext.reset(); this.searchContext.fieldClass = ['author']; - this.searchContext.query = [bibSummary.author]; + this.searchContext.query = [summary.display.author]; this.staffCat.search(); } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html index f357a6c579..ee9ca8ddf2 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html @@ -17,9 +17,9 @@
-
-
- +
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts index dca1dfad17..d9b7062e14 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts @@ -3,6 +3,7 @@ import {Observable} from 'rxjs/Observable'; import {map, switchMap, distinctUntilChanged} from 'rxjs/operators'; import {ActivatedRoute, ParamMap} from '@angular/router'; import {CatalogService} from '@eg/share/catalog/catalog.service'; +import {BibRecordService} from '@eg/share/catalog/bib-record.service'; import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service'; import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context'; import {PcrudService} from '@eg/core/pcrud.service'; @@ -25,6 +26,7 @@ export class ResultsComponent implements OnInit { private route: ActivatedRoute, private pcrud: PcrudService, private cat: CatalogService, + private bib: BibRecordService, private catUrl: CatalogUrlService, private staffCat: StaffCatalogService ) {} @@ -70,37 +72,7 @@ export class ResultsComponent implements OnInit { if (!records || records.length === 0) { return; } // Flesh the creator / editor fields with the user object. - // Handle the user fleshing here (instead of record.component so - // we only need to grab one copy of each user. - const userIds: {[id: number]: boolean} = {}; - records.forEach(recSum => { - if (this.userCache[recSum.creator]) { - recSum.creator = this.userCache[recSum.creator]; - } else { - userIds[Number(recSum.creator)] = true; - } - - if (this.userCache[recSum.editor]) { - recSum.editor = this.userCache[recSum.editor]; - } else { - userIds[Number(recSum.editor)] = true; - } - }); - - if (!Object.keys(userIds).length) { return; } - - this.pcrud.search('au', {id : Object.keys(userIds)}) - .subscribe(usr => { - this.userCache[usr.id()] = usr; - records.forEach(recSum => { - if (recSum.creator === usr.id()) { - recSum.creator = usr; - } - if (recSum.editor === usr.id()) { - recSum.editor = usr; - } - }); - }); + this.bib.fleshBibUsers(records.map(r => r.record)); } searchIsDone(): boolean { diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html index ae9170c8c5..da54f4a9b9 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html @@ -48,13 +48,13 @@ TODO focus search input
@@ -207,7 +207,7 @@ TODO focus search input
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts index dbffe9c7f1..aa9877aa0f 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts @@ -92,8 +92,30 @@ export class SearchFormComponent implements OnInit, AfterViewInit { this.searchContext.matchOp.splice(index, 1); } - formEnter() { + formEnter(source) { this.searchContext.pager.offset = 0; + + switch (source) { + + case 'query': // main search form query input + + // Be sure a previous ident search does not take precedence + // over the newly entered/submitted search query + this.searchContext.identQuery = null; + break; + + case 'ident': // identifier query input + const iq = this.searchContext.identQuery; + const qt = this.searchContext.identQueryType + if (iq) { + // Ident queries ignore search-specific filters. + this.searchContext.reset(); + this.searchContext.identQuery = iq; + this.searchContext.identQueryType = qt; + } + break; + } + this.searchByForm(); } diff --git a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html index 66266086d8..ac35bdbe42 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html @@ -23,28 +23,28 @@
  • Title:
    -
    {{summary.title}}
    +
    {{summary.display.title}}
    Edition:
    -
    {{summary.edition}}
    +
    {{summary.display.edition}}
    TCN:
    -
    {{summary.tcn_value}}
    +
    {{summary.record.tcn_value()}}
    Created By:
    -
    - {{summary.creator.usrname()}} +
    + {{summary.record.creator().usrname()}}
  • Author:
    -
    {{summary.author}}
    +
    {{summary.display.author}}
    Pubdate:
    -
    {{summary.pubdate}}
    +
    {{summary.display.pubdate}}
    Database ID:
    {{summary.id}}
    Last Edited By:
    -
    - {{summary.editor.usrname()}} +
    + {{summary.record.editor().usrname()}}
  • diff --git a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts index 75a79936b2..aa7247df6e 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts @@ -2,6 +2,7 @@ import {Component, OnInit, Input} from '@angular/core'; import {NetService} from '@eg/core/net.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {CatalogService} from '@eg/share/catalog/catalog.service'; +import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service'; @Component({ selector: 'eg-bib-summary', @@ -17,15 +18,16 @@ export class BibSummaryComponent implements OnInit { @Input() recordId: number; // Otherwise, we'll use the provided bib summary object. - summary: any; + summary: BibRecordSummary; @Input() set bibSummary(s: any) { this.summary = s; if (this.initDone) { - this.fetchBibCallNumber(); + this.summary.getBibCallNumber(); } } constructor( + private bib: BibRecordService, private cat: CatalogService, private net: NetService, private pcrud: PcrudService @@ -34,7 +36,7 @@ export class BibSummaryComponent implements OnInit { ngOnInit() { this.initDone = true; if (this.summary) { - this.fetchBibCallNumber(); + this.summary.getBibCallNumber(); } else { if (this.recordId) { this.loadSummary(); @@ -43,40 +45,11 @@ export class BibSummaryComponent implements OnInit { } loadSummary(): void { - this.cat.getBibSummary(this.recordId).then(summary => { + this.bib.getBibSummary(this.recordId).then(summary => { + summary.getBibCallNumber(); + this.bib.fleshBibUsers([summary.record]); this.summary = summary; - this.fetchBibCallNumber(); - - // Flesh the user data - this.pcrud.search('au', {id: [summary.creator, summary.editor]}) - .subscribe(user => { - if (user.id() === summary.creator) { - summary.creator = user; - } - if (user.id() === summary.editor) { - summary.editor = user; - } - }); - }); - } - - fetchBibCallNumber(): void { - if (!this.summary || this.summary.callNumber) { - return; - } - - // TODO labelClass = cat.default_classification_scheme YAOUS - const labelClass = 1; - - this.net.request( - 'open-ils.cat', - 'open-ils.cat.biblio.record.marc_cn.retrieve', - this.summary.id, labelClass - ).subscribe(cnArray => { - if (cnArray && cnArray.length > 0) { - const key1 = Object.keys(cnArray[0])[0]; - this.summary.callNumber = cnArray[0][key1]; - } + console.log(this.summary.display); }); } } -- 2.11.0