From 2c872e9324b9b4af87b9b5cdd5eab4f47c6d8fcd Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Fri, 2 Jul 2021 16:57:40 -0400 Subject: [PATCH] LP1908722 Staff catalog Show More Details Adds a "Show More Details" (and "Show Fewer Details") buttons to the Angualr staff catalog. Similar to the TPAC, activating the button means more holdings details are displayed in the search results page. Adds a new workstation setting type called 'eg.staff.catalog.results.show_more' Signed-off-by: Bill Erickson Signed-off-by: Galen Charlton Signed-off-by: Michele Morgan --- .../src/app/share/catalog/bib-record.service.ts | 15 ++-- .../eg2/src/app/share/catalog/catalog.service.ts | 17 +++-- .../eg2/src/app/share/catalog/search-context.ts | 4 + .../eg2/src/app/staff/catalog/catalog.service.ts | 1 - .../eg2/src/app/staff/catalog/resolver.service.ts | 1 + .../app/staff/catalog/result/record.component.html | 27 +++++++ .../staff/catalog/result/results.component.html | 8 +- .../app/staff/catalog/result/results.component.ts | 39 +++++++++- .../lib/OpenILS/Application/Search/Biblio.pm | 87 +++++++++++++++++++++- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 11 +++ .../XXXX.data.angstcat-show-more-details.sql | 15 ++++ 11 files changed, 206 insertions(+), 19 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.angstcat-show-more-details.sql 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 index ce352c7868..999ce372ee 100644 --- 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 @@ -38,6 +38,7 @@ export class BibRecordSummary { net: NetService; displayHighlights: {[name: string]: string | string[]} = {}; eResourceUrls: EResourceUrl[] = []; + copies: any[]; constructor(record: IdlObject, orgId: number, orgDepth?: number) { this.id = Number(record.id()); @@ -95,8 +96,8 @@ export class BibRecordService { return this.getBibSummaries([id], orgId, isStaff); } - getBibSummaries(bibIds: number[], - orgId?: number, isStaff?: boolean): Observable { + getBibSummaries(bibIds: number[], orgId?: number, + isStaff?: boolean, options?: any): Observable { if (bibIds.length === 0) { return from([]); } if (!orgId) { orgId = this.org.root().id(); } @@ -104,7 +105,7 @@ export class BibRecordService { let method = 'open-ils.search.biblio.record.catalog_summary'; if (isStaff) { method += '.staff'; } - return this.net.request('open-ils.search', method, orgId, bibIds) + 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 @@ -113,12 +114,14 @@ export class BibRecordService { summary.holdCount = bibSummary.hold_count; summary.holdingsSummary = bibSummary.copy_counts; summary.eResourceUrls = bibSummary.urls; + summary.copies = bibSummary.copies; + return summary; })); } getMetabibSummaries(metabibIds: number[], - orgId?: number, isStaff?: boolean): Observable { + orgId?: number, isStaff?: boolean, options?: any): Observable { if (metabibIds.length === 0) { return from([]); } if (!orgId) { orgId = this.org.root().id(); } @@ -126,7 +129,7 @@ export class BibRecordService { let method = 'open-ils.search.biblio.metabib.catalog_summary'; if (isStaff) { method += '.staff'; } - return this.net.request('open-ils.search', method, orgId, metabibIds) + 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 @@ -136,6 +139,8 @@ export class BibRecordService { summary.attributes = metabibSummary.attributes; summary.holdCount = metabibSummary.hold_count; summary.holdingsSummary = metabibSummary.copy_counts; + summary.copies = metabibSummary.copies; + return summary; })); } 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 136e27a71b..d2e3044136 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 @@ -205,20 +205,27 @@ export class CatalogService { // 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(); + const org = ctx.global ? ctx.org.root() : ctx.searchOrg; + const depth = org.ou_type().depth(); const isMeta = ctx.termSearch.isMetarecordSearch(); let observable: Observable; + const options: any = {}; + if (ctx.showResultExtras) { + options.flesh_copies = true; + options.copy_depth = depth; + options.copy_limit = 5; + options.pref_ou = ctx.prefOu; + } + if (isMeta) { observable = this.bibService.getMetabibSummaries( - ctx.currentResultIds(), ctx.searchOrg.id(), ctx.isStaff); + ctx.currentResultIds(), ctx.searchOrg.id(), ctx.isStaff, options); } else { observable = this.bibService.getBibSummaries( - ctx.currentResultIds(), ctx.searchOrg.id(), ctx.isStaff); + ctx.currentResultIds(), ctx.searchOrg.id(), ctx.isStaff, options); } return observable.pipe(map(summary => { diff --git a/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts b/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts index 4552d5e215..7ee7ab1271 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts @@ -341,6 +341,7 @@ export class CatalogSearchContext { showBasket: boolean; searchOrg: IdlObject; global: boolean; + prefOu: number; termSearch: CatalogTermContext; marcSearch: CatalogMarcContext; @@ -352,6 +353,9 @@ export class CatalogSearchContext { result: CatalogSearchResults; searchState: CatalogSearchState = CatalogSearchState.PENDING; + // fetch and show extra holdings data, etc. + showResultExtras = false; + // List of IDs in page/offset context. resultIds: number[]; diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts index 081135c37b..e763b35ad2 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts @@ -25,7 +25,6 @@ export class StaffCatalogService { // Display the Exclude Electronic checkbox showExcludeElectronic = false; - // TODO: does unapi support pref-lib for result-page copy counts? prefOrg: IdlObject; // Default search tab diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts b/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts index 5ec91d5fd7..f7e956224d 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts @@ -57,6 +57,7 @@ export class CatalogResolver implements Resolve> { 'opac.search.enable_bookplate_search', 'eg.staffcat.exclude_electronic', 'eg.catalog.search.form.open', + 'eg.staff.catalog.results.show_more', 'circ.staff_placed_holds_fallback_to_ws_ou' ]).then(settings => { this.staffCat.defaultSearchOrg = 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 3f05d42a49..a8cb874daa 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 @@ -210,6 +210,33 @@ +
+
+
+ +
+
Library
+
Shelving location
+
Call number
+
Status
+
+
+
{{copy.circ_lib_sn}}
+
{{copy.copy_location}}
+
+ {{copy.call_number_prefix_label}} + {{copy.call_number_label}} + {{copy.call_number_suffix_label}} +
+
{{copy.copy_status}}
+
+
+ + No Items To Display + +
+
+
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 4346fc044c..96953ee54a 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 @@ -43,7 +43,7 @@

Basket View

-
+
+
-
+
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 edcb381044..34706f117b 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 @@ -10,6 +10,7 @@ import {PcrudService} from '@eg/core/pcrud.service'; import {StaffCatalogService} from '../catalog.service'; import {IdlObject} from '@eg/core/idl.service'; import {BasketService} from '@eg/share/catalog/basket.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; @Component({ selector: 'eg-catalog-results', @@ -28,6 +29,7 @@ export class ResultsComponent implements OnInit, OnDestroy { searchSub: Subscription; routeSub: Subscription; basketSub: Subscription; + showMoreDetails = false; constructor( private route: ActivatedRoute, @@ -36,6 +38,7 @@ export class ResultsComponent implements OnInit, OnDestroy { private bib: BibRecordService, private catUrl: CatalogUrlService, private staffCat: StaffCatalogService, + private serverStore: ServerStoreService, private basket: BasketService ) {} @@ -95,16 +98,44 @@ export class ResultsComponent implements OnInit, OnDestroy { searchByUrl(params: ParamMap): void { this.catUrl.applyUrlParams(this.searchContext, params); + if (this.searchContext.isSearchable()) { - this.cat.search(this.searchContext) - .then(ok => { - this.cat.fetchFacets(this.searchContext); - this.cat.fetchBibSummaries(this.searchContext); + this.serverStore.getItem('eg.staff.catalog.results.show_more') + .then(showMore => { + + this.showMoreDetails = + this.searchContext.showResultExtras = showMore; + + if (this.staffCat.prefOrg) { + this.searchContext.prefOu = this.staffCat.prefOrg.id(); + } + + this.cat.search(this.searchContext) + .then(ok => { + this.cat.fetchFacets(this.searchContext); + this.cat.fetchBibSummaries(this.searchContext); + }); }); } } + toggleShowMore() { + this.showMoreDetails = !this.showMoreDetails; + + this.serverStore.setItem( + 'eg.staff.catalog.results.show_more', this.showMoreDetails) + .then(_ => { + + if (this.showMoreDetails) { + this.staffCat.search(); + } else { + // Clear the collected copies. No need for another search. + this.searchContext.result.records.forEach(rec => rec.copies = undefined); + } + }); + } + searchIsDone(): boolean { return this.searchContext.searchState === CatalogSearchState.COMPLETE; } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm index 95720a8eeb..1bdb62ffcd 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm @@ -2887,8 +2887,9 @@ sub mk_copy_query { my $copy_offset = shift; my $pref_ou = shift; my $is_staff = shift; + my $base_query = shift; - my $query = $U->basic_opac_copy_query( + my $query = $base_query || $U->basic_opac_copy_query( $rec_id, undef, undef, $copy_limit, $copy_offset, $is_staff ); @@ -2912,6 +2913,16 @@ sub mk_copy_query { } } }}; + + if ($pref_ou) { + # Make sure the pref OU is included in the results + my $in = $query->{from}->{acp}->[1]->{aou}->{filter}->{id}->{in}; + delete $query->{from}->{acp}->[1]->{aou}->{filter}->{id}; + $query->{from}->{acp}->[1]->{aou}->{filter}->{'-or'} = [ + {id => {in => $in}}, + {id => $pref_ou} + ]; + } }; # Unsure if we want these in the shared function, leaving here for now @@ -3057,8 +3068,9 @@ __PACKAGE__->register_method( sub catalog_record_summary { - my ($self, $client, $org_id, $record_ids) = @_; + my ($self, $client, $org_id, $record_ids, $options) = @_; my $e = new_editor(); + $options ||= {}; my $is_meta = ($self->api_name =~ /metabib/); my $is_staff = ($self->api_name =~ /staff/); @@ -3086,12 +3098,83 @@ sub catalog_record_summary { $response->{hold_count} = $U->simplereq('open-ils.circ', $holds_method, $rec_id); + if ($options->{flesh_copies}) { + $response->{copies} = get_representative_copies( + $e, $rec_id, $org_id, $is_staff, $is_meta, $options); + } + $client->respond($response); } return undef; } +# Returns a snapshot of copy information for a given record or metarecord, +# sorted by pref org and search org. +sub get_representative_copies { + my ($e, $rec_id, $org_id, $is_staff, $is_meta, $options) = @_; + + my @rec_ids; + my $limit = $options->{copy_limit}; + my $copy_depth = $options->{copy_depth}; + my $copy_offset = $options->{copy_offset}; + my $pref_ou = $options->{pref_ou}; + + my $org_tree = $U->get_org_tree; + if (!$org_id) { $org_id = $org_tree->id; } + my $org = $U->find_org($org_tree, $org_id); + + return [] unless $org; + + my $func = 'unapi.biblio_record_entry_feed'; + my $includes = '{holdings_xml,acp,acnp,acns}'; + my $limits = "acn=>$limit,acp=>$limit"; + + if ($is_meta) { + $func = 'unapi.metabib_virtual_record_feed'; + $includes = '{holdings_xml,acp,acnp,acns,mmr.unapi}'; + $limits .= ",bre=>$limit"; + } + + my $xml_query = $e->json_query({from => [ + $func, '{'.$rec_id.'}', 'marcxml', + $includes, $org->shortname, $copy_depth, $limits, + undef, undef,undef, undef, undef, + undef, undef, undef, $pref_ou + ]})->[0]; + + my $xml = $xml_query->{$func}; + + my $doc = XML::LibXML->new->parse_string($xml); + + my $copies = []; + for my $volume ($doc->documentElement->findnodes('//*[local-name()="volume"]')) { + my $label = $volume->getAttribute('label'); + my $prefix = $volume->getElementsByTagName('call_number_prefix')->[0]->getAttribute('label'); + my $suffix = $volume->getElementsByTagName('call_number_suffix')->[0]->getAttribute('label'); + + my $copies_node = $volume->findnodes('./*[local-name()="copies"]')->[0]; + + for my $copy ($copies_node->findnodes('./*[local-name()="copy"]')) { + + my $status = $copy->getElementsByTagName('status')->[0]->textContent; + my $location = $copy->getElementsByTagName('location')->[0]->textContent; + my $circ_lib_sn = $copy->getElementsByTagName('circ_lib')->[0]->getAttribute('shortname'); + + push(@$copies, { + call_number_label => $label, + call_number_prefix_label => $prefix, + call_number_suffix_label => $suffix, + circ_lib_sn => $circ_lib_sn, + copy_status => $status, + copy_location => $location + }); + } + } + + return $copies; +} + sub get_one_rec_urls { my ($self, $e, $org_id, $bib_id) = @_; diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index 3e2a6b5688..612fcc7d36 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -21644,3 +21644,14 @@ VALUES 'coust', 'description'), 'integer' ); + +INSERT INTO config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.staff.catalog.results.show_more', 'gui', 'bool', + oils_i18n_gettext( + 'eg.staff.catalog.results.show_more', + 'Show more details in Angular staff catalog', + 'cwst', 'label' + ) +); + diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.angstcat-show-more-details.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.angstcat-show-more-details.sql new file mode 100644 index 0000000000..31b2e4caa5 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.angstcat-show-more-details.sql @@ -0,0 +1,15 @@ +BEGIN; + +-- SELECT evergreen.upgrade_deps_block_check('TODO', :eg_version); + +INSERT INTO config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.staff.catalog.results.show_more', 'gui', 'bool', + oils_i18n_gettext( + 'eg.staff.catalog.results.show_more', + 'Show more details in Angular staff catalog', + 'cwst', 'label' + ) +); + +COMMIT; -- 2.11.0