<link field="record" reltype="has_a" key="id" map="" class="bre"/>
</links>
</class>
- <class id="mmr" controller="open-ils.cstore" oils_obj:fieldmapper="metabib::metarecord" oils_persist:tablename="metabib.metarecord" reporter:label="Metarecord">
+ <class id="mmr" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="metabib::metarecord" oils_persist:tablename="metabib.metarecord" reporter:label="Metarecord">
<fields oils_persist:primary="id" oils_persist:sequence="metabib.metarecord_id_seq">
<field name="fingerprint" reporter:datatype="text"/>
<field name="id" reporter:datatype="id" />
<field name="master_record" reporter:datatype="link"/>
<field name="mods" reporter:datatype="text"/>
<field name="source_records" oils_persist:virtual="true" reporter:datatype="link"/>
+ <field name="source_maps" oils_persist:virtual="true" reporter:datatype="link"/>
</fields>
<links>
<link field="master_record" reltype="has_a" key="id" map="" class="bre"/>
<link field="source_records" reltype="has_many" key="metarecord" map="source" class="mmrsm"/>
+ <link field="source_maps" reltype="has_many" key="metarecord" class="mmrsm"/>
</links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <retrieve/>
+ </actions>
+ </permacrud>
</class>
<class id="cnal" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::net_access_level" oils_persist:tablename="config.net_access_level" reporter:label="Net Access Level">
<fields oils_persist:primary="id" oils_persist:sequence="config.net_access_level_id_seq">
</actions>
</permacrud>
</class>
- <class id="mmrsm" controller="open-ils.cstore" oils_obj:fieldmapper="metabib::metarecord_source_map" oils_persist:tablename="metabib.metarecord_source_map" oils_persist:field_safe="true" reporter:label="Metarecord Source Map">
+ <class id="mmrsm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="metabib::metarecord_source_map" oils_persist:tablename="metabib.metarecord_source_map" oils_persist:field_safe="true" reporter:label="Metarecord Source Map">
<fields oils_persist:primary="id" oils_persist:sequence="metabib.metarecord_source_map_id_seq">
<field name="id" reporter:datatype="id" />
<field name="metarecord" reporter:datatype="link"/>
<link field="source" reltype="has_a" key="id" map="" class="bre"/>
<link field="metarecord" reltype="has_a" key="id" map="" class="mmr"/>
</links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <retrieve/>
+ </actions>
+ </permacrud>
</class>
<class id="mde" controller="open-ils.cstore open-ils.pcrud"
oils_obj:fieldmapper="metabib::display_entry"
export class BibRecordSummary {
id: number; // == record.id() for convenience
+ metabibId: number; // If present, this is a metabib summary
orgId: number;
orgDepth: number;
record: IdlObject;
// Any attr can be multi-valued.
this.record.mattrs().forEach(attr => {
if (this.attributes[attr.attr()]) {
- this.attributes[attr.attr()].push(attr.value());
+ // Avoid dupes
+ if (this.attributes[attr.attr()].indexOf(attr.value()) < 0) {
+ this.attributes[attr.attr()].push(attr.value());
+ }
} else {
this.attributes[attr.attr()] = [attr.value()];
}
return Promise.resolve(this.holdCount);
}
+ let method = 'open-ils.circ.bre.holds.count';
+ let target = this.id;
+
+ if (this.metabibId) {
+ method = 'open-ils.circ.mmr.holds.count';
+ target = this.metabibId;
+ }
+
return this.net.request(
- 'open-ils.circ',
- 'open-ils.circ.bre.holds.count', this.id
+ 'open-ils.circ', method, target
).toPromise().then(count => this.holdCount = count);
}
}
// Avoid fetching the MARC blob by specifying which fields on the
- // bre to select. Note that fleshed fields are explicitly selected.
+ // bre to select. Note that fleshed fields are implicitly selected.
fetchableBreFields(): string[] {
return this.idl.classes.bre.fields
.filter(f => !f.virtual && f.name !== 'marc')
}));
}
+ // A Metabib Summary is a BibRecordSummary with the lead record as
+ // its core bib record plus attributes (e.g. formats) from related
+ // records.
+ getMetabibSummary(metabibIds: number | number[],
+ orgId?: number, orgDepth?: number): Observable<BibRecordSummary> {
+
+ const ids = [].concat(metabibIds);
+
+ if (ids.length === 0) {
+ return from([]);
+ }
+
+ return this.pcrud.search('mmr', {id: ids},
+ {flesh: 1, flesh_fields: {mmr: ['source_maps']}},
+ {anonymous: true}
+ ).pipe(mergeMap(mmr => this.compileMetabib(mmr, orgId, orgDepth)));
+ }
+
+ // 'metabib' must have its "source_maps" field fleshed.
+ // Get bib summaries for all related bib records so we can
+ // extract data that must be appended to the master record summary.
+ compileMetabib(metabib: IdlObject,
+ orgId?: number, orgDepth?: number): Observable<BibRecordSummary> {
+
+ const bibIds = metabib.source_maps().map(map => map.source());
+ let observer;
+ const observable =
+ new Observable<BibRecordSummary>(o => observer = o);
+
+ let master;
+ const summaries = [];
+
+ // NOTE if we only need to augment with 'mattrs' it would be
+ // faster to just grab those than to fetch the full bib-summary
+ // for related records. Revisit.
+
+ this.getBibSummary(bibIds, orgId, orgDepth).subscribe(
+ sum => {
+ summaries.push(sum);
+ if (sum.id === Number(metabib.master_record())) {
+ master = sum;
+ master.metabibId = metabib.id();
+ }
+ },
+ err => {},
+ () => {
+ summaries.forEach(sum => {
+ if (sum.id !== master.id) {
+ master.record.mattrs(
+ master.record.mattrs().concat(sum.record.mattrs()))
+ }
+ });
+
+ // Recompile attributes now that the data has been augmented
+ master.compileRecordAttrs();
+
+ // Fetch holdings data for the metarecord
+ this.getHoldingsSummary(metabib.id(), orgId, orgDepth, true)
+ .then(holdingsSummary => {
+ master.holdingsSummary = holdingsSummary;
+ observer.next(master);
+ observer.complete();
+ });
+ }
+ );
+
+ return observable;
+ }
+
// 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
}
getHoldingsSummary(recordId: number,
- orgId: number, orgDepth: number): Promise<any> {
+ orgId: number, orgDepth: number, isMetarecord?: boolean): Promise<any> {
const holdingsSummary = [];
return this.unapi.getAsXmlDocument({
- target: 'bre',
+ target: isMetarecord ? 'mmr' : 'bre',
id: recordId,
extras: '{holdings_xml}',
format: 'holdings_xml',
params.joinOp = [];
params.matchOp = [];
- ['format', 'available', 'hasBrowseEntry', 'date1', 'date2', 'dateOp']
+ ['format', 'available', 'hasBrowseEntry',
+ 'date1', 'date2', 'dateOp', 'isMetarecord']
.forEach(field => {
if (ts[field]) {
params[field] = ts[field];
} else if (params.has('query')) {
// Scalars
- ['format', 'available', 'date1', 'date2', 'dateOp']
+ ['format', 'available', 'date1', 'date2', 'dateOp', 'isMetarecord']
.forEach(field => {
if (params.has(field)) {
ts[field] = params.get(field);
let fullQuery;
if (ctx.identSearch.isSearchable()) {
- console.log('IDENT IS SEARCHABLE');
fullQuery = ctx.compileIdentSearchQuery();
} else {
fullQuery = ctx.compileTermSearchQuery();
console.debug(`search query: ${fullQuery}`);
let method = 'open-ils.search.biblio.multiclass.query';
+ if (ctx.termSearch.isSearchable() && ctx.termSearch.isMetarecord) {
+ method = 'open-ils.search.metabib.multiclass.query';
+ }
+
if (ctx.isStaff) {
method += '.staff';
}
ctx.org.root().ou_type().depth() :
ctx.searchOrg.ou_type().depth();
- return this.bibService.getBibSummary(
- ctx.currentResultIds(), ctx.searchOrg.id(), depth)
- .pipe(map(summary => {
+ const isMeta = (
+ ctx.termSearch.isSearchable() &&
+ ctx.termSearch.isMetarecord
+ );
+
+ let observable: Observable<BibRecordSummary>;
+
+ if (isMeta) {
+ observable = this.bibService.getMetabibSummary(
+ ctx.currentResultIds(), ctx.searchOrg.id(), depth);
+ } else {
+ observable = this.bibService.getBibSummary(
+ ctx.currentResultIds(), ctx.searchOrg.id(), depth);
+ }
+
+ return observable.pipe(map(summary => {
// Responses are not necessarily returned in request-ID order.
- const idx = ctx.currentResultIds().indexOf(summary.record.id());
+ let idx;
+ if (isMeta) {
+ idx = ctx.currentResultIds().indexOf(summary.metabibId);
+ } else {
+ idx = ctx.currentResultIds().indexOf(summary.id);
+ }
+
if (ctx.result.records) {
// May be reset when quickly navigating results.
ctx.result.records[idx] = summary;
ccvmFilters: {[ccvmCode: string]: string[]};
facetFilters: FacetFilter[];
copyLocations: string[]; // ID's, but treated as strings in the UI.
- isMetarecord: boolean; // TODO
+ isMetarecord: boolean;
hasBrowseEntry: string; // "entryId,fieldId"
date1: number;
date2: number;
</div>
<div class="row pt-2">
<div class="col-lg-12">
- <!-- only shows the first icon format -->
- <span *ngIf="summary.attributes.icon_format && summary.attributes.icon_format[0]">
- <img class="pr-1"
- src="/images/format_icons/icon_format/{{summary.attributes.icon_format[0]}}.png"/>
- <span>{{iconFormatLabel(summary.attributes.icon_format[0])}}</span>
- </span>
+ <ng-container *ngIf="summary.attributes.icon_format && summary.attributes.icon_format[0]">
+ <ng-container *ngFor="let icon of summary.attributes.icon_format">
+ <span class="pr-1">
+ <img class="pr-1"
+ src="/images/format_icons/icon_format/{{icon}}.png"/>
+ <span>{{iconFormatLabel(icon)}}</span>
+ </span>
+ </ng-container>
+ </ng-container>
<span class='pl-1'>{{summary.display.edition}}</span>
<span class='pl-1'>{{summary.display.pubdate}}</span>
</div>
</div>
<div class="checkbox pl-3">
<label>
- <input type="checkbox" [disabled]="true"
+ <input type="checkbox"
[(ngModel)]="context.termSearch.isMetarecord"/>
<span class="pl-1" i18n>Group Formats/Editions</span>
</label>