From 54d664261329fc0aa836d213dadc238d4448038f Mon Sep 17 00:00:00 2001 From: Jaswinder Singh Date: Thu, 14 Jun 2018 16:30:40 -0400 Subject: [PATCH] LP#1772680: enhancements to RBDigital integration This feature allows searching of RBdigital resources from the Evergreen catalog without needing to import and maintain bibliographic records for the RBdigital resources. The RBdigital search results will appear in the OPAC on a different tab - a search in basic or advanced Evergreen search will carry over to the RBdigital tab (along with filters when mapping is feasible) so that the user can type in a search once and see both sets of results. The design of the RBdigital tab will make use of the OPAC styling for consistency. This feature is sponsored by Recorded Books (RBDigital) and GPLS and was primary written by Jaswinder Singh with code contributions and testing from the Equinox Open Library Initiative. Signed-off-by: Jaswinder Singh Signed-off-by: Galen Charlton --- .../perlmods/lib/OpenILS/Application/EbookAPI.pm | 847 +- .../lib/OpenILS/Application/EbookAPI/RBDigital.pm | 1123 ++ .../src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm | 35 +- .../lib/OpenILS/WWW/EGCatLoader/Account.pm | 7 + .../lib/OpenILS/WWW/EGCatLoader/RBDigitalRecord.pm | 162 + .../lib/OpenILS/WWW/EGCatLoader/RBDigitalSearch.pm | 492 + .../perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm | 3 +- .../src/perlmods/live_t/100-ebookapi-rbdigital.t | 129 + .../1030.data.org-setting.ebook-api-rbdigital.sql | 63 + Open-ILS/src/templates/opac/css/style.css.tt2 | 5 + .../opac/ebook_api/rbdigital/advanced.tt2 | 44 + .../ebook_api/rbdigital/manage_search_fields.tt2 | 146 + .../rbdigital/map_seach_field_values_modal.tt2 | 57 + .../rbdigital/parts/advanced/global_row.tt2 | 134 + .../ebook_api/rbdigital/parts/advanced/search.tt2 | 126 + .../rbdigital/parts/coded_value_selector.tt2 | 48 + .../ebook_api/rbdigital/parts/file_formats.tt2 | 15 + .../opac/ebook_api/rbdigital/parts/filtersort.tt2 | 24 + .../opac/ebook_api/rbdigital/parts/item_parser.tt2 | 112 + .../opac/ebook_api/rbdigital/parts/record/body.tt2 | 22 + .../rbdigital/parts/record/navigation.tt2 | 5 + .../rbdigital/parts/record/recommendations.tt2 | 62 + .../ebook_api/rbdigital/parts/record/summary.tt2 | 183 + .../rbdigital/parts/record/summaryplus.tt2 | 39 + .../ebook_api/rbdigital/parts/result/facets.tt2 | 141 + .../ebook_api/rbdigital/parts/result/lowhits.tt2 | 68 + .../ebook_api/rbdigital/parts/result/table.tt2 | 454 + .../opac/ebook_api/rbdigital/parts/searchbar.tt2 | 184 + .../templates/opac/ebook_api/rbdigital/record.tt2 | 28 + .../opac/ebook_api/rbdigital/register_modal.tt2 | 61 + .../templates/opac/ebook_api/rbdigital/results.tt2 | 92 + Open-ILS/src/templates/opac/home.tt2 | 0 Open-ILS/src/templates/opac/parts/base.tt2 | 8 + .../src/templates/opac/parts/bookbag_actions.tt2 | 0 Open-ILS/src/templates/opac/parts/config.tt2 | 6 +- .../src/templates/opac/parts/ebook_api/base_js.tt2 | 12 +- .../templates/opac/parts/filter_group_selector.tt2 | 0 Open-ILS/src/templates/opac/parts/misc_util.tt2 | 20 +- .../src/templates/opac/parts/record/summary.tt2 | 0 Open-ILS/src/templates/opac/parts/topnav.tt2 | 1 + Open-ILS/src/templates/opac/parts/topnav_links.tt2 | 0 Open-ILS/src/templates/opac/parts/vendor_tabs.tt2 | 28 + Open-ILS/src/templates/opac/results.tt2 | 4 + Open-ILS/web/css/skin/default/ebook/rbdigital.css | 83 + Open-ILS/web/images/rbdigital_logo.png | Bin 0 -> 3158 bytes Open-ILS/web/js/ui/default/opac/ebook_api/ebook.js | 215 + .../web/js/ui/default/opac/ebook_api/rbdigital.js | 389 + Open-ILS/web/reports/fm_IDL-conf.xml | 12337 +++++++++++++++++++ Open-ILS/web/reports/fm_IDL.xml-jaswinder | 12158 ++++++++++++++++++ 49 files changed, 30159 insertions(+), 13 deletions(-) mode change 100644 => 100755 Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm create mode 100755 Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/RBDigital.pm mode change 100644 => 100755 Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm mode change 100644 => 100755 Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm create mode 100755 Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/RBDigitalRecord.pm create mode 100755 Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/RBDigitalSearch.pm mode change 100644 => 100755 Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm create mode 100755 Open-ILS/src/perlmods/live_t/100-ebookapi-rbdigital.t create mode 100755 Open-ILS/src/sql/Pg/upgrade/1030.data.org-setting.ebook-api-rbdigital.sql mode change 100644 => 100755 Open-ILS/src/templates/opac/css/style.css.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/advanced.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/manage_search_fields.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/map_seach_field_values_modal.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/advanced/global_row.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/advanced/search.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/coded_value_selector.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/file_formats.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/filtersort.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/item_parser.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/body.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/navigation.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/recommendations.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/summary.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/summaryplus.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/facets.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/lowhits.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/table.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/searchbar.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/record.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/register_modal.tt2 create mode 100755 Open-ILS/src/templates/opac/ebook_api/rbdigital/results.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/home.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/base.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/bookbag_actions.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/config.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/ebook_api/base_js.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/filter_group_selector.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/misc_util.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/record/summary.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/topnav.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/parts/topnav_links.tt2 create mode 100755 Open-ILS/src/templates/opac/parts/vendor_tabs.tt2 mode change 100644 => 100755 Open-ILS/src/templates/opac/results.tt2 create mode 100755 Open-ILS/web/css/skin/default/ebook/rbdigital.css create mode 100755 Open-ILS/web/images/rbdigital_logo.png mode change 100644 => 100755 Open-ILS/web/js/ui/default/opac/ebook_api/ebook.js create mode 100755 Open-ILS/web/js/ui/default/opac/ebook_api/rbdigital.js create mode 100755 Open-ILS/web/reports/fm_IDL-conf.xml create mode 100755 Open-ILS/web/reports/fm_IDL.xml-jaswinder diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm old mode 100644 new mode 100755 index bc968b5586..3db3d36336 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI.pm @@ -40,6 +40,8 @@ use OpenSRF::Utils::Logger qw($logger); use OpenSRF::Utils::Cache; use OpenSRF::Utils::JSON; use OpenILS::Utils::HTTPClient; +use Scalar::Util 'blessed'; +use Data::Dumper; my $handler; my $cache; @@ -50,7 +52,8 @@ my $default_request_timeout; our %vendor_handlers = ( 'ebook_test' => 'OpenILS::Application::EbookAPI::Test', 'oneclickdigital' => 'OpenILS::Application::EbookAPI::OneClickdigital', - 'overdrive' => 'OpenILS::Application::EbookAPI::OverDrive' + 'overdrive' => 'OpenILS::Application::EbookAPI::OverDrive', + 'rbdigital' => 'OpenILS::Application::EbookAPI::RBDigital' ); sub initialize { @@ -382,9 +385,11 @@ sub request { sub get_details { my ($self, $conn, $session_id, $title_id) = @_; + $logger->info("EbookAPI: Calling an api to get title info"); my $handler = new_handler($session_id); return $handler->get_title_info($title_id); } + __PACKAGE__->register_method( method => 'get_details', api_name => 'open-ils.ebook_api.title.details', @@ -411,6 +416,39 @@ __PACKAGE__->register_method( } ); +sub get_title_summary { + my ($self, $conn, $session_id, $title_id) = @_; + $logger->info("EbookAPI: Calling an api to get title summary"); + my $handler = new_handler($session_id); + return $handler->get_title_summary($title_id); +} + +__PACKAGE__->register_method( + method => 'get_title_summary', + api_name => 'open-ils.ebook_api.title.summary', + api_level => 1, + argc => 2, + signature => { + desc => "Get ebook title summary", + params => [ + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'title_id', + desc => 'The title ID (ISBN, unique identifier, etc.)', + type => 'string' + } + ], + return => { + desc => 'Success: { isbn => "isbn", summary => "book summary" } / Failure: { error => "Title not found" }', + type => 'hashref' + } + } +); + sub get_availability { my ($self, $conn, $session_id, $title_id) = @_; my $handler = new_handler($session_id); @@ -477,7 +515,7 @@ __PACKAGE__->register_method( # patron and a title identifier (checkout, checkin, renewal, etc). # # Params: -# - title_id: ISBN (OneClickdigital), title identifier (OverDrive) +# - title_id: ISBN (OneClickdigital), ISBN (RBDigital), title identifier (OverDrive) # - barcode: patron barcode # sub do_xact { @@ -505,7 +543,8 @@ sub do_xact { } my $handler = new_handler($session_id); - my $user_token = $handler->do_patron_auth($barcode); + + my $user_token = _get_user_token($handler, $e, $barcode); # handler method constructs and submits request (and handles any external authentication) my $res; @@ -712,10 +751,11 @@ sub _get_patron_xacts { $logger->error("EbookAPI: authentication failed: " . $e->die_event); return; } - + my $handler = new_handler($session_id); - my $user_token = $handler->do_patron_auth($barcode); + my $user_token = _get_user_token($handler, $e, $barcode); + my $xacts; if ($xact_type eq 'checkouts') { $xacts = $handler->get_patron_checkouts($user_token); @@ -737,6 +777,20 @@ sub _get_patron_xacts { } } +# Call this method to retrieve the user token/patron id +sub _get_user_token { + my ($handler, $editor, $barcode) = @_; + + #For RBDigital vendor, send user email || email + if (blessed($handler) eq 'OpenILS::Application::EbookAPI::RBDigital') { + $logger->info("EbookAPI: handler->do_patron_auth(".$editor->requestor->usrname.", ".$editor->requestor->email.")"); + return $handler->do_patron_auth($editor->requestor->usrname, $editor->requestor->email); + } + + $logger->info("EbookAPI: handler->do_patron_auth($barcode)"); + return $handler->do_patron_auth($barcode); +} + sub get_patron_xacts { my ($self, $conn, $auth, $session_id, $barcode) = @_; my $xact_type; @@ -884,4 +938,787 @@ __PACKAGE__->register_method( } ); +# Get Patron ID from vendor +sub get_patron_id { + my ($self, $conn, $auth, $session_id, $barcode) = @_; + $logger->info("EbookAPI: getting patron id for patron $barcode"); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return 0; + } + + $logger->error("EbookAPI: User Authenticated. Now, calling for patron authentication"); + my $handler = new_handler($session_id); + return $handler->do_patron_auth($editor->requestor->usrname, $editor->requestor->email); +} + +__PACKAGE__->register_method( + method => 'get_patron_id', + api_name => 'open-ils.ebook_api.get_patron_id', + api_level => 1, + argc => 3, + signature => { + desc => "Get a patron id", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'barcode', + desc => 'The barcode of the patron (Optional)', + type => 'string' + } + ], + return => { + desc => 'Returns a patron Id, or undef if no details available', + type => 'string' + } + } +); + +# Call an API to register a patron +sub register_patron { + my ($self, $conn, $auth, $session_id, $password) = @_; + $logger->info("EbookAPI: Initiating patron registration"); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + $logger->info("EbookAPI: User Authenticated. Now, registering the patron with RBDigital"); + my $handler = new_handler($session_id); + + return $handler->register_patron($editor->requestor->usrname, $password); +} + +__PACKAGE__->register_method( + method => 'register_patron', + api_name => 'open-ils.ebook_api.patron.register', + api_level => 1, + argc => 3, + signature => { + desc => "Register a patron to vendor site", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'password', + desc => 'The password for vendor portal', + type => 'string' + } + ], + return => { + desc => 'Returns a patron Id, or undef if unable to register a patron', + type => 'string' + } + } +); + +# Basic Search call to Recorded Books API +sub basic_search { + my ($self, $conn, $auth, $session_id, + $value_to_search, $search_type, + $media_format, $library_location, $facets, $search_inputs, + $sort_by, $sort_order, + $page_index, $page_size) = @_; + + $logger->info("EbookAPI: performing a basic search."); + $logger->debug("EbookAPI: $auth, $session_id, $value_to_search, $search_type, + $media_format, $library_location, $sort_by, $sort_order, $page_index, $page_size"); + my $handler = new_handler($session_id); + if ( defined($handler) && blessed($handler) ne 'OpenILS::Application::EbookAPI::RBDigital') { + $logger->error("EbookAPI: Basic search is not supported by vendor " . blessed($handler)); + return; + } + + $logger->info("EbookAPI: basic_search - checking user auth."); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + my $patron_id = _get_user_token($handler, $editor, 0); + + $logger->debug("EbookAPI: count facets: ". scalar(@$facets)); + + $logger->info("EbookAPI: basic_search - Calling an internal API."); + + return $handler->do_basic_search($patron_id, + $value_to_search, $search_type, + $media_format, $library_location, \@$facets, \@$search_inputs, + $sort_by, $sort_order, + $page_index, $page_size); +} + +__PACKAGE__->register_method( + method => 'basic_search', + api_name => 'open-ils.ebook_api.basic_search', + api_level => 1, + argc => 11, + signature => { + desc => "Perform a basic search", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'value_to_search', + desc => 'The search string', + type => 'string' + }, + { + name => 'search_type', + desc => 'The search type: keyword, title, etc', + type => 'string' + }, + { + name => 'media_format', + desc => 'The media format: eaudio, ebook, emagazine', + type => 'string' + }, + { + name => 'library_location', + desc => 'The Library Location', + type => 'string' + }, + { + name => 'facets', + desc => 'Facet for filtering', + type => 'array' + }, + { + name => 'search_inputs', + desc => 'Search inputs', + type => 'array' + }, + { + name => 'sort_by', + desc => 'The sort by field: title, author name, etc', + type => 'string' + }, + { + name => 'sort_order', + desc => 'The sort order: asc or desc', + type => 'string' + }, + { + name => 'page_index', + desc => 'The page index of a search result', + type => 'number' + }, + { + name => 'page_size', + desc => 'The number of items request in one call.', + type => 'number' + } + ], + return => { + desc => 'Returns search results.', + type => 'hashref' + } + } +); + +__PACKAGE__->register_method( + method => "get_authorized_vendors", + api_name => "open-ils.ebook_api.get_authorized_vendors", + api_level => 1, + argc => 2, + signature => { + desc => "Get vendors list", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + } + ], + return => {desc => 'List of all vendors'} + } +); + +sub get_authorized_vendors { + my ($self, $conn, $auth, $session_id) = @_; + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + my $handler = new_handler($session_id); + return $handler->get_authorized_vendors(); +} + +__PACKAGE__->register_method( + method => "is_authorized_patron", + api_name => "open-ils.ebook_api.is_authorized_patron", + api_level => 1, + argc => 2, + signature => { + desc => "Check if the patron is authorized to view library", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + } + ], + return => {desc => '1 for true or 0 for false'} + } +); + +sub is_authorized_patron { + my ($self, $conn, $auth, $session_id) = @_; + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + my $handler = new_handler($session_id); + return $handler->is_authorized_patron($editor->requestor->home_ou); +} + +sub get_book_recommendations { + my ($self, $conn, $auth, $session_id, $isbn, $media_format, $page_index, $page_size) = @_; + + $logger->info("EbookAPI: Parameters- $session_id"); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + my $handler = new_handler($session_id); + + my $patron_id = _get_user_token($handler, $editor, 0); + + return $handler->get_book_recommendations($patron_id, $isbn, $media_format, $page_index, $page_size); +} + +__PACKAGE__->register_method( + method => "get_book_recommendations", + api_name => "open-ils.ebook_api.get_book_recommendations", + api_level => 1, + argc => 6, + signature => { + desc => "Get book recommendation", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'isbn', + desc => 'The ISBN number', + type => 'string' + }, + { + name => 'media_format', + desc => 'The media format/type: eaudio, ebook, emagazine', + type => 'string' + }, + { + name => 'page_index', + desc => 'The page index of a search result', + type => 'number' + }, + { + name => 'page_size', + desc => 'The number of items request in one call.', + type => 'number' + } + ], + return => { + desc => 'Return list of book recommendations', + type => 'hashref' + } + } +); + + +__PACKAGE__->register_method( + method => "do_wishlist", + api_name => "open-ils.ebook_api.patron.wishlist", + api_level => 1, + argc => 4, + signature => { + desc => "Add/delete or get wishlist", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'title_id', + desc => 'The title ID (ISBN, unique identifier, etc.)', + type => 'string' + }, + { + name => 'action', + desc => 'add, delete, or get patron wishlist', + type => 'string' + } + ], + return => {desc => '{error_msg => "Wishlist already exist"} OR {message => "success"'} + } +); + +sub do_wishlist { + my ($self, $conn, $auth, $session_id, $isbn, $action) = @_; + + $logger->info("EbookAPI: Processing wishlist (token: $auth, session_id: $session_id, action: $action) for $isbn"); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + my $handler = new_handler($session_id); + + if ( defined($handler) && blessed($handler) ne 'OpenILS::Application::EbookAPI::RBDigital') { + $logger->error("EbookAPI: Wishlist is not supported by vendor: " . blessed($handler)); + return; + } + + my $patron_id = _get_user_token($handler, $editor, 0); + + if ($action eq 'add') { + $logger->info("EbookAPI: Calling add_to_wishlist($patron_id, $isbn)"); + return $handler->add_to_wishlist($patron_id, $isbn); + } elsif ($action eq 'delete') { + return $handler->delete_from_wishlist($patron_id, $isbn); + } elsif ($action eq 'get') { + return $handler->get_wishlist($patron_id, $isbn); + } + + $logger->error("EbookAPI: unsupported wishlist action requested"); + return undef; +} + +sub get_search_filters { + my ($self, $conn, $auth, $session_id) = @_; + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + my $handler = new_handler($session_id); + + return $handler->get_search_filters(); +} + +__PACKAGE__->register_method( + method => "get_search_filters", + api_name => "open-ils.ebook_api.get_search_filters", + api_level => 1, + argc =>2, + signature => { + desc => "Get book recommendation", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + } + ], + return => { + desc => 'Return list of book recommendations', + type => 'hashref' + } + } +); + +sub remove_search_fields { + my ($self, $conn, $auth, $session_id, $search_fields_id) = @_; + $logger->info("EbookAPI: Removing Search Field Mapping!"); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + $logger->info("EbookAPI: User Authenticated!"); + my $handler = new_handler($session_id); + + return $handler->remove_search_fields($auth, $search_fields_id); +} + +__PACKAGE__->register_method( + method => 'remove_search_fields', + api_name => 'open-ils.ebook_api.search_fields.remove', + api_level => 1, + argc => 3, + signature => { + desc => "Call to remove search fields mapping", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'search_fields_id', + desc => 'A unique id of the search field', + type => 'string' + } + ], + return => { + desc => 'Returns an Id of newly created record', + type => 'string' + } + } +); + + +# Call an API to register a patron +sub save_search_fields { + my ($self, $conn, $auth, $session_id, $vendor_key, $eg_search_field, $ds_search_field, $field_type) = @_; + $logger->info("EbookAPI: Saving Search Field"); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + $logger->info("EbookAPI: User Authenticated!"); + my $handler = new_handler($session_id); + + return $handler->save_search_fields($auth, $vendor_key, $eg_search_field, $ds_search_field, $field_type); +} + +__PACKAGE__->register_method( + method => 'save_search_fields', + api_name => 'open-ils.ebook_api.search_fields.save', + api_level => 1, + argc => 5, + signature => { + desc => "Call to save search fields mapping", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'vendor_key', + desc => 'Third party vendor key (rbdigital, OneClickDigital)', + type => 'string' + }, + { + name => 'eg_search_field', + desc => 'Evergreen Search Field', + type => 'string' + }, + { + name => 'ds_search_field', + desc => 'Digital Service Search Field', + type => 'string' + }, + { + name => 'field_type', + desc => 'Search Field Type', + type => 'string' + } + ], + return => { + desc => 'Returns an Id of newly created record', + type => 'string' + } + } +); + +sub get_search_field_mappings { + my ($self, $conn, $auth, $session_id, $search_fields_id) = @_; + $logger->info("EbookAPI: Getting Search Fields values ..."); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + $logger->info("EbookAPI: User Authenticated!"); + my $handler = new_handler($session_id); + + return $handler->get_search_field_mappings($search_fields_id); +} + +__PACKAGE__->register_method( + method => 'get_search_field_mappings', + api_name => 'open-ils.ebook_api.search_field_mappings.retrieve', + api_level => 1, + argc => 3, + signature => { + desc => "Call to save search fields mapping", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'search_fields_id', + desc => 'Search Fields ID', + type => 'number' + } + ], + return => { + desc => 'Returns a list of search_field_mappings value', + type => 'array' + } + } +); + +sub get_evergreen_search_field_values { + my ($self, $conn, $auth, $session_id, $field_type) = @_; + $logger->info("EbookAPI: Getting Search Fields values for Evergreen ..."); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + $logger->info("EbookAPI: User Authenticated!"); + my $handler = new_handler($session_id); + + return $handler->get_evergreen_search_field_values($field_type); +} + +__PACKAGE__->register_method( + method => 'get_evergreen_search_field_values', + api_name => 'open-ils.ebook_api.evergreen_search_field_values.retrieve', + api_level => 1, + argc => 3, + signature => { + desc => "Call to get evergreen search fields values", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'field_type', + desc => 'Field type like item_lang, audience, etc', + type => 'string' + } + ], + return => { + desc => 'Returns a list of search_field_mappings value', + type => 'array' + } + } +); + +sub save_search_field_values { + my ($self, $conn, $auth, $session_id, $search_fields_id, $evergreen_field_code, $evergreen_field_value, + $digital_services_field_code, $digital_services_field_value) = @_; + $logger->info("EbookAPI: Saving Search Fields values ..."); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + $logger->info("EbookAPI: User Authenticated!"); + my $handler = new_handler($session_id); + + return $handler->save_search_field_values($auth, $search_fields_id, $evergreen_field_code, $evergreen_field_value, $digital_services_field_code, $digital_services_field_value); +} + +__PACKAGE__->register_method( + method => 'save_search_field_values', + api_name => 'open-ils.ebook_api.search_field_mappings.save', + api_level => 1, + argc => 5, + signature => { + desc => "Call to save evergreen search fields values", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'search_fields_id', + desc => 'Search Fields Id', + type => 'number' + }, + { + name => 'evergreen_field_code', + desc => 'Evergreen Fields Code', + type => 'string' + }, + { + name => 'evergreen_field_value', + desc => 'Evergreen Fields value', + type => 'string' + }, + { + name => 'digital_services_field_code', + desc => 'DS Fields code', + type => 'string' + }, + { + name => 'digital_services_field_value', + desc => 'DS Fields value', + type => 'string' + } + ], + return => { + desc => 'Returns a id of newly created record in search_field_mappings', + type => 'hashref' + } + } +); + +sub remove_search_field_mappings { + my ($self, $conn, $auth, $session_id, $id) = @_; + $logger->info("EbookAPI: Removing Search Field Value Mappings!"); + + # verify that user is authenticated in EG + my $editor = new_editor(authtoken => $auth); + if (!$editor->checkauth) { + $logger->error("EbookAPI: authentication failed: " . $editor->die_event); + return; + } + + $logger->info("EbookAPI: User Authenticated!"); + my $handler = new_handler($session_id); + + return $handler->remove_search_field_mappings($auth, $id); +} + +__PACKAGE__->register_method( + method => 'remove_search_field_mappings', + api_name => 'open-ils.ebook_api.search_field_mappings.remove', + api_level => 1, + argc => 3, + signature => { + desc => "Call to remove search fields mapping", + params => [ + { + name => 'authtoken', + desc => 'Authentication token', + type => 'string' + }, + { + name => 'session_id', + desc => 'The session ID (provided by open-ils.ebook_api.start_session)', + type => 'string' + }, + { + name => 'search_fields_id', + desc => 'A unique id of the search field', + type => 'string' + } + ], + return => { + desc => 'Returns an Id of newly created record', + type => 'string' + } + } +); 1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/RBDigital.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/RBDigital.pm new file mode 100755 index 0000000000..2d2cba10cb --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/EbookAPI/RBDigital.pm @@ -0,0 +1,1123 @@ +#!/usr/bin/perl + +# Copyright (C) 2015 BC Libraries Cooperative +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +package OpenILS::Application::EbookAPI::RBDigital; + +use strict; +use warnings; + +use OpenILS::Application; +use OpenILS::Application::Actor; +use OpenILS::Application::EbookAPI; +use base qw/OpenILS::Application::EbookAPI/; +use OpenSRF::AppSession; +use OpenSRF::EX qw(:try); +use OpenSRF::Utils::SettingsClient; +use OpenSRF::Utils::Logger qw($logger); +use OpenSRF::Utils::Cache; +use OpenSRF::Utils::JSON; +use OpenILS::Application::AppUtils; +use Data::Dumper; + +use constant TEST_QA => 0; +use constant EBOOK_API_VENDOR => 'rbdigital'; +use constant EBOOK_API_DOMAIN => 'http://api.rbdigitalstage.com/v1'; +use constant EBOOK_API_DOMAIN_QA => 'http://api.rbdigitalqa.com/v1'; +use constant EBOOK_API_QA_TOKEN => '5ad925df4ee5df112009e562'; + +sub new { + my( $class, $args ) = @_; + $class = ref $class || $class; + return bless $args, $class; +} + +sub ou { + my $self = shift; + return $self->{ou}; +} + +sub vendor { + my $self = shift; + return $self->{vendor}; +} + +sub session_id { + my $self = shift; + return $self->{session_id}; +} + +sub base_uri { + my $self = shift; + return $self->{base_uri}; +} + +sub library_id { + my $self = shift; + return $self->{library_id}; +} + +sub basic_token { + my $self = shift; + return $self->{basic_token}; +} + +sub patron_id { + my $self = shift; + return $self->{patron_id}; +} + +sub initialize { + my $self = shift; + my $ou = $self->{ou}; + + $self->{base_uri} = OpenILS::Application::AppUtils->ou_ancestor_setting_value($ou, 'ebook_api.rbdigital.base_uri') || EBOOK_API_DOMAIN; + + my $object_rdsa = _get_vendor_library($ou); + + if ($object_rdsa) { + $self->{library_id} = $object_rdsa->digital_services_library_id; + $self->{basic_token} = $object_rdsa->digital_services_library_token; + } else { + $logger->error("EbookAPI: no RBDigital library ID/token found for org unit $ou"); + return; + } + + return $self; + +} + +# RBDigital API does not require separate client auth; +# we just need to include our basic auth token in requests +sub do_client_auth { + my $self = shift; + return; +} + +# This method will return the rbdigital library based on patron's home library id (usr.home_ou/org_unit.id) +sub _get_vendor_library { + my $home_ou = @_; + my $object_rdsa; + my $ebook_rds_req; + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + + $ebook_rds_req = $pcrud->request('open-ils.pcrud.search.ebook_rdsa.atomic', "ANONYMOUS", + { + home_ou => $home_ou, + "+ebook_rds" => { + vendor_key => EBOOK_API_VENDOR + } + }, + { + flesh => 1, + flesh_fields => { ebook_rdsa => ['digital_services_id']}, + join => { + ebook_rds => {} + } + } + )->gather(1); + + if ($ebook_rds_req && scalar (@$ebook_rds_req) > 0) { + #read the library.id to find corrosponding library id from digital services atuhorized table + $object_rdsa = $ebook_rds_req->[0]; + } + + return $object_rdsa; +} + +# Call this method to all the vendors (from rbdigital.digital_services table) tied to user's home library in EG +sub get_authorized_vendors { + my $home_ou = @_; + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + + my $ebook_rds_req = $pcrud->request('open-ils.pcrud.search.ebook_rds.atomic', "ANONYMOUS", + { + is_enabled => 't', + "+ebook_rdsa" => { + home_ou => $home_ou + } + }, + { + flesh => 1, + flesh_fields => { ebook_rds => ['id']}, + order_by => { ebook_rds => 'display_order ASC' }, + join => { + ebook_rdsa => {}, + } + } + )->gather(); + + return $ebook_rds_req; +} + +# retrieve RBDigital patron ID based on patron email or username +# GET http://api.rbdigital.com/v1/rpc/libraries/{libraryID}/patrons/$user_key +sub do_patron_auth { + my ($self, $username, $email) = @_; + my $patron_id = 0; + + if ($email) { + # first try to authenticate the user with a email + $patron_id = $self->_get_patron_id($email); + } + + if ($patron_id eq 0) { + # try to authenticate the user with a username + $patron_id = $self-> _get_patron_id($username); + + if ($patron_id < 1) { + # TODO: Remove/Replace the below line + $patron_id = 99999; + } + } + return $patron_id; +} + +sub _get_patron_id { + my ($self, $user_key) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + + my $req = { + method => 'GET', + uri => "$base_uri/rpc/libraries/$library_id/patrons/$user_key", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token}, + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + } + }; + + my $res = $self->request($req, $session_id); + if ($res && defined($res->{content}->{patronId})) { + $self->{patron_id} = $res->{content}->{patronId}; + return $res->{content}->{patronId}; + } + + if (defined ($res)) { + if ($res->{is_success} && defined($res->{content}->{patronId}) ) { + $self->{patron_id} = $res->{content}->{patronId}; + return $res->{content}->{patronId}; + } else { + $logger->error("EbookAPI: Unable to get patron Id from RBDigital portal: ".$res->{content}->{message}); + return 0; + } + } else { + $logger->error("EbookAPI: no API response received from RBDigital server."); + } + + return 0; +} + +# Basic Search: Retrieve ebooks from RBDigital using the library id and patron ID +# Handles ebook, eaudio, and emagazine search +# GET http://api.rbdigital.com/v1/libraries/{libraryID}/search/{media_format}?{*tokens} +sub do_basic_search { + my ($self, $patron_id, $value_to_search, $search_type, + $media_format, $library_location, $facets, $search_inputs, + $sort_by, $sort_order, + $page_index, $page_size) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $basic_search_uri = ''; + my $uri = ''; + my $req; + $logger->info('EbookAPI: do_basic_search - Start'); + + if ($value_to_search eq '' && scalar(@$search_inputs) == 0) { + # Nothing to search + $logger->info('EbookAPI: No search value was entered by user. Returning without an api call.'); + return; + } + + # Call local method to get URI for a request + $basic_search_uri = _build_search_URI($media_format, $value_to_search, $facets, $search_inputs, $sort_by, $sort_order, $page_index, $page_size); + + $logger->info('EbookAPI: Calling the search API: '.$uri); + if (TEST_QA) { + $uri = EBOOK_API_DOMAIN_QA."/$basic_search_uri"; + $req = { + method => 'GET', + uri => $uri, + headers => { + 'Authorization' => 'bearer ' . EBOOK_API_QA_TOKEN, + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + } + }; + } else { + $uri = "$base_uri/libraries/$library_id/$basic_search_uri"; + $req = { + method => 'GET', + uri => $uri, + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token}, + 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' + } + }; + } + + my $res = $self->request($req, $session_id); + + if (defined ($res) && $res->{is_success}) { + return $res; + } + + $logger->error("EbookAPI: Basic Search - API call to RBDigital failed for"); + return; +} + +sub _build_search_URI { + my ($media_format, $value_to_search, $facets, $search_inputs, $sort_by, $sort_order, $page_index, $page_size) = @_; + my $uri = ''; + my $sort_string = ''; + + if ($sort_by ne '') { + $sort_string = "&sort-by=$sort_by&sort-order=$sort_order"; + } + + # convert book to ebook + if ($media_format eq 'book') { + $media_format = 'ebook'; + } + + if ($media_format eq 'eaudio' || $media_format eq 'ebook') { + $uri = "search/$media_format?search-source=quick-all&page-size=$page_size". + "&page-index=$page_index&all=$value_to_search&mediatype=$media_format".$sort_string; + } elsif ($media_format eq '' && scalar (@$search_inputs) == 0) { + $uri = "search?q=$value_to_search&page-size=$page_size". + "&page-index=$page_index".$sort_string; + } elsif (scalar (@$search_inputs) > 0) { + #build URI for Advanced Search + $uri = "search?search-source=advanced-search"; + $logger->error('EbookAPI: looping over search inputs for advanced search.'); + for my $input (@$search_inputs) { + # Split search input e.g. title_happy and join to have title=happy + $input =~ s/\Q_\E/=/g; + if ($input ne '') { + $uri = $uri . '&' . $input; + } + } + $uri = $uri . "&page-size=$page_size&page-index=$page_index" . $sort_string; + } else { + # Unsupported format return nothing + $logger->error('EbookAPI: Unsupported media type requested'); + } + + for my $facet (@$facets) { + if ($facet ne '') { + # Split facet e.g. audience_young-adult and join to have audience=young-adult + $facet =~ s/\Q_\E/=/g; + $uri = $uri.'&'.$facet; + } + } + + return $uri; +} + +# Create a new account with RBDigital platform +# POST http://api.rbdigital.com/v1/libraries/{libraryId}/patrons +# { +# "firstName": "john", +# "lastName": "smith", +# "userName": "jsmith", +# "password": "jsmith-2018", +# "email": "jsmith@yahoo.com", +# "postalCode": "30904", +# "isActive": false, +# "libraryId": {libraryId} +# } +sub register_patron { + my ($self, $username, $password) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $postal_code = ''; + my $is_active = 'true'; + my $email = ""; + + $logger->info("EbookAPI: Registering Patron to RBDigital portal"); + + # First, get the user information + my $cstore = OpenSRF::AppSession->create('open-ils.cstore'); + $cstore->connect(); + + $logger->info("EbookAPI: Calling to retreive a user from db."); + my $user_req = $cstore->request('open-ils.cstore.direct.actor.user.search', + { usrname => $username } + )->gather(); + + $logger->info("EbookAPI: Checking if postal code is found from db."); + # Second, get the user postal_code information (if any) + if ($user_req && defined ($user_req->mailing_address) ) { + $logger->info("EbookAPI: Calling CStore to retreive user mailing address postal code: " . $user_req->mailing_address); + #Get the mailing address id to retrieve postal code + my $user_mailing_address_req = $cstore->request('open-ils.cstore.direct.actor.user_address.search', + { id => $user_req->mailing_address } + )->gather(); + + if ( $user_mailing_address_req && defined($user_mailing_address_req->post_code) ) { + $postal_code = $user_mailing_address_req->post_code; + $logger->info("EbookAPI: Postal code received: $postal_code"); + } else { + #Log Postal code not found, this should not happen since the user is already authenticated + $logger->info("EbookAPI: Postal code not found in db for patron registration"); + } + } else { + $logger->info("EbookAPI: User's mailing address not found for patron registration"); + return 0; + } + + $logger->info("EbookAPI: Building a request for patron registration"); + my $request_content = { + firstName => $user_req->first_given_name, + lastName => $user_req->family_name, + userName => $user_req->usrname, + password => $password, + email => $user_req->email, + postalCode => $postal_code, + isActive => $is_active, + libraryId => $library_id + }; + + my $req = { + method => 'POST', + uri => "$base_uri/libraries/$library_id/patrons", + content => OpenSRF::Utils::JSON->perl2JSON($request_content), + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token}, + 'Content-Type' => 'application/json' + } + }; + + $logger->info("EbookAPI: register patron post request: ". Dumper($req)); + + $logger->info("EbookAPI: Calling an API to registering Patron: $username"); + my $res = $self->request($req, $session_id); + if (defined ($res)) { + if ($res->{content}->{patronId}) { + $logger->info("EbookAPI: User $username successfully registered with RBDigital portal."); + return $res->{content}->{patronId}; + } else { + $logger->error("EbookAPI: Registration for $username to RBDigital failed: ".$res->{content}->{message}); + } + } + + $logger->error("EbookAPI: API call to register patron with RBDigital failed for : $username"); + return 0; +} + +# get basic metadata for an item (title, author, cover image if any) +# GET //api.{domain}/v1/libraries/{libraryId}/books/{isbn} +sub get_title_info { + my ($self, $isbn) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req; + + $logger->error("EbookAPI: Getting RBDigital title details for ISBN: $isbn"); + $req = { + method => 'GET', + uri => "$base_uri/libraries/$library_id/book-metadata/$isbn", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token} + } + }; + + my $res = $self->request($req, $session_id); + if (defined($res)) { + $logger->debug("EbookAPI: get_title_info api response ".Dumper($res->{content})); + return $res; + } else { + $logger->error("EbookAPI: could not retrieve RBDigital title details for ISBN: $isbn"); + } + + return undef; +} + +# get title for an item (title, author, cover image if any) +# GET //api.{domain}/v1/libraries/$library_id/book-metadata/$isbn/summary +sub get_title_summary { + my ($self, $isbn) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req; + + $logger->error("EbookAPI: Getting RBDigital title details for ISBN: $isbn"); + $req = { + method => 'GET', + uri => "$base_uri/libraries/$library_id/book-metadata/$isbn/summary", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token} + } + }; + + my $res = $self->request($req, $session_id); + if (defined($res)) { + $logger->info("EbookAPI: get_title_info api response: ".Dumper($res->{content})); + return $res; + } else { + $logger->error("EbookAPI: could not retrieve RBDigital title details for ISBN: $isbn"); + } + + return undef; +} + +# GET //api.{domain}/v1/libraries/$library_id/patron-books/Wishlist/$isbn +sub get_wishlist { + my ($self, $patron_id, $isbn) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req; + + $logger->error("EbookAPI: Getting wishlist for ISBN: $isbn from RBDigital."); + $req = { + method => 'GET', + uri => "$base_uri/libraries/$library_id/patron-books/Wishlist/$isbn", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token} + } + }; + + my $res = $self->request($req, $session_id); + if (defined($res)) { + return $res; + } else { + $logger->error("EbookAPI: could not get ISBN: $isbn from RBDigital portal."); + } + + return undef; +} + +# POST //api.{domain}/v1/libraries/$library_id/patron-books/Wishlist/$isbn +sub add_to_wishlist { + my ($self, $patron_id, $isbn) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req; + + $logger->error("EbookAPI: Adding ISBN: $isbn to a RBDigital wishlist."); + $req = { + method => 'POST', + uri => "$base_uri/libraries/$library_id/patrons/$patron_id/books/Wishlist/$isbn", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token}, + 'Content-Length' => '0' + } + }; + + my $res = $self->request($req, $session_id); + + if (defined ($res)) { + if ($res->{is_success}) { + return $res->{content}; + } else { + $logger->error("EbookAPI: could not add ISBN: $isbn to RBDigital portal: ".$res->{content}->{message}); + return $res->{content}; + } + } else { + $logger->error("EbookAPI: no response received from RBDigital server."); + } + + return { message => "Vendor API error" }; +} + +# POST //api.{domain}/v1/libraries/$library_id/patron-books/Wishlist/$isbn +sub delete_from_wishlist { + my ($self, $patron_id, $isbn) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req; + + $logger->error("EbookAPI: Removing ISBN: $isbn from a RBDigital wishlist."); + $req = { + method => 'DELETE', + uri => "$base_uri/libraries/$library_id/patrons/$patron_id/books/Wishlist/$isbn", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token} + } + }; + + my $res = $self->request($req, $session_id); + if (defined ($res)) { + if ($res->{is_success}) { + return $res->{content}; + } else { + $logger->error("EbookAPI: could not add ISBN: $isbn to RBDigital portal: ".$res->{content}->{message}); + return $res->{content}; + } + } else { + $logger->error("EbookAPI: no response received from RBDigital server."); + } + + return { message => "Vendor API error when delete an ISBN." }; +} + +# GET //api.{domain}/v1/libraries/$library_id/patrons/$patron_id/book-recommendations/$media_format?isbn=$isbn&mediatype=$media_format&$page-index=$page_index&page-size=$page_size +sub get_book_recommendations { + my ($self, $patron_id, $isbn, $media_format, $page_index, $page_size) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req; + + # convert book to ebook + if ($media_format eq 'book') { + $media_format = 'ebook'; + } + + $logger->error("EbookAPI: Getting RBDigital $media_format recommendations."); + $req = { + method => 'GET', + uri => "$base_uri/libraries/$library_id/patrons/$patron_id/book-recommendations/$media_format?". + "isbn=$isbn&mediatype=$media_format&page-index=$page_index&page-size=$page_size", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token} + } + }; + + my $res = $self->request($req, $session_id); + if (defined($res) && $res->{is_success}) { + $logger->info("EbookAPI: get_book_recommendations api response received."); + return $res; + } else { + $logger->error("EbookAPI: could not retrieve RBDigital $media_format recommendations"); + } + + return undef; +} + +# does this title have available "copies"? y/n +# GET //api.{domain}/v1/libraries/{libraryID}/media/{isbn}/availability +sub do_availability_lookup { + my ($self, $isbn) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req = { + method => 'GET', + uri => "$base_uri/libraries/$library_id/media/$isbn/availability" + }; + my $res = $self->request($req, $session_id); + if (defined ($res)) { + $logger->info("EbookAPI: received availability response for ISBN $isbn: " . Dumper $res); + return $res->{content}->{availability}; + } else { + $logger->error("EbookAPI: could not retrieve RBDigital availability for ISBN $isbn"); + return; + } +} + +# RBDigital API does not support detailed holdings lookup, +# so we return basic availability information. +sub do_holdings_lookup { + my ($self, $isbn) = @_; + my $avail = $self->do_availability_lookup($isbn); + return { available => $avail }; +} + +# checkout an item to a patron +# item is identified by ISBN, patron ID is their barcode +# POST //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/books/checkouts/$isbn +sub checkout { + my ($self, $isbn, $patron_id) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req; + $req = { + method => 'POST', + uri => "$base_uri/libraries/$library_id/patrons/$patron_id/books/checkouts/$isbn", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token}, + 'Content-Length' => '0' + } + }; + + my $res = $self->request($req, $session_id); + + # HTTP 200 response indicates success, HTTP 409 indicates checkout limit reached + if (defined ($res)) { + if ($res->{is_success}) { + return $res; + } else { + $logger->error("EbookAPI: checkout failed for RBDigital title $isbn"); + return { error_msg => $res->{content} }; + } + } else { + $logger->error("EbookAPI: no response received from RBDigital server"); + return; + } +} + +# renew a checked-out item +# item id = ISBN, patron id = barcode +# PUT //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/checkouts/{isbn} +sub renew { + my ($self, $isbn, $patron_id) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req = { + method => 'PUT', + uri => "$base_uri/libraries/$library_id/patrons/$patron_id/checkouts/$isbn", + headers => { "Content-Length" => "0" } + }; + my $res = $self->request($req, $session_id); + + # TODO: more sophisticated response handling + # HTTP 200 response indicates success + if (defined ($res)) { + if ($res->{is_success}) { + return { + xact_id => $res->{content}->{transactionId}, + due_date => $res->{content}->{expiration} + }; + } else { + $logger->error("EbookAPI: renewal failed for RBDigital title $isbn"); + return { error_msg => $res->{content} }; + } + } else { + $logger->error("EbookAPI: no response received from RBDigital server"); + return; + } +} + +# checkin a checked-out item +# item id = ISBN, patron id = barcode +# XXX API docs indicate that a bearer token is required! +# DELETE //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/checkouts/{isbn} +sub checkin { + my ($self, $isbn, $patron_id) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req; + $req = { + method => 'DELETE', + uri => "$base_uri/libraries/$library_id/patrons/$patron_id/checkouts/$isbn", + headers => { + 'Authorization' => 'Basic ' . $self->{basic_token} + } + }; + + my $res = $self->request($req, $session_id); + if (defined ($res)) { + if ($res->{is_success}) { + return $res; + } else { + $logger->error("EbookAPI: checkin failed for RBDigital title $isbn"); + return { error_msg => $res->{content} }; + } + } else { + $logger->error("EbookAPI: no response received from RBDigital server"); + return; + } +} + +sub place_hold { +} + +sub cancel_hold { +} + +# GET //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/checkouts/all +sub get_patron_checkouts { + my ($self, $patron_id) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req = { + method => 'GET', + uri => "$base_uri/libraries/$library_id/patrons/$patron_id/checkouts/all" + }; + my $res = $self->request($req, $session_id); + + my $checkouts = []; + if (defined ($res)) { + $logger->info("EbookAPI: received response for RBDigital checkouts: " . Dumper $res); + foreach my $checkout (@{$res->{content}}) { + push @$checkouts, { + xact_id => $checkout->{transactionId}, + title_id => $checkout->{isbn}, + due_date => $checkout->{expiration}, + download_url => $checkout->{downloadUrl}, + title => $checkout->{title}, + author => $checkout->{authors} + }; + }; + $logger->info("EbookAPI: retrieved " . scalar(@$checkouts) . " RBDigital checkouts for patron $patron_id"); + $self->{checkouts} = $checkouts; + return $self->{checkouts}; + } else { + $logger->error("EbookAPI: failed to retrieve RBDigital checkouts for patron $patron_id"); + return; + } +} + +# GET //api.{domain}/v1/libraries/{libraryId}/patrons/{patronId}/holds/all +sub get_patron_holds { + my ($self, $patron_id) = @_; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + my $req = { + method => 'GET', + uri => "$base_uri/libraries/$library_id/patrons/$patron_id/holds/all" + }; + my $res = $self->request($req, $session_id); + + my $holds = []; + if (defined ($res)) { + $logger->info("EbookAPI: received response for RBDigital holds: " . Dumper $res); + foreach my $hold (@{$res->{content}}) { + push @$holds, { + xact_id => $hold->{transactionId}, + title_id => $hold->{isbn}, + expire_date => $hold->{expiration}, + title => $hold->{title}, + author => $hold->{authors}, + # XXX queue position/size and pending vs ready info not available via API + queue_position => '-', + queue_size => '-', + is_ready => 0 + }; + }; + $logger->info("EbookAPI: retrieved " . scalar(@$holds) . " RBDigital holds for patron $patron_id"); + $self->{holds} = $holds; + return $self->{holds}; + } else { + $logger->error("EbookAPI: failed to retrieve RBDigital holds for patron $patron_id"); + return; + } +} + +# Call this method to return 1 - true or 0 - false if the patron is associated with rbdigital library +sub is_authorized_patron { + my ($self, $home_library_id) = @_; + my $is_authorized = 0; + my $ebook_rds_req; + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + + $ebook_rds_req = $pcrud->request('open-ils.pcrud.search.ebook_rdsa.atomic', "ANONYMOUS", + { home_ou => $home_library_id } + )->gather(); + + if ($ebook_rds_req && scalar (@$ebook_rds_req) > 0) { + #read the library.id to find corrosponding library id from digital services atuhorized table + $is_authorized = 1; + } + + return $is_authorized; +} + +# Get user info based on Barcode +sub _get_user_info { + my $barcode = @_; + + my $cstore = OpenSRF::AppSession->create('open-ils.cstore'); + $cstore->connect(); + + my $user_card_req = $cstore->request('open-ils.cstore.direct.actor.card.search', + { barcode => $barcode } + )->gather(); + + if ($user_card_req && defined ($user_card_req->usr)) { + my $user_req = $cstore->request('open-ils.cstore.direct.actor.user.search', + { id => $user_card_req->usr } + )->gather(); + + return $user_req; + } + + return undef; +} + +# Get Search Filters +sub get_search_filters { + my $self = shift; + my $base_uri = $self->{base_uri}; + my $library_id = $self->{library_id}; + my $session_id = $self->{session_id}; + + # No need to send a token for this api call + my $req = { + method => 'GET', + uri => "$base_uri/facets/book/all?libraryId=$library_id", + headers => { + 'Authorization' => '' + } + }; + + my $res = $self->request($req, $session_id); + + if (defined ($res)) { + if ($res->{is_success}) { + return $res; + } else { + $logger->error("EbookAPI: Unable to retrieve search filters for RBDigital library id: $library_id - ". Dumper($res->{content})); + return { error_msg => $res->{content} }; + } + } else { + $logger->error("EbookAPI: API call failed to retrieve RBDigital search filters for library id: $library_id"); + } + + return undef; +} + +# Remove a record from rbdigital.search_fields table +sub remove_search_fields { + my ($self, $authtoken, $search_fields_id) = @_; + my $session_id = $self->{session_id}; + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + $pcrud->request('open-ils.pcrud.transaction.begin',$authtoken)->gather(1); + + my $search_fields_req = $pcrud->request('open-ils.pcrud.delete.ebook_rsf', $authtoken, $search_fields_id)->recv(1); + + $pcrud->request('open-ils.pcrud.transaction.commit',$authtoken)->gather(1); + + return { status => "success" }; +} + +# Save a record to rbdigital.search_fields table that holds the mapping information for Evergreen/Pines and Third party vendor search field +sub save_search_fields { + my ($self, $authtoken, $vendor_key, $eg_search_field, $ds_search_field, $field_type) = @_; + my $session_id = $self->{session_id}; + my $digital_services_id = 0; + + $logger->info("EbookAPI: Saving DS Service Field"); + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + + # First, get the Digital Service ID by name + $logger->info("EbookAPI: Calling to retreive vendor id from db."); + my $digital_services_req = $pcrud->request('open-ils.pcrud.search.ebook_rds.atomic', "ANONYMOUS", + { vendor_key => $vendor_key } + )->recv(); + + if ($digital_services_req) { + $digital_services_id = $digital_services_req->content->[0]->id; + + if ($digital_services_id > 0) { + + # Second, Check if it is not a duplicate entry + my $ebook_rsf_req = $pcrud->request('open-ils.pcrud.search.ebook_rsf.atomic', "ANONYMOUS", + { + digital_services_id => $digital_services_id, + digital_services_field => $ds_search_field + } + )->recv(); + + if ($ebook_rsf_req && defined($ebook_rsf_req->content->[0])) { + if ($ebook_rsf_req->content->[0]->evergreen_field eq $eg_search_field) { + return { status => "failed", message => "Duplicate entry found! Please try selecting different values" }; + } + + return { status => "failed", message => "The value '$ds_search_field' already exists! Please try selecting different values." }; + } + + #Save a record + my $xact; + + eval { + $xact = $pcrud->request( + 'open-ils.pcrud.transaction.begin', + $authtoken + )->gather(1); + }; + + if ($@) { + undef($xact); + } + + if ($xact) { + my $tmpl_rec = Fieldmapper::rbdigital::search_fields->new; + $tmpl_rec->digital_services_id($digital_services_id); + $tmpl_rec->evergreen_field($eg_search_field); + $tmpl_rec->digital_services_field($ds_search_field); + $tmpl_rec->field_type($field_type); + + $pcrud->request('open-ils.pcrud.create.ebook_rsf', $authtoken, $tmpl_rec )->gather(1); + + $xact = $pcrud->request( + 'open-ils.pcrud.transaction.commit', + $authtoken + )->gather(1); + + $logger->info("EbookAPI: Record saved"); + + return { status => "success" }; + } + } + } + + return { status => "failed", message => "Unknown error occurred! Please try to save the fields again." }; +} + +# Returns an hash containing array list of mapped search field values +sub get_search_field_mappings { + my ($self, $search_fields_id) = @_; + my $session_id = $self->{session_id}; + + $logger->info("EbookAPI: Getting DS Service Field Values for search field id: " . $search_fields_id); + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + + my $search_fields_mapping_req = $pcrud->request('open-ils.pcrud.search.ebook_rsfm.atomic', "ANONYMOUS", + { + search_fields_id => $search_fields_id + } + )->recv(); + + my @search_field_values = (); + # Iterate over list of values and build an array for JS + foreach my $value (@ {$search_fields_mapping_req->content}) { + push @search_field_values, [$value->id, $value->search_fields_id, $value->evergreen_field_code, + $value->evergreen_field_value, $value->digital_services_field_code, $value->digital_services_field_value]; + } + + return { status => "success", data => [@search_field_values] }; +} + +sub get_evergreen_search_field_values { + my ($self, $field_type) = @_; + my $session_id = $self->{session_id}; + + $logger->info("EbookAPI: Getting DS Service Field Values for search field type: " . $field_type); + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + + my $ccvm_req = $pcrud->request('open-ils.pcrud.search.ccvm.atomic', "ANONYMOUS", + { + opac_visible => 't', + ctype => $field_type, + code => {'!=' => ' '} + } + )->recv(); + + my @search_field_values = (); + # Iterate over list of values and build an array for JS + foreach my $value (@ {$ccvm_req->content}) { + push @search_field_values, [$value->id, $value->code, $value->value]; + } + + return { status => "success", data => [@search_field_values] }; +} + +# Save a record to rbdigital.search_fields_mappings table that holds the mapping information for Evergreen/Pines and Third party vendor search field values +sub save_search_field_values { + my ($self, $authtoken, $search_fields_id, $evergreen_field_code, $evergreen_field_value, $digital_services_field_code, $digital_services_field_value) = @_; + my $session_id = $self->{session_id}; + + $logger->info("EbookAPI: Saving Search Field Mappings"); + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + + # First, Check if it is not a duplicate entry and if it is strictly one to one mapping + my $ebook_rsfm_req = $pcrud->request('open-ils.pcrud.search.ebook_rsfm.atomic', "ANONYMOUS", + { + search_fields_id => $search_fields_id, + digital_services_field_code => $digital_services_field_code + } + )->recv(); + + if ($ebook_rsfm_req && defined($ebook_rsfm_req->content->[0])) { + if ($ebook_rsfm_req->content->[0]->evergreen_field_code eq $evergreen_field_code) { + return { status => "failed", message => "Duplicate entry found! Please try selecting different values" }; + } + + return { status => "failed", message => "The value '$digital_services_field_value' already exists! Please try selecting different values." }; + } + + # Now, Save an entry to rbdigital.search_field_mappings table + my $xact; + + eval { + $xact = $pcrud->request( + 'open-ils.pcrud.transaction.begin', + $authtoken + )->gather(1); + }; + + if ($@) { + undef($xact); + } + + if ($xact) { + my $tmpl_rec = Fieldmapper::rbdigital::search_field_mappings->new; + $tmpl_rec->search_fields_id($search_fields_id); + $tmpl_rec->evergreen_field_code($evergreen_field_code); + $tmpl_rec->evergreen_field_value($evergreen_field_value); + $tmpl_rec->digital_services_field_code($digital_services_field_code); + $tmpl_rec->digital_services_field_value($digital_services_field_value); + + $pcrud->request('open-ils.pcrud.create.ebook_rsfm', $authtoken, $tmpl_rec )->gather(1); + + $xact = $pcrud->request( + 'open-ils.pcrud.transaction.commit', + $authtoken + )->gather(1); + + $ebook_rsfm_req = $pcrud->request('open-ils.pcrud.search.ebook_rsfm.atomic', "ANONYMOUS", + { + search_fields_id => $search_fields_id, + evergreen_field_code => $evergreen_field_code, + evergreen_field_value => $evergreen_field_value, + digital_services_field_code => $digital_services_field_code + } + )->recv(); + + $logger->info("EbookAPI: Record saved to search_fields_mappings table"); + + return { status => "success" , data => {id => $ebook_rsfm_req->content->[0]->id} }; + } + + return { status => "failed", message => "Unknown error occurred! Please try to save the fields again." }; +} + +# Remove a record from rbdigital.search_fields_mappings table +sub remove_search_field_mappings { + my ($self, $authtoken, $id) = @_; + my $session_id = $self->{session_id}; + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + $pcrud->request('open-ils.pcrud.transaction.begin',$authtoken)->gather(1); + + my $search_fields_req = $pcrud->request('open-ils.pcrud.delete.ebook_rsfm', $authtoken, $id)->recv(1); + + $pcrud->request('open-ils.pcrud.transaction.commit',$authtoken)->gather(1); + + return { status => "success" }; +} +1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm old mode 100644 new mode 100755 index 64d10f820f..4a8b56ab97 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm @@ -27,6 +27,10 @@ use OpenILS::WWW::EGCatLoader::Container; use OpenILS::WWW::EGCatLoader::SMS; use OpenILS::WWW::EGCatLoader::Register; +# EBook API sub-modules +use OpenILS::WWW::EGCatLoader::RBDigitalSearch; +use OpenILS::WWW::EGCatLoader::RBDigitalRecord; + my $U = 'OpenILS::Application::AppUtils'; use constant COOKIE_SES => 'ses'; @@ -159,6 +163,13 @@ sub load { return $self->load_temp_warn_post if $path =~ m|opac/temp_warn/post|; return $self->load_temp_warn if $path =~ m|opac/temp_warn|; + # Load EbookAPI modules + return $self->load_rbdigital_advanced if $path =~ m|opac/ebook_api/rbdigital/advanced|; + return $self->load_rbdigital_manage_search_fields if $path =~ m|opac/ebook_api/rbdigital/manage_search_fields|; + return $self->load_rbdigital_results if $path =~ m|opac/ebook_api/rbdigital/results|; + return $self->load_rbdigital_print_record if $path =~ m|opac/ebook_api/rbdigital/record/print|; + return $self->load_rbdigital_record if $path =~ m|opac/ebook_api/rbdigital/record/\d|; + # ---------------------------------------------------------------- # Everything below here requires SSL # ---------------------------------------------------------------- @@ -265,7 +276,7 @@ sub load_simple { my ($self, $page) = @_; $self->ctx->{page} = $page; $self->ctx->{search_ou} = $self->_get_search_lib(); - + return Apache2::Const::OK; } @@ -424,9 +435,31 @@ sub load_common { return $rows; }; + $self->load_rbdigital_common(); + return Apache2::Const::OK; } +sub load_rbdigital_common { + my $self = shift; + + # Load vendor tabs for authenticated patrons only + if (defined($self->editor->requestor)) { + # Call sub to get the session id + my $session_id = _get_ebook_session_id(); + + # Send back rbdigital_patron_id + $self->ctx->{rbdigital_patron_id} = _get_patron_id($self->editor->authtoken, $session_id); + $logger->info('EbookAPI: Got patron id: '. $self->ctx->{rbdigital_patron_id}); + + # Call sub to get all the vendors to be displayed on the UI + $self->ctx->{vendors} = _get_authorized_vendors($self->editor->authtoken, $session_id); + } else { + $self->ctx->{rbdigital_patron_id} = 0; + $self->ctx->{vendors} = []; + } +} + sub update_dashboard_stats { my $self = shift; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm old mode 100644 new mode 100755 index 9b311ec356..ca10071cb5 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm @@ -2149,6 +2149,13 @@ sub load_myopac_main { pub => 't' }) ); + + # Call sub to get the session id + my $session_id = _get_ebook_session_id(); + + # Call sub to get all the vendors to be displayed on the UI + $self->ctx->{vendors} = _get_authorized_vendors($self->editor->authtoken, $session_id); + return $self->prepare_fines($limit, $offset) || Apache2::Const::OK; } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/RBDigitalRecord.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/RBDigitalRecord.pm new file mode 100755 index 0000000000..414e9c9c77 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/RBDigitalRecord.pm @@ -0,0 +1,162 @@ +package OpenILS::WWW::EGCatLoader; +use strict; use warnings; +use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_GONE HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST HTTP_NOT_FOUND); +use OpenSRF::Utils::Logger qw/$logger/; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Application::AppUtils; +use Net::HTTP::NB; +use IO::Select; +my $U = 'OpenILS::Application::AppUtils'; + +our $ac_types = ['toc', 'anotes', 'excerpt', 'summary', 'reviews']; + +# context additions: +# record : bre object +sub load_rbdigital_record { + my $self = shift; + my %kwargs = @_; + my $ctx = $self->ctx; + $ctx->{page} = 'record'; + $self->ctx->{badge_scores} = []; + + $logger->info('EbookAPI: RBDigitalRecord started'); + + $self->timelog("load_rbdigital_record() began"); + + my $isbn = $ctx->{page_args}->[0]; + + $logger->info('EbookAPI: RBDigitalRecord started for ISBN: '. $isbn); + return Apache2::Const::HTTP_BAD_REQUEST + unless $isbn and $isbn =~ /^\d+$/; + + # Call sub to get the session id + my $session_id = _get_ebook_session_id(); + + $ctx->{title_info} = _get_ebook_title_info($session_id, $isbn); + + $ctx->{title_summary} = {}; + # Get content summary + if ($self->cgi->param('expand') eq 'summaryplus') { + $ctx->{title_summary} = _get_ebook_title_summary($session_id, $isbn); + } + + # Get recommended books + $ctx->{recommended_books} = {}; + if ($self->cgi->param('expand') eq 'recommendations') { + my $media_format = $self->cgi->param('fi:search_format') || 'ebook'; + my $page_index = 0; + my $page_size = 10; + $ctx->{recommended_books} = _get_book_recommendations($self->editor->authtoken, $session_id, $isbn, $media_format, $page_index, $page_size); + } + + $self->ctx->{copy_depth} = $self->cgi->param('copy_depth'); + + my $copy_limit = int($self->cgi->param('copy_limit') || 10); + my $copy_offset = int($self->cgi->param('copy_offset') || 0); + + $self->get_staff_search_settings; + if ($ctx->{staff_saved_search_size}) { + $ctx->{saved_searches} = ($self->staff_load_searches)[1]; + } + $self->timelog("past staff saved searches"); + + # Check for user and load lists and prefs + if ($self->ctx->{user}) { + $self->_load_lists_and_settings; + $self->timelog("load user lists and settings"); + } + + return Apache2::Const::OK; +} + +# Get ebook title info +sub _get_ebook_title_info { + my ($session_id, $isbn) = @_; + + $logger->info('EbookAPI: RBDigitalRecord _get_ebook_title_info'); + my $ebook_api = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $api_request = $ebook_api->request('open-ils.ebook_api.title.details', $session_id, $isbn); + + if ($api_request) { + return $api_request->recv()->{content}->{content}; + } + $logger->info('EbookAPI: RBDigitalRecord _get_ebook_title_info - No data found'); + return undef; +} + +# Get ebook summary +sub _get_ebook_title_summary { + my ($session_id, $isbn) = @_; + + $logger->info('EbookAPI: RBDigitalRecord _get_ebook_title_summary'); + my $ebook_api = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $api_request = $ebook_api->request('open-ils.ebook_api.title.summary', $session_id, $isbn); + + if ($api_request) { + return $api_request->recv()->{content}->{content}; + } + $logger->info('EbookAPI: RBDigitalRecord _get_ebook_title_summary - No data found'); + return undef; +} + +sub _get_book_recommendations { + my ($authtoken, $session_id, $isbn, $media_format, $page_index, $page_size) = @_; + + $logger->info('EbookAPI: RBDigitalRecord _get_book_recommendations'); + my $ebook_api = OpenSRF::AppSession->create('open-ils.ebook_api'); + $logger->info("EbookAPI: Parameters passing $session_id, $isbn, $media_format, $page_index, $page_size"); + + # $session_id, $isbn, $media_format, $page_index, $page_size + my $api_request = $ebook_api->request('open-ils.ebook_api.get_book_recommendations', $authtoken, $session_id, $isbn, $media_format, $page_index, $page_size); + + if ($api_request) { + return $api_request->recv()->{content}->{content}; + } + + $logger->info('EbookAPI: RBDigitalRecord _get_book_recommendations - No data found'); + return undef; + +} + +# collect IDs and info on the search that lead to this details page +# If no search query, etc is present, we leave ctx.search_result_index == -1 +sub fetch_rbdigital_related_search_info { + my $self = shift; + my $rec_id = shift; + my $ctx = $self->ctx; + $ctx->{search_result_index} = -1; + + $self->load_rbdigital_rresults(internal => 1); + + my @search_ids = @{$ctx->{ids}}; + return unless @search_ids; + + for my $idx (0..$#search_ids) { + if ($search_ids[$idx] == $rec_id) { + $ctx->{prev_search_record} = $search_ids[$idx - 1] if $idx > 0; + $ctx->{next_search_record} = $search_ids[$idx + 1]; + $ctx->{search_result_index} = $idx; + last; + } + } + + $ctx->{first_search_record} = $search_ids[0]; + $ctx->{last_search_record} = $search_ids[-1]; +} + +sub load_rbdigital_print_record { + my $self = shift; + + my $rec_id = $self->ctx->{page_args}->[0] + or return Apache2::Const::HTTP_BAD_REQUEST; + + $self->{ctx}->{bre_id} = $rec_id; + $self->{ctx}->{printable_record} = $U->simplereq( + 'open-ils.search', + 'open-ils.search.biblio.record.print', $rec_id); + + return Apache2::Const::OK; +} + +1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/RBDigitalSearch.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/RBDigitalSearch.pm new file mode 100755 index 0000000000..71caf30da4 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/RBDigitalSearch.pm @@ -0,0 +1,492 @@ +package OpenILS::WWW::EGCatLoader; +use strict; +use warnings; +use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST); +use OpenSRF::Utils::Logger qw/$logger/; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Application::AppUtils; +use OpenSRF::Utils::JSON; +use Data::Dumper; +$Data::Dumper::Indent = 0; +my $U = 'OpenILS::Application::AppUtils'; + +use constant EBOOK_API_VENDOR => 'rbdigital'; +use constant EBOOK_API_OU => 1; + +sub load_rbdigital_advanced { + my $self = shift; + $self->ctx->{search_filters} = {}; + $logger->info('EbookAPI: RBDigitalSearch calling an API to get search filters'); + + # Call sub to get the session id + my $session_id = _get_ebook_session_id(); + + my $ebook_api = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $api_request = $ebook_api->request('open-ils.ebook_api.get_search_filters', $self->editor->authtoken, $session_id); + + if ($api_request) { + $self->ctx->{search_filters} = $api_request->recv()->{content}->{content}; + } + + #Build an array of mapped search fields to be used later by the UI code + $self->ctx->{mapped_search_fields} = _get_search_fields(); + + my %mapped_fields_array; + + my %mapped_search_field_values; + # Iterate over list of values and build an array for JS (Only Dropdown Field Types) + foreach my $value (@ {$self->ctx->{mapped_search_fields}}) { + if ($value->field_type == 'dropdown') { + $mapped_search_field_values{lc($value->evergreen_field)} = [lc($value->digital_services_field), _get_search_field_mappings($self->editor->authtoken, $session_id, $value->id)]; + } + + $mapped_fields_array{lc($value->evergreen_field)} = lc ($value->digital_services_field); + } + + $self->ctx->{mapped_search_field_values} = \%mapped_search_field_values; + + $self->ctx->{mapped_fields_array} = \%mapped_fields_array; + + $logger->info('EbookAPI: RBDigitalRecord load_rbdigital_advanced'); + + return Apache2::Const::OK; +} + +sub load_rbdigital_manage_search_fields { + my $self = shift; + + $logger->info('EbookAPI: RBDigitalSearch calling an API to search fields for mapping with evergreen'); + + # Call sub to get the session id + my $session_id = _get_ebook_session_id(); + + my $ebook_api = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $api_request = $ebook_api->request('open-ils.ebook_api.get_search_filters', $self->editor->authtoken, $session_id); + if ($api_request) { + $self->ctx->{search_filters} = $api_request->recv()->{content}->{content}; + } + + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + + #Get Evergreen Search Fields + my $evergreen_text_search_field_req = $pcrud->request('open-ils.pcrud.search.cmc.atomic',"ANONYMOUS", + { + name => {"!=" => undef} + }, + { + order_by => { cmc => 'name ASC' } + } + )->recv(); + + $self->ctx->{eg_search_input_fields} = $evergreen_text_search_field_req->{content}; + + $self->ctx->{mapped_search_fields} = _get_search_fields(); + + $logger->info('EbookAPI: RBDigitalRecord load_rbdigital_advanced_manage_search_fields - No data found'); + + return Apache2::Const::OK; +} + +# context additions: +# page_size +# hit_count +# records : list of bre's and copy-count objects +sub load_rbdigital_results { + my $self = shift; + my %args = @_; + my $internal = $args{internal}; + my $cgi = $self->cgi; + my $ctx = $self->ctx; + my $e = $self->editor; + my $record_ids; + my $results; + my $session_id; + $logger->info('EbookAPI: RBDigitalSearch started'); + + return $self->redirect_auth unless $self->editor->requestor; + $logger->info('EbookAPI: RBDigitalSearch User is authenticated.'); + + # Call sub to get the session id + $session_id = _get_ebook_session_id(); + + # Read all the parameters passed + my $auth_session = $cgi->cookie('ses') || $cgi->param('ses'); + my $media_format = $cgi->param('fi:search_format') || ''; + my $value_to_search = $cgi->param('query') || ''; + my $page_index = $cgi->param('page') || 0; + my $search_type = $cgi->param('qtype') || ''; + my @facets = (); + my @search_inputs = (); + my $page_size = $cgi->param('limit') || 10; + my $library_location = $cgi->param('locg') || ''; + my $sort_by = ''; + my $sort_order = ''; + + # Prepage ctx structure + $ctx->{page} = 'rresult' unless $internal; + $ctx->{ids} = []; + $ctx->{records} = []; + $ctx->{search_facets} = {}; + $ctx->{hit_count} = 0; + $ctx->{is_meta} = 0; + $ctx->{page_size} = $page_size; + $ctx->{search_page} = $page_index; + + if ($cgi->param('facet')) { + for my $item ($cgi->param('facet')) { + push @{facets}, $item; + } + } + + $logger->info('EbookAPI: RBDigitalSearch: facet: '.scalar(@facets)); + + # filters + my $filters = []; + # my $query; + # foreach (grep /^fi:/, $cgi->param) { + # /:(-?\w+)$/ or next; + # my $term = join(",", $cgi->param($_)); + # $query .= " $1($term)" if length $term; + # } + + # Build array with Search Inputs + # Title (if any) + if (defined($cgi->param('query_title'))) { + push @{search_inputs}, 'title_' . $cgi->param('query_title'); + } + + # Author (if any) + if (defined($cgi->param('query_author')) && $cgi->param('query_author') ne '') { + push @{search_inputs}, 'author_' . $cgi->param('query_author'); + } + + # Narrator (if any) + if (defined($cgi->param('query_narrator')) && $cgi->param('query_narrator') ne '') { + push @{search_inputs}, 'narrator_' . $cgi->param('query_narrator'); + } + + # Series (if any) + if (defined($cgi->param('query_series')) && $cgi->param('query_series') ne '') { + push @{search_inputs}, 'series_' . $cgi->param('query_series'); + } + + # Publisher (if any) + if (defined($cgi->param('query_publisher')) && $cgi->param('query_publisher') ne '') { + push @{search_inputs}, 'publisher_' . $cgi->param('query_publisher'); + } + + # Genre (if any) + if (defined($cgi->param('item_genre')) && $cgi->param('item_genre') ne '') { + push @{search_inputs}, 'genre_' . $cgi->param('item_genre'); + } + + # Audience (if any) + if (defined($cgi->param('item_audience')) && $cgi->param('item_audience') ne '') { + push @{search_inputs}, 'audience_' . $cgi->param('item_audience'); + } + + # Availability (if any) + if (defined($cgi->param('item_availability')) && $cgi->param('item_availability') ne '') { + push @{search_inputs}, 'availability_' . $cgi->param('item_availability'); + } + + # Published Date (if any) + if (defined($cgi->param('item_published')) && $cgi->param('item_published') ne '') { + push @{search_inputs}, 'published_' . $cgi->param('item_published'); + } + + # Date Added (if any) + if (defined($cgi->param('item_date-added')) && $cgi->param('item_date-added') ne '') { + push @{search_inputs}, 'date-added_' . $cgi->param('item_date-added'); + } + + # Fiction (if any) + if (defined($cgi->param('item_isfiction')) && $cgi->param('item_isfiction') ne '') { + push @{search_inputs}, 'isfiction_' . $cgi->param('item_isfiction'); + } + + # Language (if any) + if (defined($cgi->param('item_language')) && $cgi->param('item_language') ne '') { + push @{search_inputs}, 'language_' . $cgi->param('item_language'); + } + + # Digital Rights (if any) + if (defined($cgi->param('item_hasdrm')) && $cgi->param('item_hasdrm') ne '') { + push @{search_inputs}, 'hasdrm_' . $cgi->param('item_hasdrm'); + } + + # Determine sort by and sort order + if (defined($cgi->param('sort')) && $cgi->param('sort') ne '') { + ($sort_by, $sort_order) = _handle_sort($cgi->param('sort')); + # If the sort request is by pubdate, change it to releasedate + if ($sort_by eq 'pubdate') { + $sort_by = 'releasedate'; + } + } + + $logger->info('EbookAPI: RBDigitalSearch Checking media type'); + # Check if RBDigital can perform requested query + if ($media_format eq 'eaudio' || $media_format eq 'ebook' || $media_format eq 'book' || $media_format eq 'music' || $media_format eq '') { + $logger->info('EbookAPI: RBDigitalSearch Invoking search method'); + + $results = _search ($self->editor->authtoken, $session_id, + $value_to_search, $search_type, $media_format, $library_location, \@facets, \@search_inputs, + $sort_by, $sort_order, $page_index, $page_size); + + $record_ids = _get_record_ids(@{$results->{content}->{content}->{items}}); + $logger->debug('EbookAPI: RBDigitalSearch Record Ids: '. join(" ",@{$record_ids}) ); + + $logger->info('EbookAPI: RBDigitalSearch Basic Search Record Count: '. scalar @$record_ids); + + # Add values to returned query structure for GUI to process + $ctx->{ids} = $record_ids; + $ctx->{records} = $results->{content}->{content}->{items}; + $ctx->{hit_count} = $results->{content}->{content}->{resultSetCount}; + $ctx->{superpage} = $page_index; + $ctx->{superpage_size} = 10; + $ctx->{pagable_limit} = $page_size; + $ctx->{query_struct} = {}; + $ctx->{canonicalized_query} = ''; + $ctx->{search_summary} = ''; + $ctx->{search_facets} = _get_search_facets(@{$results->{content}->{content}->{filters}}); + } + + return Apache2::Const::OK; +} + +sub _is_authorized { + my ($authtoken, $session_id) = @_; + my $ebookapi_session = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $vendors_req = $ebookapi_session->request('open-ils.ebook_api.is_authorized_patron',$authtoken, $session_id); + + return $vendors_req->recv->content; +} + +sub _get_authorized_vendors { + my ($authtoken, $session_id) = @_; + my $ebookapi_session = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $vendors_req = $ebookapi_session->request('open-ils.ebook_api.get_authorized_vendors',$authtoken, $session_id); + + return $vendors_req->recv->content; +} + +sub _get_patron_id { + my ($authtoken, $session_id) = @_; + + $logger->info('EbookAPI: RBDigitalRecord _get_patron_id'); + my $ebook_api = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $api_request = $ebook_api->request('open-ils.ebook_api.get_patron_id', $authtoken, $session_id,0); + + if ($api_request) { + return $api_request->recv->content; + } + + return 1; +} + +sub _get_ebook_session_id { + # Create an instance of EbookAPI + my $ebook_api = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $session_id_req = $ebook_api->request( + 'open-ils.ebook_api.start_session', EBOOK_API_VENDOR, EBOOK_API_OU); + my $session_id = $session_id_req->recv->content; + + return $session_id; +} + +sub _search { + my ($authtoken, $session_id, + $value_to_search, $search_type, + $media_format, $library_location, $facets, $search_inputs, + $sort_by, $sort_order, + $page_index, $page_size) = @_; + my $record_ids; + my $search_results; + + try { + $logger->info('EbookAPI: RBDigitalSearch Calling search API'); + + my $method = "open-ils.ebook_api.basic_search"; + + my $ebookapi_session = OpenSRF::AppSession->create('open-ils.ebook_api'); + if ($value_to_search eq '' && scalar(@$search_inputs) == 0) { + # Nothing to search + $logger->info('EbookAPI: RBDigitalSearch No search value was entered by user.'.$value_to_search); + return Apache2::Const::OK; + } + + #$self->timelog("Firing off the basic search from RBDigital library"); + my $basic_search_req = $ebookapi_session->request($method, + $authtoken, $session_id, + $value_to_search, $search_type, + $media_format, $library_location, \@$facets, \@$search_inputs, + $sort_by, $sort_order, + $page_index, $page_size); + + $search_results = $basic_search_req->recv(); + $logger->debug('EbookAPI: RBDigitalSearch search result: '.Dumper($search_results)); + + if (defined($search_results->{content}->{is_success}) && $search_results->{content}->{is_success} eq '1') { + $logger->error('EbookAPI: RBDigitalSearch Basic Search API call was successful'); + } else { + $logger->error('EbookAPI: RBDigitalSearch Basic Search API call failed'); + return Apache2::Const::OK; + } + + #$self->timelog("Returned from the basic search from RBDigital library"); + $logger->info('EbookAPI: RBDigitalSearch Done calling search API'); + + } catch Error with { + my $err = shift; + $logger->error("EbookAPI: basic search error: $err"); + $search_results = {content => {}}; + + }; + + return $search_results; +} + +# Converts string 'pubdate.desc' or authorsort to author, desc +sub _handle_sort { + my ($sort_value) = @_; + my $sort_by; + my $sort_order; + my $temp; + + ($sort_by, $sort_order) = split (/\./, $sort_value); + + if ($sort_order eq 'descending') { + $sort_order = 'desc'; + } else { + $sort_order = 'asc'; + } + + ($sort_by, $temp) = split('sort', $sort_by); + + return ($sort_by, $sort_order); +} + +sub _get_record_ids { + my @items = @_; + my $record_ids = []; #declare array reference + my $item; + + for my $item( @items ) { + push @{$record_ids}, "'$item->{item}->{id}'"; + }; + + # return array of record ids + return $record_ids; +} + +# Group the facets by tokenlabel +sub _get_search_facets { + my @filters = @_; + my $facets = {}; + + for my $filter ( @filters ) { + if ($filter->{count} > 0) { + #split token value by '-', change case and then add it to value + $filter->{displayLabel} = _scrub_name($filter->{tokenValue}); + + if ($filter->{tokenLabel} eq 'genre') { + push @{$facets->{genre}}, [$filter]; + } elsif ($filter->{tokenLabel} eq 'audience') { + push @{$facets->{audience}}, [$filter]; + } elsif ($filter->{tokenLabel} eq 'isfiction') { + push @{$facets->{fiction}}, [$filter]; + } elsif ($filter->{tokenLabel} eq 'hasdrm') { + push @{$facets->{digitalRights}}, [$filter]; + } elsif ($filter->{tokenLabel} eq 'availability') { + push @{$facets->{availability}}, [$filter]; + } elsif ($filter->{tokenLabel} eq 'language') { + push @{$facets->{language}}, [$filter]; + } + } + + }; + + # return hash reference + return $facets; +} + +# Call this method to split the name 'the-quick-brown' into The Quick Brown +sub _scrub_name { + my $name = shift; + my $return_value = ''; + my @value_array = split('-',$name); + + for my $word (@value_array) { + $return_value = $return_value . ucfirst($word).' '; + } + + return rtrim($return_value); +} + + +# Perl trim function to remove whitespace from the start and end of the string +sub trim($) +{ + my $string = shift; + $string =~ s/^\s+//; + $string =~ s/\s+$//; + return $string; +} +# Left trim function to remove leading whitespace +sub ltrim($) +{ + my $string = shift; + $string =~ s/^\s+//; + return $string; +} +# Right trim function to remove trailing whitespace +sub rtrim($) +{ + my $string = shift; + $string =~ s/\s+$//; + return $string; +} + +# returns a list of records from search_field_mappings table +sub _get_search_field_mappings { + my ($authtoken, $session_id, $search_fields_id) = @_; + my $ebook_api = OpenSRF::AppSession->create('open-ils.ebook_api'); + my $api_request = $ebook_api->request('open-ils.ebook_api.search_field_mappings.retrieve', $authtoken, $session_id, $search_fields_id); + + if ($api_request) { + return $api_request->recv()->{content}->{data}; + } + + return []; +} + +# returns a list of records from search_fields table +sub _get_search_fields { + my $vendor_key = 'rbdigital'; + my $pcrud = OpenSRF::AppSession->create('open-ils.pcrud'); + $pcrud->connect(); + my $digital_services_req = $pcrud->request('open-ils.pcrud.search.ebook_rds.atomic', "ANONYMOUS", + { vendor_key => $vendor_key } + )->recv(); + + if ($digital_services_req && defined($digital_services_req->content->[0])) { + + #Get RBDigital Search fields + my $mapped_search_field_req = $pcrud->request('open-ils.pcrud.search.ebook_rsf.atomic', "ANONYMOUS", + { + #TODO: Replace the below by join statement + digital_services_id => $digital_services_req->content->[0]->id + + } + ); + + return $mapped_search_field_req->recv()->{content}; + + } + + return []; +} + +1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm old mode 100644 new mode 100755 index c21566a628..ee3f65c922 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm @@ -350,7 +350,8 @@ sub load_rresults { my $cgi = $self->cgi; my $ctx = $self->ctx; my $e = $self->editor; - + my $session_id; + # 1. param->metarecord : view constituent bib records for a metarecord # 2. param->modifier=metabib : perform a metarecord search my $metarecord = $ctx->{metarecord} = $cgi->param('metarecord'); diff --git a/Open-ILS/src/perlmods/live_t/100-ebookapi-rbdigital.t b/Open-ILS/src/perlmods/live_t/100-ebookapi-rbdigital.t new file mode 100755 index 0000000000..279711d725 --- /dev/null +++ b/Open-ILS/src/perlmods/live_t/100-ebookapi-rbdigital.t @@ -0,0 +1,129 @@ +#!perl + +use Test::More tests => 8; + +diag("Tests Ebook API: RBDigital Books"); + +use strict; use warnings; + +use OpenILS::Application::EbookAPI; +use OpenSRF::AppSession; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Utils::TestUtils; +use Scalar::Util 'blessed'; +use Data::Dumper; + +# ------------------------------------------------------------ +# 1. Set up test environment. +# ------------------------------------------------------------ + +use constant EBOOK_API_VENDOR => 'rbdigital'; +use constant EBOOK_API_OU => 1; + +# Patrons. +use constant EBOOK_API_PATRON_USERNAME => 'egadmin'; +use constant EBOOK_API_PATRON_PASSWORD => 'password'; +use constant BARCODE => 'e29bc74e9d13243c861c1d209e9a2e20'; + +my $script = OpenILS::Utils::TestUtils->new(); +$script->bootstrap; + + +# Authenticate the user +$script->authenticate({ + username => EBOOK_API_PATRON_USERNAME, + password => EBOOK_API_PATRON_PASSWORD, + type => 'staff'}); + +my $authtoken = $script->authtoken; + +print "User Authenticated: Auth Token: $authtoken\n"; + +my $editor = $script->editor(authtoken=>$script->authtoken); +$editor->xact_begin; + +ok( + $authtoken, + 'Have an authtoken' +); +is( + $script->authtime, + 14400, + 'Default authtime for staff login is 7200 seconds. 14400 seconds are equal to 4 hours' +); + +my $cached_obj = $script->cache()->get_cache("oils_auth_$authtoken"); + +ok( + ref $cached_obj, + 'Can retrieve authtoken from memcached' +); + +# Create an instance of EbookAPI +my $ebook_api = $script->session('open-ils.ebook_api'); + +# ------------------------------------------------------------ +# 2. Sessions. +# ------------------------------------------------------------ + +# Initiate a new EbookAPI session and get a session ID. +# Returns undef unless a new session was created. +my $session_id_req = $ebook_api->request( + 'open-ils.ebook_api.start_session', EBOOK_API_VENDOR, EBOOK_API_OU); +my $session_id = $session_id_req->recv->content; +ok($session_id, 'Initiated an EbookAPI session: '.$session_id); + +# Check that an EbookAPI session exists matching our session ID. +my $ck_session_id_req = $ebook_api->request( + 'open-ils.ebook_api.check_session', $session_id, EBOOK_API_VENDOR, EBOOK_API_OU); +my $ck_session_id = $ck_session_id_req->recv->content; +ok($ck_session_id eq $session_id, 'Validated existing EbookAPI session'); + + +# ------------------------------------------------------------ +# 3. Call Recorded Books API to fetch patron_id +# ------------------------------------------------------------ +my $get_patron_id_req = $ebook_api->request( + 'open-ils.ebook_api.get_patron_id', $authtoken, $session_id, 1); +my $patronid = $get_patron_id_req->recv->content; + +ok( + $patronid, + 'Patron ID Received: '.$patronid +); + +# ------------------------------------------------------------ +# 4. Perform Basic Search +# ------------------------------------------------------------ + +my $value_to_search = 'a'; +my $search_type = 'keyword', +my $media_format = 'eaudio'; +my $library_location = 1, +my $page_index = 0; +my $page_size = 1; +my $sort_by = 'title'; +my $sort_order ='asc'; +my @facets = (); + +my $basic_search_req = $ebook_api->request( + 'open-ils.ebook_api.basic_search', $authtoken, $session_id, + $value_to_search, $search_type, + $media_format, $library_location, \@facets, + $sort_by, $sort_order, + $page_index, $page_size); + +#returns an object of OpenSRF::DomainObject::oilsResult class +my $search_results = $basic_search_req->recv; +my $output = "Call Failed!"; + +if (defined($search_results->{content}->{is_success}) && $search_results->{content}->{is_success} eq '1') { + $output = "API Call Succeeded\n"; +} + +ok( + $search_results, + 'Search Results: '. $output +); + + diff --git a/Open-ILS/src/sql/Pg/upgrade/1030.data.org-setting.ebook-api-rbdigital.sql b/Open-ILS/src/sql/Pg/upgrade/1030.data.org-setting.ebook-api-rbdigital.sql new file mode 100755 index 0000000000..fab2dc9306 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/1030.data.org-setting.ebook-api-rbdigital.sql @@ -0,0 +1,63 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('1030', :eg_version); + +INSERT INTO config.org_unit_setting_type + (name, label, description, grp, datatype) +VALUES ( + 'ebook_api.rbdigital.base_uri', + oils_i18n_gettext( + 'ebook_api.rbdigital.base_uri', + 'RBDigital Base URI', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'ebook_api.rbdigital.base_uri', + 'Base URI for RBDigital API (defaults to https://api.rbdigital.com/v1). Using HTTPS here is strongly encouraged.', + 'coust', + 'description' + ), + 'ebook_api', + 'string' +),( + 'ebook_api.rbdigital.library_id', + oils_i18n_gettext( + 'ebook_api.rbdigital.library_id', + 'RBDigital Library ID', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'ebook_api.rbdigital.library_id', + 'Identifier assigned to this library by RBDigital', + 'coust', + 'description' + ), + 'ebook_api', + 'string' +),( + 'ebook_api.rbdigital.basic_token', + oils_i18n_gettext( + 'ebook_api.rbdigital.basic_token', + 'RBDigital Basic Token', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'ebook_api.rbdigital.basic_token', + 'Basic token for client authentication with RBDigital API (supplied by RBDigital)', + 'coust', + 'description' + ), + 'ebook_api', + 'string' +); + +INSERT INTO actor.org_unit_setting (org_unit, name, value) + VALUES + (1, 'ebook_api.rbdigital.base_uri', '"api.rbdigital.com"'), + (1, 'ebook_api.rbdigital.basic_token', '"BF8DD41C-B286-4F60-9FF5-F4C5B3E57A11"'), + (1, 'ebook_api.rbdigital.library_id', '"3925"'); +COMMIT; + diff --git a/Open-ILS/src/templates/opac/css/style.css.tt2 b/Open-ILS/src/templates/opac/css/style.css.tt2 old mode 100644 new mode 100755 index eff36cdb88..6fecb4f294 --- a/Open-ILS/src/templates/opac/css/style.css.tt2 +++ b/Open-ILS/src/templates/opac/css/style.css.tt2 @@ -1472,6 +1472,11 @@ div.result_table_utils_cont { max-width:300px; } +#vendor_tabs { + background-color: [% css_colors.primary_fade %]; + padding-top:5px; +} + #myopac_tabs, #adv_search_parent, #fines_payments_wrapper { background-color: [% css_colors.primary_fade %]; padding-top:5px; diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/advanced.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/advanced.tt2 new file mode 100755 index 0000000000..e9b61a3768 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/advanced.tt2 @@ -0,0 +1,44 @@ +[%- PROCESS "opac/parts/header.tt2"; + WRAPPER "opac/parts/base.tt2"; + vendor_key = 'rbdigital'; + ebook_uri = ctx.opac_root _ '/ebook_api/rbdigital'; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Advanced Search"); + pane = CGI.param("pane") || "advanced"; + loc = ctx.search_ou; + + ctx.metalinks.push(''); +-%] +

[% l('Advanced Search') %]

+
+ +
+
+ [% l('Advanced Search') %] + + [%- IF ctx.user.super_user == 't'; %] + [% l('Manage Search Fields') %] + [%- END; %] +
+ +
+
+
+
+
+ [% IF pane == 'advanced' %] + [% INCLUDE "opac/ebook_api/rbdigital/parts/advanced/search.tt2" %] + [% END %] +
+
+
+
+[% END %] \ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/manage_search_fields.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/manage_search_fields.tt2 new file mode 100755 index 0000000000..cfd6ec6754 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/manage_search_fields.tt2 @@ -0,0 +1,146 @@ +[%- PROCESS "opac/parts/header.tt2"; + WRAPPER "opac/parts/base.tt2"; + vendor_key = 'rbdigital'; + ebook_uri = ctx.opac_root _ '/ebook_api/rbdigital'; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Advanced Search"); + pane = CGI.param("pane") || "advanced"; + loc = ctx.search_ou; + PROCESS "opac/parts/misc_util.tt2"; + PROCESS get_library; + ctx.metalinks.push(''); +-%] +

[% l('Advanced Search') %]

+
+ +
+
+ [% l('Advanced Search') %] + + [%- IF ctx.user.super_user == 't'; %] + [% l('Manage Search Fields') %] + [%- END; %] +
+ +
+
+
+
+
+
[% l("Map Search Fields") %]
+
+ +
+ + + + + + + + + + + + + + + + + + [% IF ctx.mapped_search_fields.size == 0; %] + + [% ELSE; %] + [% FOREACH item IN ctx.mapped_search_fields; %] + + + + + + + [% END; %] + [% END; %] + +
Evergreen Search FieldRBDigital Search FieldData Field TypeAction
No Data
[% item.evergreen_field FILTER ucfirst; %][% item.digital_services_field FILTER ucfirst; %][% item.field_type FILTER ucfirst; %] + Map Values + + Remove +
+
+
+
+[% END %] + +[% INCLUDE "opac/ebook_api/rbdigital/map_seach_field_values_modal.tt2"; %] + + \ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/map_seach_field_values_modal.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/map_seach_field_values_modal.tt2 new file mode 100755 index 0000000000..d123a0211d --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/map_seach_field_values_modal.tt2 @@ -0,0 +1,57 @@ + + \ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/advanced/global_row.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/advanced/global_row.tt2 new file mode 100755 index 0000000000..25fecc40ae --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/advanced/global_row.tt2 @@ -0,0 +1,134 @@ +[% + contains = CGI.param('contains'); + queries = CGI.param('query'); + bools = CGI.param('bool'); + qtypes = CGI.param('qtype'); + + # scalar.merge treats the scalar as a 1-item array + WHILE queries.size < rowcount; queries = queries.merge(['']); END; + WHILE bools.size < rowcount; bools = bools.merge(['and']); END; + WHILE qtypes.size < rowcount; qtypes = qtypes.merge(search.default_qtypes.${qtypes.size} ? [search.default_qtypes.${qtypes.size}] : ['keyword']); END; + + IF ctx.mapped_fields_array.size > 0; + FOR qtype IN qtypes; + c = contains.shift; + b = bools.shift; + q = queries.shift; + # We are only dealing with "contains" option + IF c == "contains"; + IF ctx.mapped_fields_array.$qtype == 'keyword'; + query_keyword = q; + ELSIF ctx.mapped_fields_array.$qtype == 'title'; + query_title = q; + ELSIF ctx.mapped_fields_array.$qtype == 'author'; + query_author = q; + ELSIF ctx.mapped_fields_array.$qtype == 'narrator'; + query_narrator = q; + ELSIF ctx.mapped_fields_array.$qtype == 'series'; + query_series = q; + ELSIF ctx.mapped_fields_array.$qtype == 'publisher'; + query_publisher = q; + END; + END; + END; + ELSE; + # Default if there is no mapping available + FOR qtype IN qtypes; + c = contains.shift; + b = bools.shift; + q = queries.shift; + + # We are only dealing with "contains" option + IF c == "contains"; + IF qtype == 'keyword'; + query_keyword = q; + ELSIF qtype == 'author'; + query_author = q; + ELSIF qtype == 'title'; + query_title = q; + END; + END; + END; + END; + + IF CGI.param('query_title') != ''; + query_title = CGI.param('query_title'); + END; + + IF CGI.param('query_author') != ''; + query_author = CGI.param('query_author'); + END; + + IF CGI.param('query_narrator') != ''; + query_narrator = CGI.param('query_narrator'); + END; + + IF CGI.param('query_series') != ''; + query_series = CGI.param('query_series'); + END; + + IF CGI.param('query_publisher') != ''; + query_publisher = CGI.param('query_publisher'); + END; +%] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/advanced/search.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/advanced/search.tt2 new file mode 100755 index 0000000000..c1279a99ce --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/advanced/search.tt2 @@ -0,0 +1,126 @@ +[% + PROCESS "opac/parts/misc_util.tt2"; + PROCESS get_library; + ebook_uri = ctx.opac_root _ '/ebook_api/rbdigital'; + + #Build an array of dropdown and their values based on URI keys + dropdown_items = {}; + FOREACH item IN ctx.mapped_search_field_values; + IF CGI.param('fi:' _ item.key).size > 0; + dropdown_value = item.value.0; + dropdown_items.$dropdown_value = {}; + FOREACH uri_field_value IN CGI.param('fi:' _ item.key); + FOREACH subitem IN item.value.1; + subitem_value = subitem.2; + digital_services_field_code = subitem.4; + IF subitem_value == uri_field_value; + dropdown_items.$dropdown_value.$digital_services_field_code = subitem_value; + END; + END; + END; + END; + END; +%] + +
+ + +
\ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/coded_value_selector.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/coded_value_selector.tt2 new file mode 100755 index 0000000000..54323ef951 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/coded_value_selector.tt2 @@ -0,0 +1,48 @@ + +[%- + # If caller passes a list of possible attribute types, + # search all until we find some values + + IF !attr.size; attr = [attr]; END; + all_values = []; + attr_class = ''; + FOR attr_class IN attr; + all_values = ctx.search_ccvm('ctype', attr_class, 'opac_visible', 't'); + IF all_values.size > 0; LAST; END; + END; + name = name || "fi:" _ attr_class; + id = id || attr_class _ "_selector"; + values = values || CGI.param(name); + IF size AND size < 1; size = all_values.size; END; +-%] + + + diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/file_formats.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/file_formats.tt2 new file mode 100755 index 0000000000..aaf08424ee --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/file_formats.tt2 @@ -0,0 +1,15 @@ +[% + # Build all_formats for image icon ebook/eaudio + format = {}; + all_formats = []; + format.label = 'E-audio'; + format.icon = ctx.media_prefix _'/images/format_icons/icon_format/eaudio.png' _ ctx.cache_key; + format.search_format = 'eaudio'; + all_formats.push(format); + + format = {}; + format.label = 'E-book'; + format.icon = ctx.media_prefix _'/images/format_icons/icon_format/ebook.png' _ ctx.cache_key; + format.search_format = 'ebook'; + all_formats.push(format); +%] \ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/filtersort.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/filtersort.tt2 new file mode 100755 index 0000000000..596c82d002 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/filtersort.tt2 @@ -0,0 +1,24 @@ + diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/item_parser.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/item_parser.tt2 new file mode 100755 index 0000000000..8d2043ca3a --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/item_parser.tt2 @@ -0,0 +1,112 @@ +[% + #Set the ebook_record_item before processing the item_parser + attrs = {}; + ebook_item = {}; + + vendor_search_uri = '/ebook_api/rbdigital/results'; + vendor_record_uri = '/ebook_api/rbdigital/record/'; + + vendor_record_url = mkurl(ctx.opac_root _ vendor_record_uri _ ebook_record_item.isbn,{},['expand','summaryplus','recommendations']); + + ebook_item.title = ebook_record_item.title; + ebook_item.isbn = ebook_record_item.isbn; + ebook_item.publisher = ebook_record_item.publisher.text; + ebook_item.shortDescription = ebook_record_item.shortDescription; + ebook_item.language = ebook_record_item.language; + ebook_item.authors = ""; + ebook_item.narrators = ""; + ebook_item.genres = ""; + ebook_item.series = ""; + ebook_item.format_label = ''; + ebook_item.pubdate = ebook_record_item.releasedDate || ''; + + ebook_item.image_medium = ''; + ebook_item.image_large = ''; + ebook_item.image_xl_url = ''; + ebook_item.image_xxl_url = ''; + ebook_item.mediaType = ''; + ebook_item.authors_detail = ''; + + # Check title is available for Checkout Service + ebook_item.isTitleAvailable = false; + IF item.interest.isAvailable == 'true'; + ebook_item.isTitleAvailable = true; + END; + + # Loop over AUTHORS array + FOREACH item IN ebook_record_item.authors; + IF item.text != ''; + author_search_url = mkurl(ctx.opac_root _ vendor_search_uri, {qtype => 'author', query => item.text}); + ebook_item.authors_detail = ebook_item.authors_detail _ + ''_ item.text _ ' (Author). '; + ebook_item.authors = ebook_item.authors _ item.text; + IF !loop.last(); + ebook_item.authors = ebook_item.authors _ ", "; + ebook_item.authors_detail = ebook_item.authors_detail _ ", "; + END; + END; + END; + + # Loop over Narrators array + FOREACH item IN ebook_record_item.narrators; + author_search_url = mkurl(ctx.opac_root _ vendor_search_uri, {qtype => 'author', query => item.text}); + ebook_item.authors_detail = ebook_item.authors_detail _ + ''_ item.text _ ' (Narrator). '; + ebook_item.narrators = ebook_item.narrators _ item.text; + IF !loop.last(); + ebook_item.narrators = ebook_item.narrators _ ", "; + END; + END; + + # Get Genre information + FOREACH item IN ebook_record_item.genres; + genre_search_url = mkurl(ctx.opac_root _ vendor_search_uri, {qtype => 'genre', query => item.token}); + ebook_item.genres = ebook_item.genres _ '' _ item.text _ ''; + IF !loop.last(); + ebook_item.genres = ebook_item.genres _ ", "; + END; + END; + + # Get Series information + series_search_url = mkurl(ctx.opac_root _ vendor_search_uri, {qtype => 'series', query => ebook_record_item.series.token}); + ebook_item.series = ebook_item.series _ '' _ ebook_record_item.series.text _ ''; + + # Loop over IMAGES array and get the medium image link + FOREACH pair IN ebook_record_item.images; + IF pair.name == 'medium'; + ebook_item.image_medium = pair.url; + ELSIF pair.name == 'large'; + ebook_item.image_large = pair.url; + ELSIF pair.name == 'x-large'; + ebook_item.image_xl_url = pair.url; + ELSIF pair.name == 'xx-large'; + ebook_item.image_xxl_url = pair.url; + END; + END; + + # Change the Ebook and Eaudio media type to display native icons + IF ebook_record_item.mediaType == 'eAudio'; + ebook_item.format_label = 'E-audio'; + ELSIF ebook_record_item.mediaType == 'eBook'; + ebook_item.format_label = 'E-book'; + END; + + + # Get Extended Authors information + ebook_item.authors_extended = ''; + IF ebook_item.authors != ''; + ebook_item.authors_extended = ' / ' _ ebook_item.authors_extended _ ebook_item.authors; + END; + + # Get Extended Title information + ebook_item.title_extended = ebook_record_item.title _ ebook_item.authors_extended; + + # Get only year from publication/releasedDate + IF ebook_item.pubdate != ''; + ebook_item.pubdate = ebook_item.pubdate.substr(0, 4); + END; +%] \ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/body.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/body.tt2 new file mode 100755 index 0000000000..b8315db065 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/body.tt2 @@ -0,0 +1,22 @@ +[%- attrs = {marc_xml => ctx.marc_xml}; + PROCESS "opac/parts/misc_util.tt2"; + PROCESS get_marc_attrs args=attrs; + stop_parms = ['expand','cnoffset','copy_offset','copy_limit']; + ctx.record_attrs = attrs; # capture for JS + ctx.metalinks.push(''); + + vendor_search_uri = '/ebook_api/rbdigital/results'; + vendor_record_uri = '/ebook_api/rbdigital/record/'; +%] +
+[%- FOREACH link IN args.links.sameAs; %] + +[%- END; %] +[%- FOREACH link IN args.links.exampleOfWork; %] + +[%- END; %] + [%- INCLUDE "opac/ebook_api/rbdigital/parts/record/navigation.tt2" %] + [%- INCLUDE "opac/ebook_api/rbdigital/parts/record/summary.tt2" %] + [%- INCLUDE "opac/ebook_api/rbdigital/parts/record/navigation.tt2" %] +
+ diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/navigation.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/navigation.tt2 new file mode 100755 index 0000000000..601f073101 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/navigation.tt2 @@ -0,0 +1,5 @@ + diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/recommendations.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/recommendations.tt2 new file mode 100755 index 0000000000..6022df6537 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/recommendations.tt2 @@ -0,0 +1,62 @@ +[% + arrow_right = '►'; + arrow_down = '▼'; + recommendations = {}; + recommendations.name = 'recommendations'; + recommendations.label = 'Recommended Read'; + + join_operator = '&'; + IF vendor_record_url.search(";") == false AND vendor_record_url.search("&") == false; + join_operator = '?'; + END; + recommendations.link_show = vendor_record_url.search('expand=recommendations') ? + vendor_record_url : vendor_record_url _ join_operator _ 'expand=recommendations#recommendations'; + + recommendations.link_hide = mkurl('', {}, ['expand','recommendations']); +%] + +
+
+
+ +
+ [% IF CGI.param('expand') == 'recommendations' %] +
+
+
+ + [% book_recommendation = {}; + book_recommendation.image_url = ""; + book_display_count = 0; + FOREACH item IN ebook_record_item.recommended_books.items; + IF book_display_count == 10; + LAST; + END; + book_display_count = book_display_count+1; + book_recommendation.url = mkurl(ctx.opac_root _ vendor_record_uri _ item.item.isbn,{},[]); + FOREACH image IN item.item.images; + IF image.name == 'large'; + book_recommendation.image_url = image.url; + %] + + [% + LAST; + END; + + END; + END; + %] + + +
+
+
+ [% END; %] +
\ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/summary.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/summary.tt2 new file mode 100755 index 0000000000..c565ec423d --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/summary.tt2 @@ -0,0 +1,183 @@ +[% PROCESS "opac/parts/misc_util.tt2"; + USE ResolverResolver; + USE Dumper; + + PROCESS "opac/ebook_api/rbdigital/parts/file_formats.tt2"; + ctx.page_title = ctx.title_info.item.bookTitle.text | html; + + #Set the ebook_record_item before processing the item_parser + ebook_record_item = ctx.title_info.item; + ebook_record_item.summary = ctx.title_summary.summary; + ebook_record_item.recommended_books = ctx.recommended_books; + + PROCESS "opac/ebook_api/rbdigital/parts/item_parser.tt2"; +%] + + + + + +
+ +[%-# This holds the record summary information %] +
+
+

[% ebook_item.title_extended | html %]

+
+ [% ebook_item.authors_detail %] +
+
+
+ [% l('Image of item') %] + +
+
+
+ [% FOR format IN all_formats %] + [% IF format.label == ebook_item.format_label; %] + [% format.label | html %] + [% format.label | html %] + [% END; %] + [% END %] + +
+ [% IF ebook_item.isTitleAvailable == true; %] + + [% END; %] + + +
+ + [% IF !ctx.is_staff %] + +  [% l("Add to Wishlist") %] + + + [% END %] +
+ + [%- IF ctx.refworks.enabled == 'true' %] + [%- INCLUDE 'opac/parts/record/refworks.tt2' %] + [%- END %] + [% IF !ctx.is_staff %] + + [% END %] + [%- IF ctx.is_staff %] + + [%- END %] +
+
+
+ +[% +IF ebook_api.enabled == 'true'; + INCLUDE "opac/parts/ebook_api/avail.tt2"; +END; +%] + +

[% l("Record details") %]

+
    + [%- IF ebook_item.publisher; %] +
  • + [% l('ISBN:'); %] + [% ebook_item.isbn | html %] +
  • + [%- END %] + + [%- IF ebook_item.issns; %] +
  • + [% l('ISSN:'); %] + [% issn | html %] +
  • + [%- END %] + + [%- IF ebook_item.publisher; %] +
  • + [% l("Publisher:") %] + + [% ebook_item.publisher | html; %], + + [%- IF ebook_item.pubdate; %] + [% ebook_item.pubdate | html; %] + [%- END; %] + +
  • + [%- END %] + + [%- IF ebook_item.language %] +
  • + [% l("Language:") %] + + [% ebook_item.language | html; %] + +
  • + [% END %] + + [%- IF ebook_item.copyright %] + + [%- END %] + + [%- IF ebook_item.genres; %] +
  • + [% l("Genres:") %] + [% ebook_item.genres %] +
  • + [%- END %] + + [%- IF ebook_item.series && !ebook_item.series.search("Default Blank"); %] +
  • + [% l("Series:") %] + [% ebook_item.series %] +
  • + [%- END %] +
+ +[%- IF (ebook_item.shortDescription.size > 0) %] +

[% l('Content descriptions') %]

+ + + + + + + +
Short Description: [%- ebook_item.shortDescription %]
+[%- END %] +[% INCLUDE "opac/ebook_api/rbdigital/parts/record/summaryplus.tt2" vendor_search_uri = vendor_search_uri %] +[% INCLUDE "opac/ebook_api/rbdigital/parts/record/recommendations.tt2" vendor_search_uri = vendor_search_uri %] diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/summaryplus.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/summaryplus.tt2 new file mode 100755 index 0000000000..c870afa4ee --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/record/summaryplus.tt2 @@ -0,0 +1,39 @@ +[% + arrow_right = '►'; + arrow_down = '▼'; + summary = {}; + summary.name = 'summaryplus'; + summary.label = 'Summaries & More'; + + join_operator = '&'; + IF vendor_record_url.search(";") == false AND vendor_record_url.search("&") == false; + join_operator = '?'; + END; + summary.link_show = vendor_record_url.search('expand=summaryplus') ? vendor_record_url : vendor_record_url _ join_operator _ 'expand=summaryplus#summaryplus'; + + summary.link_hide = mkurl('', {}, ['expand','summaryplus']); +%] + +
+
+
+ +
+ [% IF CGI.param('expand') == 'summaryplus' %] +
+
+
+ [% l('Summary: ') %] + [% ebook_record_item.summary | html %] +
+
+
+ [% END; %] +
\ No newline at end of file diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/facets.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/facets.tt2 new file mode 100755 index 0000000000..0d4cc39c30 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/facets.tt2 @@ -0,0 +1,141 @@ +
+[% + +long_facets = CGI.param('long_facet') || []; +selected_facets = CGI.param('facet') || []; + +# we'll clobber the facet. "namespace" later +# Provide a default value if unset in config.tt2 +DEFAULT_DISPLAY_COUNT = facet.default_display_count || 5; +display_count_by_cmf = {}; + +# Loop over each filter +FOREACH facet IN ctx.search_facets; + label = ''; + key = facet.key; + fclass = ctx.search_facets.$key.0.tokenLabel; + fname = ctx.search_facets.$key.0.tokenValue; + + IF key == 'audience'; + label = 'Audience'; + ELSIF key == 'availability'; + label = 'Availabilty'; + ELSIF key == 'digitalRights'; + label = 'Digital Rights'; + ELSIF key == 'fiction'; + label = 'Fiction'; + ELSIF key == 'genre'; + label = 'Genre'; + ELSIF key == 'language'; + label = 'Language'; + ELSE; + label = key; + END; + fid = facet.tokenValue; + + long_key = key; +%] +
+
+ [% IF long_facets.grep(long_key).0; + new_long = []; + FOR fct IN long_facets; + IF fct != long_key; + new_long.push(fct); + END; + END; + expand_url = mkurl('', {long_facet => new_long}); + IF new_long.size == 0; + expand_url = mkurl('', {}, ['long_facet']); + END; + %] + + [% l("Fewer") %] + + [% ELSIF ctx.search_facets.$key.size > DEFAULT_DISPLAY_COUNT %] + + [% l("More") %] + + [% END %] +

[% label %]

+
+
+
+ [% FOR facet_data IN ctx.search_facets.$key; + facet_data = facet_data.0; + display_value = ''; + param_string = facet_data.tokenLabel _ '_' _ facet_data.tokenValue; + + new_facets = []; + filtered_selected_facets = []; + this_selected = 0; + + FOR selected IN selected_facets; + IF selected == param_string; + this_selected = 1; + ELSE; + new_facets.push(selected); + IF !selected.search(facet_data.tokenLabel); + filtered_selected_facets.push(selected); + END; + END; + END; + + IF facet_data.displayLabel == 'True'; + display_value = 'Yes'; + ELSIF facet_data.displayLabel == 'False'; + display_value = 'No'; + ELSE; + display_value = facet_data.displayLabel; + END; + %] + + [% IF this_selected; + # This facet is already selected by the user. + # Link removes the facet from the set of selected facets. + %] +
+
+ [% IF new_facets.size == 0 %] + [% display_value %] + [% ELSE %] + [% display_value %] + [% END %] +
+
([% facet_data.count; IF facet_data.count == (ctx.superpage + 1) * ctx.superpage_size; '+'; END %])
+
+ [% + ELSE; + # This facet is not currently selected. If selected, + # append this facet to the list of currently active facets. + + %] + + [% END; %] + [% IF loop.count == DEFAULT_DISPLAY_COUNT; + LAST; + END; %] + + [% END; %] +
+
+
+[% END; %] +
+ diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/lowhits.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/lowhits.tt2 new file mode 100755 index 0000000000..cd6b5a935c --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/lowhits.tt2 @@ -0,0 +1,68 @@ +
+
+
+

[% l('Search Results filters') %]

+
+
+ [% INCLUDE "opac/parts/staff_saved_searches.tt2" %] +
+
+

[% qhtml = CGI.param('query') | html; + IF ctx.bookbag; + wbbag = ctx.bookbag.name | html; + fmt_bookbag = '' _ wbbag _ ''; + IF is_advanced OR is_special; + l('Sorry, no entries were found for your search within [_1].', fmt_bookbag); + ELSE; + l('Sorry, no entries were found for [_1] within [_2].', '' _ qhtml _ '', fmt_bookbag); + END; + ELSE; + IF is_advanced OR is_special; + l('Sorry, no entries were found for your search.'); + ELSE; + IF !qhtml; + l('Please enter a search term in the Search box.'); + ELSE; + l('Sorry, no entries were found for [_1].', '' _ qhtml _ ''); + END; + END; + END %] +

+
+
+ [% INCLUDE "opac/parts/result/lowhits_purchase.tt2" %] +

+ [% l('Keyword Search Tips') %]
+ [% i18n_advsearch = l('Advanced Search'); + l('Try changing to [_1].', '' _ i18n_advsearch _ '') %] +

+

+ [% l('Adjacency') %]
+ [% l('Multiple words are not searched together as a phrase. They will ' _ + 'be found in various parts of the record. To search for a phrase, enclose your ' _ + 'search terms in quotation marks.') %]
+ [% i18n_searchphrase = l('garcia marquez'); + l('(example: [_1])', '"' _ i18n_searchphrase _ '"') %] +

+

+ [% l('Truncation') %]
+ [% l('Words may be right-hand truncated using an asterisk. Use a single asterisk * ' _ + 'to truncate any number of characters.') %]
+ [% i18n_searchtrunc = l('environment* agency'); + l('(example: [_1])', '' _ i18n_searchtrunc _ '') %] +

+

+ [% l('Anchored Searching') %]
+ [% l('You may use ^ and $ to indicate "phrase begins with" and ' _ + '"phrase ends with," respectively, within a search phrase ' _ + 'enclosed in quotation marks.') %]
+ [% i18n_searchbegins = l('harry'); + i18n_searchends = l('stone'); + l('(examples: [_1] for phrases that begin with the term [_2]. ' _ + '[_3] for phrases that end in [_4].)', + '"^' _ i18n_searchbegins _ '"', '' _ i18n_searchbegins _ '', + '"' _ i18n_searchends _ '$"', '' _ i18n_searchends _ '') %] +

+
+
+
diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/table.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/table.tt2 new file mode 100755 index 0000000000..672b2bbbb6 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/result/table.tt2 @@ -0,0 +1,454 @@ +[% PROCESS "opac/parts/misc_util.tt2"; + PROCESS "opac/ebook_api/rbdigital/parts/file_formats.tt2"; + USE ResolverResolver; + + ctx.result_start = 1 + ctx.page_size * page; + ctx.result_stop = ctx.page_size * (page + 1); + IF ctx.result_stop > ctx.hit_count; ctx.result_stop = ctx.hit_count; END; + + result_count = ctx.result_start; + USE Dumper; +%] +[% PROCESS "opac/parts/result/paginate.tt2" %] +[% ctx.results_count_header = PROCESS results_count_header; + ctx.results_count_header %] +[% IF ctx.bookbag %] +
+
[% ctx.bookbag.name | html %]
+
[% ctx.bookbag.description | html %]
+
+[% END %] + +
+
+ [%- IF ctx.is_staff %] +

[% l('Saved Searches') %]

+ [% INCLUDE "opac/parts/staff_saved_searches.tt2" %] + [%- END %] +

[% l('Search Results facets') %]

+ [% INCLUDE 'opac/ebook_api/rbdigital/parts/result/facets.tt2' %] +

[% l('Search Results List') %]

+
+
+ + + + + + + + + + [% + FOR rec IN ctx.records; + ebook_record_item = rec.item; + PROCESS "opac/ebook_api/rbdigital/parts/item_parser.tt2"; + + -%] + + + + + + + [%- IF ENV.OILS_CHILIFRESH_ACCOUNT %] + + + + + + + [%- END %] + [% END %] + +
[% l('Search result number') %][% l('Book jacket cover art') %][% l('Item details and Actions') %]
+ [% result_count; result_count = result_count + 1 %]. + [% l('Book cover') %]
+
+ +
+
+ [% IF ebook_item.isTitleAvailable == true; %] + + [% END; %] + [% IF !ctx.is_meta %] +
+ [% IF !ctx.is_staff; + + %] + +  [% l("Add to Wishlist") %] + + + [% END %] +
+ [% END %] + + [% IF ENV.OILS_CONTENT_CAFE_USER %] + [% ident = ebook_item.isbn_clean || ebook_item.upc %] + + [% END %] +
+
+ +
+ + +
+ + +
+
+
+ + diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/searchbar.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/searchbar.tt2 new file mode 100755 index 0000000000..37fbea890c --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/parts/searchbar.tt2 @@ -0,0 +1,184 @@ +

[% l('Catalog Search') %]

+[% PROCESS "opac/parts/org_selector.tt2"; + +ebook_uri = ctx.opac_root _ '/ebook_api/rbdigital'; +# We need to ignore some filters in our count + +fignore = ['location_groups','site','core_limit','limit','badge_orgs','badges','estimation_strategy','depth']; +fcount = 0; +FOR f IN ctx.query_struct.filters; + IF fignore.grep('^' _ f.name _ '$').size; + NEXT; + END; + fcount = fcount + 1; +END; + + # don't display a box for the search_format filter, + # as that's got its own widget + ignore_filters = ['search_format']; + + trimmed_filters = []; + FOR filter IN ctx.query_struct.filters; + fname = filter.name; + IF ignore_filters.grep('^' _ fname _ '$').size; + NEXT; + END; + trimmed_filters.push(filter); + END; + + ctx.query_struct.filters = trimmed_filters; + + %] + +
+ +
+ + [% IF ctx.page == 'rresult' && ctx.metarecord && search.metarecord_default %] + + [% END %] + [% IF (ctx.page == 'place_hold' || ctx.page == 'myopac' || ctx.page == 'home' || ctx.page == 'record') && search.metarecord_default %] + + [% END %] + + + [% IF ctx.bookbag %] +
+ + +
+ [% END %] + [% IF is_advanced || is_special %] +
+ + [% IF ctx.processed_search_query OR (NOT is_advanced AND NOT is_special) %] + + [% END %] + [% IF is_advanced; + FOR p IN CGI.params.keys; + NEXT UNLESS p.match('^fi:'); + NEXT IF p.match('^fi:search_format'); + FOR pv IN CGI.params.$p; + %][% + END; + END; + END %] + [% IF is_special %] + [% + number_of_expert_rows = CGI.param('tag').list.size; + index = 0; + WHILE index < number_of_expert_rows %] + + + + [% index = index + 1; %] + [% END %] + [% END %] +
+ [%- END %] + [% UNLESS took_care_of_form %] + [% IF ctx.default_sort %] + + [% END %] +
+ [% END %] + [% IF fcount > 0 %] + + [% END %] + [% IF ctx.query_struct.filters.size > 0 %] + [% stuff = INCLUDE 'opac/parts/result/adv_filter.tt2' %] + [% IF stuff %] +

[% l('Search Results filters') %]

+
+ [% l('Filtered by:') %] + [% stuff %] +
+ [% END %] + [% END %] + [% IF (is_advanced AND NOT is_special) AND CGI.param('qtype') %] + + [% END %] + + + + +
+
diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/record.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/record.tt2 new file mode 100755 index 0000000000..f6d656fb08 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/record.tt2 @@ -0,0 +1,28 @@ +[%- PROCESS "opac/parts/header.tt2"; + WRAPPER "opac/parts/base.tt2"; + vendor_key="rbdigital"; + INCLUDE "opac/parts/topnav.tt2"; + ctx.page_title = l("Record Detail"); + canon = ctx.proto _ '://' _ ctx.hostname _ mkurl('', {}, 1); + ctx.metalinks.push(''); + ctx.metalinks.push(''); + IF CGI.param("expand"); basic_search = "f"; END; +-%] +

[% l('Record Details') %]

+ [% INCLUDE "opac/ebook_api/rbdigital/parts/searchbar.tt2" %] +
+
+ [% IF ctx.staff_saved_search_size %] +
+ +
+ [% END %] +
+ [% INCLUDE "opac/ebook_api/rbdigital/parts/record/body.tt2" %] +
+
+
+
+[%- END %] diff --git a/Open-ILS/src/templates/opac/ebook_api/rbdigital/register_modal.tt2 b/Open-ILS/src/templates/opac/ebook_api/rbdigital/register_modal.tt2 new file mode 100755 index 0000000000..e932ef8c62 --- /dev/null +++ b/Open-ILS/src/templates/opac/ebook_api/rbdigital/register_modal.tt2 @@ -0,0 +1,61 @@ +
+ + + + + [% END -%] [% END -%] + diff --git a/Open-ILS/src/templates/opac/parts/record/summary.tt2 b/Open-ILS/src/templates/opac/parts/record/summary.tt2 old mode 100644 new mode 100755 diff --git a/Open-ILS/src/templates/opac/parts/topnav.tt2 b/Open-ILS/src/templates/opac/parts/topnav.tt2 old mode 100644 new mode 100755 index 920571c648..5fc4a6fa7f --- a/Open-ILS/src/templates/opac/parts/topnav.tt2 +++ b/Open-ILS/src/templates/opac/parts/topnav.tt2 @@ -104,4 +104,5 @@
[% INCLUDE "opac/parts/topnav_links.tt2" %] +[% INCLUDE "opac/parts/vendor_tabs.tt2" %] [% END %] diff --git a/Open-ILS/src/templates/opac/parts/topnav_links.tt2 b/Open-ILS/src/templates/opac/parts/topnav_links.tt2 old mode 100644 new mode 100755 diff --git a/Open-ILS/src/templates/opac/parts/vendor_tabs.tt2 b/Open-ILS/src/templates/opac/parts/vendor_tabs.tt2 new file mode 100755 index 0000000000..a0693ac4cc --- /dev/null +++ b/Open-ILS/src/templates/opac/parts/vendor_tabs.tt2 @@ -0,0 +1,28 @@ +[% IF ctx.user and ctx.vendors.size > 1; %] + +
+
+ [% vendor_search_uri = ""; + vendor_tab_class = ""; + IF vendor_key == ''; + vendor_key = 'default'; + END; + FOR vendor IN ctx.vendors; + IF vendor.vendor_key == vendor_key; + vendor_tab_class = "acct-tab-on"; + vendor_search_uri = vendor.search_uri; + ELSE; + vendor_tab_class = "acct-tab-off"; + END; %] + [% vendor.name %] + [% END; %] +
+
+ + [% IF ctx.rbdigital_patron_id == 0 %] + + [%- INCLUDE 'opac/ebook_api/rbdigital/register_modal.tt2' %] + [% END;%] + +[% END; %] diff --git a/Open-ILS/src/templates/opac/results.tt2 b/Open-ILS/src/templates/opac/results.tt2 old mode 100644 new mode 100755 index 08532ec95e..3d12a94d44 --- a/Open-ILS/src/templates/opac/results.tt2 +++ b/Open-ILS/src/templates/opac/results.tt2 @@ -1,5 +1,7 @@ [%- PROCESS "opac/parts/header.tt2"; WRAPPER "opac/parts/base.tt2"; + + vendor_key="default"; INCLUDE "opac/parts/topnav.tt2"; IF is_advanced || is_special; @@ -18,6 +20,7 @@ PROCESS "opac/parts/misc_util.tt2"; PROCESS get_library; + -%]

[% l('Search Results') %]

@@ -130,4 +133,5 @@
+ [%- END %] diff --git a/Open-ILS/web/css/skin/default/ebook/rbdigital.css b/Open-ILS/web/css/skin/default/ebook/rbdigital.css new file mode 100755 index 0000000000..e8d9f0fd74 --- /dev/null +++ b/Open-ILS/web/css/skin/default/ebook/rbdigital.css @@ -0,0 +1,83 @@ +.rbdigital .modal-header, .rbdigital h4, .close { + background-color: #007a54; + color:white !important; + text-align: center; + font-size: 30px; +} + +.rbdigital .modal-body { + padding:40px 50px; +} + +.rbdigital .btn-success { + background-color: #007a54; +} + +/* Tooltip */ +.rbdigital .tooltip > .tooltip-inner { + background-color: #007a54; + color: #FFFFFF; + border: 1px solid green; + padding: 10px; + font-size: 11px; + width: 600px; +} +/* Tooltip on top */ +.rbdigital .tooltip.top > .tooltip-arrow { + border-top: 5px solid green; +} +/* Tooltip on bottom */ +.rbdigital .tooltip.bottom > .tooltip-arrow { + border-bottom: 5px solid blue; +} +/* Tooltip on left */ +.rbdigital .tooltip.left > .tooltip-arrow { + border-left: 5px solid red; +} +/* Tooltip on right */ +.rbdigital .tooltip.right > .tooltip-arrow { + border-right: 5px solid black; +} + +#divPassword .tooltip > .tooltip-inner { + width: 600px; +} + +#divPassword .tooltip > .tooltip-inner > ul { + list-style: none; + padding-left: 20px; + text-align: left; + padding-bottom: 0; +} + +.rbdigital #registerProgress, #registerError { + font-size: 18px; + color: #007a54; + margin-left: 2em; +} + +#registerProgress > img { + width: 30px; + height: auto; +} + +.rbdigital .adv_global_input_container { + width: 230px !important; +} + +.rbdigital #adv_global_input_table td { + padding-bottom: 10px; +} + +#modalMapSearchFieldValues .opac-button { + margin-top: 0; +} + +#modalMapSearchFieldValues .modal-body { + padding-top: 10px; + padding-bottom: 150px; +} +a.opac-button:hover { + color: white; + text-decoration: none; +} \ No newline at end of file diff --git a/Open-ILS/web/images/rbdigital_logo.png b/Open-ILS/web/images/rbdigital_logo.png new file mode 100755 index 0000000000000000000000000000000000000000..c6a8d87773dd96ef05d28a17710338963aaf8c35 GIT binary patch literal 3158 zcmV-c45{;pP)5Gt5prt@<-t`47rXvits8g+7$6^A> z-knjg=zzQ&tSB?$P*Fe&>b;w^Aq|=;jHs=nv;~ziNJF-1)fP#idG!Id$+p=fyL->) zAKfjtG1+X&=F#bXGnv_Y&)MHS_jkVYd!4fpj1o>16-_$@6bYq-ip3NZi-w2u{Qk{I zVZy?dOkr&$CG!!ne@LSEq|GY;fZ*vy0-`iMrSc1i5MtYTiddCW>-h^UrGgYvgh(Mp zh!j$UNFhat6jFppAw`H3QiMn$MTiu}0|{3uxqNxXkt0XuMSz)NV)_SSS zeoGn~!wJhOE6Z%_>dF@-1)!Qik*ONc1L3Wp9o2`RP}6#5}r zL*}IH4KrVU`DALU#?fVP`SOg`{K5zN0;k?4)#D^?ijiS(NJ5)dlmz<$LZX;TJ=_~O zb*Qbd_&z@PV9LFXhlMzr|KTgzJ34;@pqLY5j2BU}T)Zy*usT!GjrYxBJw1D0t`2ovuTN4qX+A zL@qmP)wLBCexzT*jU9S;No0n;=e8E)b%%UYDo_xAaGx3{#k^n}CVeE>F` zd9kCVrSm}f1ITF;)tr(1U~f3`hht@Blg9G7kx1k*k~5=YSW{hHeN(bzE2Vx8U=m3I zaCdce^}>ODOw+v4G|j)h`R1DelG~H4(~C%^pA|!}tjyIPvY(H=^n?ac(*NJCN5?bP zN~VPn5p4iL29sR?U@3sCF?rGU6DPg`aA5+&Z-mHGnrL4W(F|zXI!*8^0sc8MEnt_3 zJg7rf>bfj}q~!~=6$0D`k(U$rzNfRa^y;xhAg++-8=BU*XynG`Fy`i zOH0eDsHixGafYkCtabEr3M;<^kviu$$z9p}u2T)@K5NKvmSA2fSYItwZ!%1mF$;i%4E7 zBCP;+q^GC*0)fD9eLmkyRaI5@h(iySWmS>VWi^37;28jE0Opft z7VK2ZvYv}wOqnv}i*C0&K_lKeW!GLm^D7m>UTKv5i$&Lg>! z=q2Wm(pTrO7@ zfcqV{kGWi~tk|C;F4-<3ztuGDE=|*xW@TmN>AF4(KskU|!7Doy3auZmQaLM*pZ?c) z1~?zc$T-jw?2D6ZAR$6%ns%*HYEsPV>AJqMva&Kpwi5u}GEH+OfUAbGRt_J27HVs2 z=l1va&pD$|muqc#dHLb;@^S!p*|MxBNv?|$%OwfKQ3KqDZJOp;8;6yZmH(#{!!Qm3 z_`)D!>U=)m0;SY0k5EsAtYCuFHaZE&Zg2kUg$K{(2GQ4kuLLq0m z9=Y|_TMrJfF8^^5>+12)Bgt1CpK}@-8m7jx5ElZRCx;eeg!Uta|(Xnb*#Sh`uh4A1FX}fj?0$O8i#3` zWdK@8-b?a&k~sjf0c4ZROt=n_n$q`;eIn8qQ+hBMe1PPgBBJW+>t_UmLBr9vc{B!4 zUtd3?r>CbrW)Vc>5P)9+I3DeCSu`C@5n{CA@pw*}rs)H4d(=vN!?LVZmSuGWgTaL) zCr2l#kK~U>V+_5$y`Oft#h|Y1AFHgaJQ8d7`Fu+fCBy~cGDPHr!>kV#P)!}IeumJ-e3M0F zv7?WFB*LS01#6I|QzG&r$&Y%y-ls+5SZ zO|!kXxAz^ApLMi7HgEz5rji4(di83ObU6rQ+O~bbvaD9ivUXUOwZ5jN=3_(D2!=hW zI9#8Cf`Y98b~!A<9i5$>U6y5aL?V%9l9$EaLqxtiW-dd6EB;Y@Q6oCw!PTqNK@NE^ zml_g2`d?L5^%D_!DmJ;%RevcR@5b{e!H~6H#UFcUB$;WE(yanXhTu z9U@XKBClXTrI%z?b#-<5fN>p*$rh2fhZz2GN1s8TUtV5*xVX5uKt%2ok>;3-6p^DM z^1QC=#U77m{SbAO*t@kzYknc)XmQirX8&!8D*bqA>C{u9NLQR`d13Be|BZ>tYb_}L z5~%8jWV5E-mAj|uoKv})nwn{qm6cti>8?!EyhcPelBai-MWoI!jPlWTljnL$JgF99 zdShd^Kw9J2h$U?$CG!(r)u1>2s^|MUr;!XY)Ya8ZF-`N4jT<+9Xt4HZs@dkS65SXB zxZQ61Tz)c;{KAxm-$R`)(hP!3hSgI5hP4vy1;rJL?;RK!#9f@sx$?@F;P+!Z(bv~k z0pRX%IDEHdS-S!JMMU>D&q zGXT66XS_!NteZM@>MbKV4>2A{YEXG*V)IK%ZP>5H%Mb=Ur%QlzlGzFJ_!o2cHhpEH zDf8*4pS~m%3e5v>g;Hv!uIrsjsn#h|ro85MyF)k+Fe-!qfY!p|yP(vMl0IM&f5!A_ zOJ}|K;whvs@m)p?bN4hoDw=FaE*a1c;ipq%I1dQ{0J;9Awa~P60wlq2%5FW)bzR<` zJsqiOJ5LEABAmOY=^==Ivp90`5z4SNktg$VFIke)*w~tyvh$b_G2}JB{-@mBoGV4c z+URMXL=X~@dXe;nxy}CDW!tv?)O3wOP}!DWbkC5jI`-xH{acg$(AI**iy-z*n&O8D w%z|JhfF6R!Ky+xDzSG4SJIC`kAr~tC2d@OIi&K}rdjJ3c07*qoM6N<$f>?Password must meet the following:
    '+ + '
  • At least 8 to 16 characters long
  • '+ + '
  • At least 1 lowercase character
  • ' + + '
  • At least 1 uppercase character
  • ' + + '
  • At least 1 numeric character
  • ' + + '
  • At least 1 special character:
  • '+ + '
  • -~!@#\$%\^&
' + }); + + //Load Tooltips & popover elements + $('[data-toggle="tooltip"]').tooltip({ + trigger: 'hover focus' + }); + +}); + +function setupEventHandlers() { + $('.ebook_checkout_link').on('click',function(event) { + console.log('Not implement'); + }); + + $(document).on("click", ".ebook_wishlist_link_add", function () { + showEbookSpinner($(this)); + var isbn = $(this).data('isbn'); + ebook_rbdigital.id = isbn; + + ebook_rbdigital.wishlist(authtoken, 'add', wishlistAddCallback); + return false; + }); + + $(document).on("click", ".ebook_wishlist_link_remove", function () { + showEbookSpinner($(this)); + var isbn = $(this).data('isbn'); + ebook_rbdigital.id = isbn; + ebook_rbdigital.wishlist(authtoken, 'delete', wishlistRemoveCallback); + return false; + }); + + $("#lnkSearchResults_rbdigital").click(function(event){ + //Do not show registration modal if patronid exists + if (rbdigitalPatronId > 0) { + return true; + } + event.preventDefault(); + $('#password').focus(); + $("#rbdigitalRegisterModal").modal(); + return false; + }); + + $( "#frmRegisterRBDigital" ).submit(function( event ) { + event.preventDefault(); + + var password = $('#password'), + confirmPassword = $('#confirmPassword'), + passwordError = $('#passwordError'), + confirmPasswordError = $('#confirmPasswordError'); + + passwordError.addClass('hidden'); + confirmPasswordError.addClass('hidden'); + $('#registerError').addClass('hidden'); + + console.log(password.val()); + //Check if password matches the strength + if (!checkPasswordStrength(password.val())) { + password.focus(); + passwordError.removeClass('hidden'); + return; + } else if (password.val() !== confirmPassword.val()) { + confirmPasswordError.removeClass('hidden'); + confirmPassword.focus(); + return; + } + $('#btnRegister').prop("disabled", true); + $('#registerProgress').removeClass('hidden'); + ebook_rbdigital.registerPatron(authtoken, password.val(), registerPatronCallback); + + }); + + //Set Modal Focus for Register + $('#rbdigitalRegisterModal').on('shown.bs.modal', function () { + $('#password').focus(); + }); + + //Display Search after modal for register success is closed + $("#btnCloseRegisterSuccess").click(function(event) { + //redirect user to rbdigital search page + window.location.href = window.location.protocol+"//"+window.location.host+$("#lnkSearchResults_rbdigital").attr('href'); + + }); + + //Listen for Save Mapping Fields Event + $("#btnSaveSearchField").click(function(event) { + var eg_search_field = $('#eg_search_field').val(), + rbdigital_search_field = $('#rbdigital_search_field').val(), + eg_selected_optgroup = $('#eg_search_field :selected').closest('optgroup').attr('label'), + rbdigital_selected_optgroup = $('#rbdigital_search_field :selected').closest('optgroup').attr('label'), + field_type; + + if (eg_search_field == '' || rbdigital_search_field == '') { + showNotification('Empty Selection','Please select Evergreen and RBDigital fields to save!','error'); + } else if (eg_selected_optgroup != rbdigital_selected_optgroup) { + showNotification('Invalid Selection','Please select both Text values or Dropdown values from selections!','error'); + } else { + showEbookSpinner($(this)); + field_type = eg_selected_optgroup.indexOf("Dropdown") == 0 ? "dropdown" : "text"; + ebook_rbdigital.saveSearchField(authtoken, vendor_rbdigital.name, eg_search_field, rbdigital_search_field, field_type, saveSearchFieldCallback); + } + }); + + //Listen for Save Search Field Mappings (Field Values) + $('#btnSaveSearchFieldMappings').click(function(event) { + var eg_search_field_value = $('#eg_search_field_values').val(), + digital_services_field_code = $('#ds_search_field_values').val(), + digital_services_field_code, + eg_selected_value, evergreen_field_code, evergreen_field_value; + + if (eg_search_field_value == '' || digital_services_field_code == '') { + showNotification('Empty Selection','Please select Evergreen and RBDigital field value to save!','error'); + } else { + //Split the value __ to get code and value separately + eg_selected_value = eg_search_field_value.split(EG_ID_FIELD_SEPARATOR); + evergreen_field_code = eg_selected_value[0]; + evergreen_field_value = eg_selected_value[1]; + digital_services_search_field_value = $('#ds_search_field_values option:selected').text(); + + ebook_rbdigital.saveSearchFieldValue(authtoken, $('#txtSearchFieldsID').val(), evergreen_field_code, evergreen_field_value, + digital_services_field_code, digital_services_search_field_value, saveSearchFieldValueCallback); + } + }); + + //Listen for Modal that display Search Field Values + $(document).on('click', '.openModalMapSearchFieldValues', function () { + var eg_search_field = $(this).data('eg_search_field'), + digital_services_field = $(this).data('digital_services_field'), + search_fields_id = $(this).data('id'); + + $('#spanEvergreenField').html(eg_search_field.ucfirst()); + $('#spanRBDigitalField').html(digital_services_field.ucfirst()); + + //Store search_fields_id to a hidden input element + $('#txtSearchFieldsID').val(search_fields_id); + + populateSeachFieldValuesForSelection(eg_search_field, digital_services_field); + ebook_rbdigital.getMappedSearchFieldValues(authtoken, search_fields_id, getMappedSearchFieldValuesCallback); + }); + + $(document).on('click', '.removeSearchField', function () { + var search_fields_id = $(this).attr('data-id'); + ebook_rbdigital.removeSearchField(authtoken, search_fields_id, removeSearchFieldCallback); + }); + + $(document).on('click', '.removeSearchFieldValue', function () { + var search_field_mappings_id = $(this).attr('data-id'); + ebook_rbdigital.removeSearchFieldValue(authtoken, search_field_mappings_id, removeSearchFieldValueCallback); + }); +} + +function showNotification(title, text, type) { + new PNotify({ + title: title, + text: text, + type: type + }); +} + +function showEbookSpinner(adjacent_element) { + //Add an ebook spinner right to the adjacent element + $(adjacent_element).after(ebook_spinner); + ebook_spinner.removeClass('hidden'); +} + +function hideEbookSpinner() { + ebook_spinner.addClass('hidden'); +} + +//Process post ajax call to add wishlist to patron's account +function wishlistAddCallback(responseContents, isbn) { + if (responseContents.message == "success") { + $("#"+isbn+"_ebook_wishlist_add").addClass('hidden'); + $("#"+isbn+"_ebook_wishlist_remove").removeClass('hidden'); + } else if (responseContents.message.indexOf("already exists") > 1) { + $("#"+isbn+"_ebook_wishlist_add").addClass('hidden'); + $("#"+isbn+"_ebook_wishlist_remove").removeClass('hidden'); + } else { + console.log(responseContents.message); + } + hideEbookSpinner(); +} + +//Process post ajax call to remove wishlist to patron's account +function wishlistRemoveCallback(responseContents, isbn) { + if (responseContents.message == "success") { + console.log(responseContents.message); + $("#"+isbn+"_ebook_wishlist_add").removeClass('hidden'); + $("#"+isbn+"_ebook_wishlist_remove").addClass('hidden'); + } else { + console.log(responseContents.message); + } + + hideEbookSpinner(); +} + +//Process post ajax call to register the patron +function registerPatronCallback(response) { + rbdigitalPatronId = parseInt(response); + console.log(rbdigitalPatronId); + if (rbdigitalPatronId < 1 ) { + $('#registerError').removeClass('hidden'); + $('#registerProgress').addClass('hidden'); + $('#btnRegister').prop("disabled", false); + return; + } + + $('#rbdigitalRegisterModal').modal('hide') + + $('#btnRegister').prop("disabled", false); + $('#registerProgress').addClass('hidden'); + + $('#rbdigitalRegisterSuccess').modal(); +} + +function saveSearchFieldCallback(responseContents) { + if (responseContents.status == "success") { + showNotification("Success", "Search Field Saved", "success"); + location.reload(); + } else if (typeof responseContents.message != 'undefined') { + showNotification("Error", responseContents.message, "error"); + } else { + showNotification("Error", "Unable to save entry", "error"); + } + hideEbookSpinner(); +} + +function removeSearchFieldCallback(responseContents) { + if (responseContents.status == "success") { + showNotification("Success", "Search Field Removed", "success"); + } else { + showNotification("Error", "Unable to remove entry", "error"); + } + + hideEbookSpinner(); + //Refresh a page + location.reload(); +} + +function saveSearchFieldValueCallback(responseContents) { + if (responseContents.status == "success") { + var data, eg_search_field_value, ds_search_field_value, + eg_selected_value, evergreen_field_value; + data = responseContents.data; + eg_search_field_value = $('#eg_search_field_values').val(); + ds_search_field_value = $('#ds_search_field_values option:selected').text(); + + //Split the value __ to get code and value separately + eg_selected_value = eg_search_field_value.split(EG_ID_FIELD_SEPARATOR); + evergreen_field_value = eg_selected_value[1]; + + //Add an entry to the table row + var tableMappedSearchFieldBody = $('#table_mapped_search_values > tbody:last-child'); + tableMappedSearchFieldBody.append(''+ evergreen_field_value + ''+ ds_search_field_value + + ' Remove '); + showNotification("Success", "Search Field value Saved", "success"); + } else { + showNotification("Error", "Unable to save entry", "error"); + } +} + +function removeSearchFieldValueCallback(responseContents, search_field_mappings_id) { + if (responseContents.status == "success") { + $('#search_value_'+search_field_mappings_id).remove(); + showNotification("Success", "Search Field Value Removed", "success"); + } else { + showNotification("Error", "Unable to remove entry", "error"); + } +} + +function populateSeachFieldValuesForSelection(eg_search_field, digital_services_field) { + var ds_search_field_values, eg_data, data; + + ds_search_field_values = $('#ds_search_field_values'); + + if (typeof rbdigital_search_fields[digital_services_field] != 'undefined' ) { + data = rbdigital_search_fields[digital_services_field]; + } else { + data = []; + } + + //Empty dropdown except for first one + ds_search_field_values.find('option:gt(0)').remove(); + + //Iterate over Digital Service values and add them to the drop down list + for (i = 0; i < data.length; i++) { + //Read and build select elememnts for selections + ds_search_field_values.append(''); + } + + //Call an API to get Ebook A + ebook_rbdigital.getEvergreenbSearchFieldValues(authtoken, eg_search_field, getEvergreenbSearchFieldValuesCallback); +} + +function getEvergreenbSearchFieldValuesCallback(responseContents) { + var data, eg_search_field_values; + + if (responseContents.status == "success") { + data = responseContents.data; + eg_search_field_values = $('#eg_search_field_values'); + + //Empty dropdown except for first one + eg_search_field_values.find('option:gt(0)').remove(); + + //Loop over EG Values and build selection list + //code_value, value + for (i = 0; i < data.length; i++) { + if (data[i][1].trim() != '') { + eg_search_field_values.append(''); + } + } + + } else { + showNotification("Error", "No data was found! Please contact system admin to load the data.", "error"); + } +} + +function getMappedSearchFieldValuesCallback(responseContents) { + var data, tableMappedSearchFieldBody; + if (responseContents.status == "success") { + data = responseContents.data; + tableMappedSearchFieldBody = $('#table_mapped_search_values > tbody'); + tableMappedSearchFieldBody.empty(); + //Loop over the data array to read the values in the following order: + //id, search_fields_id, evergreen_field_value, digital_services_field_value + for (i = 0; i < data.length; i++) { + tableMappedSearchFieldBody.append(''+ data[i][3] + ''+ data[i][5] + + ' Remove '); + } + } else { + showNotification("Error", "No data was found! Please contact system admin to load the data.", "error"); + } +} + +function getCookie(cname) { + var name = cname + "="; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for(var i = 0; i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NOW() - (SELECT MAX(value) FROM ( + SELECT value::INTERVAL FROM actor.org_unit_ancestor_setting( + 'circ.staff.max_visible_event_age', + COALESCE(targ_circ.circ_lib, targ_ahr.pickup_lib) + ) UNION + SELECT '1000 YEARS'::INTERVAL AS value + ) ous) + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT * FROM metabib.author_field_entry + UNION ALL + SELECT * FROM metabib.keyword_field_entry + UNION ALL + SELECT * FROM metabib.identifier_field_entry + UNION ALL + SELECT * FROM metabib.title_field_entry + UNION ALL + SELECT * FROM metabib.subject_field_entry + UNION ALL + SELECT * FROM metabib.series_field_entry + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +SELECT usr, + SUM( + CASE + WHEN ( + ((fine_interval >= '1 day' AND due_date >= 'today') OR (fine_interval < '1 day' AND due_date > 'now')) + AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE')) + ) THEN 1 + ELSE 0 + END + ) AS out, + + SUM( + CASE + WHEN ( + ((fine_interval >= '1 day' AND due_date < 'today') OR (fine_interval < '1 day' AND due_date < 'now')) + AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE')) + ) THEN 1 + ELSE 0 + END + ) AS overdue, + + SUM( CASE WHEN (xact_finish IS NULL AND stop_fines = 'LOST') THEN 1 ELSE 0 END) AS lost, + SUM( CASE WHEN stop_fines = 'CLAIMSRETURNED' THEN 1 ELSE 0 END) AS claims_returned, + SUM( CASE WHEN (xact_finish IS NULL AND stop_fines = 'LONGOVERDUE') THEN 1 ELSE 0 END) AS long_overdue + FROM action.circulation + WHERE checkin_time IS NULL + GROUP BY 1 + + + + + + + + + + + + + + + +SELECT usr, + STRING_AGG( + CASE + WHEN ( + ((fine_interval >= '1 day' AND due_date >= 'today') OR (fine_interval < '1 day' AND due_date > 'now')) + AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE')) + ) THEN id::TEXT + ELSE '0' + END + ,',') AS out, + + STRING_AGG( + CASE + WHEN ( + ((fine_interval >= '1 day' AND due_date < 'today') OR (fine_interval < '1 day' AND due_date < 'now')) + AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE')) + ) THEN id::TEXT + ELSE '0' + END + ,',') AS overdue, + + STRING_AGG( CASE WHEN (xact_finish IS NULL AND stop_fines = 'LOST') THEN id::TEXT ELSE '0' END,',') AS lost, + STRING_AGG( CASE WHEN stop_fines = 'CLAIMSRETURNED' THEN id::TEXT ELSE '0' END,',') AS claims_returned, + STRING_AGG( CASE WHEN (xact_finish IS NULL AND stop_fines = 'LONGOVERDUE') THEN id::TEXT ELSE '0' END,',') AS long_overdue + FROM action.circulation + WHERE checkin_time IS NULL + GROUP BY 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ahr.requestor) AS is_staff_hold, + ahcm_1.copy_count AS potential_copies + FROM action.hold_request ahr + JOIN asset.copy acp ON (acp.id = ahr.current_copy) + JOIN asset.call_number acn ON (acp.call_number = acn.id) + JOIN asset.call_number_prefix acnp ON (acn.prefix = acnp.id) + JOIN asset.call_number_suffix acns ON (acn.suffix = acns.id) + JOIN actor.usr au ON (au.id = ahr.usr) + JOIN ( + SELECT *, (ROW_NUMBER() OVER (ORDER BY name) + 1000000) AS fallback_position + FROM asset.copy_location + ) acpl_ordered ON (acpl_ordered.id = acp.location) + LEFT JOIN actor.usr_standing_penalty ausp + ON (ahr.usr = ausp.usr AND (ausp.stop_date IS NULL OR ausp.stop_date > NOW())) + LEFT JOIN config.standing_penalty csp + ON ( + csp.id = ausp.standing_penalty AND + csp.block_list LIKE '%CAPTURE%' AND ( + (csp.org_depth IS NULL AND ahr.pickup_lib = ausp.org_unit) OR + (csp.org_depth IS NOT NULL AND ahr.pickup_lib IN ( + SELECT id FROM actor.org_unit_descendants(ausp.org_unit, csp.org_depth)) + ) + ) + ) + JOIN ( + SELECT COUNT(target_copy) AS copy_count, hold + FROM action.hold_copy_map + GROUP BY 2 + ) ahcm_1 ON (ahcm_1.hold = ahr.id) + LEFT JOIN serial.issuance siss + ON (ahr.hold_type = 'I' AND siss.id = ahr.target) + LEFT JOIN asset.copy_location_order acplo + ON (acp.location = acplo.location AND + acp.circ_lib = acplo.org) + WHERE + ahr.capture_time IS NULL AND + ahr.cancel_time IS NULL AND + csp.id IS NULL AND + (ahr.expire_time is NULL OR ahr.expire_time > NOW()) AND + acp.status IN (0,7) + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT ahr.* FROM action.hold_request ahr JOIN (SELECT current_copy, MAX(capture_time) AS capture_time FROM action.hold_request WHERE capture_time IS NOT NULL AND current_copy IS NOT NULL AND fulfillment_time IS NULL GROUP BY current_copy)x USING (current_copy, capture_time) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT DISTINCT l.* + FROM action.unfulfilled_hold_loops l + JOIN action.unfulfilled_hold_max_loop m USING (hold) + WHERE l.count = m.max + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + cbrebi.id AS id, -- so we can have a pkey in our view + uvs.id AS session, + uvs.owning_lib, + cbrebi.target_biblio_record_entry + FROM url_verify.session uvs + JOIN container.biblio_record_entry_bucket cbreb + ON (uvs.container = cbreb.id) + JOIN container.biblio_record_entry_bucket_item cbrebi + ON (cbrebi.bucket = cbreb.id) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT b.id, + MAX(dcp.edit_date) AS last_delete_date + FROM biblio.record_entry b + JOIN asset.call_number cn ON (cn.record = b.id) + JOIN asset.copy dcp ON (cn.id = dcp.call_number) + WHERE NOT b.deleted + GROUP BY b.id + HAVING SUM( CASE WHEN NOT dcp.deleted THEN 1 ELSE 0 END) = 0 + + + + + + + + + + + + + + + + + + + + -- -- If we uncomment the RIGHT JOIN against biblio.record_entry, then we'll get a row for every non-deleted bib, whether it has active holds or not. + -- -- If we expect to use pcrud to query against specific bibs, we probably want to do this. However, if we're using this to populate a report, we + -- -- may not. + -- SELECT + -- bre.id AS bib_id, + -- COALESCE( z.copy_count, 0 ) AS copy_count, + -- COALESCE( z.hold_count, 0 ) AS hold_count, + -- COALESCE( z.copy_hold_ratio, 0 ) AS hold_copy_ratio + -- FROM ( + SELECT + y.bre AS id, + COALESCE( x.copy_count, 0 ) AS copy_count, + y.hold_count AS hold_count, + (y.hold_count::REAL / (CASE WHEN x.copy_count = 0 OR x.copy_count IS NULL THEN 0.1 ELSE x.copy_count::REAL END)) AS hold_copy_ratio + FROM ( + SELECT + (SELECT bib_record FROM reporter.hold_request_record r WHERE r.id = h.id LIMIT 1) AS bre, + COUNT(*) AS hold_count + FROM action.hold_request h + WHERE + cancel_time IS NULL + AND fulfillment_time IS NULL + -- AND NOT frozen -- a frozen hold is still a desired hold, eh? + GROUP BY 1 + )y LEFT JOIN ( + SELECT + (SELECT id + FROM biblio.record_entry + WHERE id = (SELECT record FROM asset.call_number WHERE id = call_number and deleted is false) + ) AS bre, + COUNT(*) AS copy_count + FROM asset.copy + JOIN asset.copy_location loc ON (copy.location = loc.id AND loc.holdable) + WHERE copy.holdable + AND NOT copy.deleted + AND copy.status IN ( SELECT id FROM config.copy_status WHERE holdable ) + GROUP BY 1 + )x ON x.bre = y.bre + -- )z RIGHT JOIN ( + -- SELECT id + -- FROM biblio.record_entry + -- WHERE NOT deleted + -- )bre ON (z.bib_id = bre.id) + + + + + + + + + + + + + + + + + + + + + + SELECT *, + CASE WHEN copy_count_at_pickup_library = 0 THEN 'Infinity'::FLOAT ELSE holds_at_pickup_library::FLOAT/copy_count_at_pickup_library END AS pickup_library_ratio, + CASE WHEN copy_count_everywhere = 0 THEN 'Infinity'::FLOAT ELSE holds_everywhere::FLOAT/copy_count_everywhere END AS everywhere_ratio + FROM + (SELECT bib_record as id, pickup_lib, count(DISTINCT ahr.id) AS holds_at_pickup_library, COALESCE(count(DISTINCT ac.id),0) as copy_count_at_pickup_library + FROM + action.hold_request ahr + JOIN reporter.hold_request_record rhrr USING (id) + LEFT JOIN action.hold_copy_map ahcm ON (ahr.id = ahcm.hold) + LEFT JOIN asset.copy ac ON (ahcm.target_copy = ac.id AND ahr.pickup_lib = ac.circ_lib) + WHERE + ahr.cancel_time IS NULL + AND ahr.fulfillment_time IS NULL + GROUP BY bib_record, pickup_lib + )x + JOIN + (SELECT bib_record as id, count(DISTINCT ahr.id) AS holds_everywhere, COALESCE(count(DISTINCT target_copy),0) as copy_count_everywhere + FROM + action.hold_request ahr + JOIN reporter.hold_request_record rhrr USING (id) + LEFT JOIN action.hold_copy_map ahcm ON (ahr.id = ahcm.hold) + WHERE + ahr.cancel_time IS NULL + AND ahr.fulfillment_time IS NULL + GROUP BY bib_record + )y + USING (id) + + + + + + + + + + + + + + + + + + + + + + + + + WITH counts_at_ou AS ( + SELECT rhrr.bib_record AS id, + aou.id AS pickup_lib_or_desc, + COUNT(DISTINCT ahr.id) AS holds_at_or_below, + COALESCE(COUNT(DISTINCT ac.id),0) AS copy_count_at_or_below + FROM actor.org_unit aou + JOIN actor.org_unit_type aout ON (aou.ou_type = aout.id), + action.hold_request ahr + JOIN reporter.hold_request_record rhrr USING (id) + LEFT JOIN action.hold_copy_map ahcm ON (ahr.id = ahcm.hold) + LEFT JOIN asset.copy ac ON (ahcm.target_copy = ac.id) + WHERE ahr.cancel_time IS NULL AND ahr.fulfillment_time IS NULL + AND ac.circ_lib IN (SELECT id FROM actor.org_unit_descendants(aou.id)) + AND (actor.org_unit_ancestor_at_depth(ahr.pickup_lib,aout.depth)).id = (actor.org_unit_ancestor_at_depth(ac.circ_lib,aout.depth)).id + GROUP BY 1, 2 + ) + SELECT x.id, x.pickup_lib_or_desc, x.holds_at_or_below, x.copy_count_at_or_below, + y.holds_everywhere, y.copy_count_everywhere, + CASE WHEN copy_count_at_or_below = 0 THEN 'Infinity'::FLOAT ELSE x.holds_at_or_below::FLOAT/x.copy_count_at_or_below END AS hold_copy_ratio_at_or_below_ou, + CASE WHEN copy_count_everywhere = 0 THEN 'Infinity'::FLOAT ELSE y.holds_everywhere::FLOAT/y.copy_count_everywhere END AS everywhere_ratio + FROM counts_at_ou x + JOIN (SELECT bib_record AS id, count(DISTINCT ahr.id) AS holds_everywhere, COALESCE(count(DISTINCT target_copy),0) as copy_count_everywhere + FROM + action.hold_request ahr + JOIN reporter.hold_request_record rhrr USING (id) + LEFT JOIN action.hold_copy_map ahcm ON (ahr.id = ahcm.hold) + WHERE + ahr.cancel_time IS NULL + AND ahr.fulfillment_time IS NULL + GROUP BY bib_record + )y + USING (id) + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + ac.id, + COALESCE(MAX(actac.xact_start), ac.create_date) AS last_circ_or_create, + MAX(actac.xact_start) AS last_circ + FROM asset.copy ac + LEFT JOIN action.all_circulation actac ON ac.id = actac.target_copy + GROUP BY ac.id + + -- Alternate version, say if you have migrated last checkout information in extend_reporter.legacy_circ_timestamp: + --SELECT + -- ac.id, + -- GREATEST(MAX(actac.xact_start), erlct.last_cko_ts, ac.create_date) AS last_circ_or_create, + -- GREATEST(MAX(actac.xact_start), erlct.last_cko_ts) AS last_circ + --FROM asset.copy ac + -- LEFT JOIN action.all_circulation actac ON ac.id = actac.target_copy + -- LEFT JOIN extend_reporter.legacy_circ_timestamp erlct ON ac.id = erlct.id + --GROUP BY ac.id, ac.create_date, erlct.last_cko_ts + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + po.ordering_agency AS ordering_agency, + po.id AS purchase_order, + li.id AS lineitem, + lid.id AS lineitem_detail, + cpa.id AS claim_policy_action + FROM + acq.lineitem_detail lid + JOIN acq.lineitem li ON (li.id = lid.lineitem) + JOIN acq.purchase_order po ON (po.id = li.purchase_order) + JOIN acq.claim_policy cp ON (li.claim_policy = cp.id) + JOIN acq.claim_policy_action cpa ON ( + cpa.claim_policy = cp.id + + -- we only care about claim policy actions whose claim + -- interval we'd reached or exceeded + AND (NOW() - cpa.action_interval) > po.order_date + + -- filter out all claim policy actions where claim events + -- have occurred on or after the action's action_interval + AND NOT EXISTS ( + SELECT 1 + FROM + acq.claim_event evt + JOIN acq.claim claim ON ( + claim.id = evt.claim + AND claim.lineitem_detail = lid.id + ) + WHERE + evt.event_date >= (po.order_date + cpa.action_interval) + ) + ) + WHERE + lid.cancel_reason IS NULL + AND li.cancel_reason IS NULL -- belt/suspenders + AND po.cancel_reason IS NULL -- belt/suspenders + AND lid.recv_time IS NULL + AND po.state = 'on-order' + ORDER BY 1, 2, 3, 4, 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT * FROM acq.lineitem_summary + WHERE item_count > (invoice_count + cancel_count) + + + + + + + + + + + + + + + + + + + + + SELECT t.* + FROM action.transit_copy t + JOIN actor.org_unit AS s ON (t.source = s.id) + JOIN actor.org_unit AS d ON (t.dest = d.id) + WHERE s.parent_ou <> d.parent_ou + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT copy, SUM(count) AS count, year, is_renewal FROM ( + SELECT + cp.id as copy, + COUNT(circ.id), + EXTRACT(YEAR FROM circ.xact_start) AS year, + (phone_renewal OR desk_renewal OR opac_renewal) as is_renewal + FROM + asset.copy cp + JOIN action.circulation circ ON (cp.id = circ.target_copy) + GROUP BY 1, 3, 4 + UNION ALL + SELECT + cp.id as copy, + COUNT(circ.id), + EXTRACT(YEAR FROM circ.xact_start) AS year, + (phone_renewal OR desk_renewal OR opac_renewal) as is_renewal + FROM + asset.copy cp + JOIN action.aged_circulation circ ON (cp.id = circ.target_copy) + GROUP BY 1, 3, 4 + UNION ALL + SELECT + id as copy, + circ_count, + -1 AS year, + false as is_renewal + FROM + extend_reporter.legacy_circ_count + )x GROUP BY 1, 3, 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT ahcm.target_copy AS id,count(*) AS count + FROM + action.hold_request ahr, + action.hold_copy_map ahcm + WHERE + ahr.cancel_time IS NULL AND + ahr.fulfillment_time IS NULL AND + ahr.capture_time IS NULL AND + ahr.id = ahcm.hold + GROUP BY ahcm.target_copy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/web/reports/fm_IDL.xml-jaswinder b/Open-ILS/web/reports/fm_IDL.xml-jaswinder new file mode 100755 index 0000000000..3ee35c2f20 --- /dev/null +++ b/Open-ILS/web/reports/fm_IDL.xml-jaswinder @@ -0,0 +1,12158 @@ + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT atevdef.hook, + atevdef.name, + atevdef.reactor, + atev.id, + atev.event_def, + atev.add_time, + atev.run_time, + atev.start_time, + atev.update_time, + atev.complete_time, + atev.update_process, + atev.state, + atev.user_data, + atev.template_output, + atev.error_output, + atev.async_output, + targ_circ.id AS target_circ, + targ_ahr.id AS target_hold, + COALESCE( + targ_circ.circ_lib, + targ_ahr.pickup_lib + ) AS perm_lib + FROM action_trigger.event atev + JOIN action_trigger.event_definition atevdef ON + (atevdef.id = atev.event_def) + JOIN action_trigger.hook ath ON + (ath.key = atevdef.hook) + LEFT JOIN action.circulation targ_circ ON + (ath.core_type = 'circ' AND targ_circ.id = atev.target) + LEFT JOIN action.hold_request targ_ahr ON + (ath.core_type = 'ahr' AND targ_ahr.id = atev.target) + WHERE atev.add_time > NOW() - (SELECT MAX(value) FROM ( + SELECT value::INTERVAL FROM actor.org_unit_ancestor_setting( + 'circ.staff.max_visible_event_age', + COALESCE(targ_circ.circ_lib, targ_ahr.pickup_lib) + ) UNION + SELECT '1000 YEARS'::INTERVAL AS value + ) ous) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT * FROM metabib.author_field_entry + UNION ALL + SELECT * FROM metabib.keyword_field_entry + UNION ALL + SELECT * FROM metabib.identifier_field_entry + UNION ALL + SELECT * FROM metabib.title_field_entry + UNION ALL + SELECT * FROM metabib.subject_field_entry + UNION ALL + SELECT * FROM metabib.series_field_entry + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +SELECT usr, + SUM( + CASE + WHEN ( + ((fine_interval >= '1 day' AND due_date >= 'today') OR (fine_interval < '1 day' AND due_date > 'now')) + AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE')) + ) THEN 1 + ELSE 0 + END + ) AS out, + + SUM( + CASE + WHEN ( + ((fine_interval >= '1 day' AND due_date < 'today') OR (fine_interval < '1 day' AND due_date < 'now')) + AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE')) + ) THEN 1 + ELSE 0 + END + ) AS overdue, + + SUM( CASE WHEN (xact_finish IS NULL AND stop_fines = 'LOST') THEN 1 ELSE 0 END) AS lost, + SUM( CASE WHEN stop_fines = 'CLAIMSRETURNED' THEN 1 ELSE 0 END) AS claims_returned, + SUM( CASE WHEN (xact_finish IS NULL AND stop_fines = 'LONGOVERDUE') THEN 1 ELSE 0 END) AS long_overdue + FROM action.circulation + WHERE checkin_time IS NULL + GROUP BY 1 + + + + + + + + + + + + + + + +SELECT usr, + STRING_AGG( + CASE + WHEN ( + ((fine_interval >= '1 day' AND due_date >= 'today') OR (fine_interval < '1 day' AND due_date > 'now')) + AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE')) + ) THEN id::TEXT + ELSE '0' + END + ,',') AS out, + + STRING_AGG( + CASE + WHEN ( + ((fine_interval >= '1 day' AND due_date < 'today') OR (fine_interval < '1 day' AND due_date < 'now')) + AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE')) + ) THEN id::TEXT + ELSE '0' + END + ,',') AS overdue, + + STRING_AGG( CASE WHEN (xact_finish IS NULL AND stop_fines = 'LOST') THEN id::TEXT ELSE '0' END,',') AS lost, + STRING_AGG( CASE WHEN stop_fines = 'CLAIMSRETURNED' THEN id::TEXT ELSE '0' END,',') AS claims_returned, + STRING_AGG( CASE WHEN (xact_finish IS NULL AND stop_fines = 'LONGOVERDUE') THEN id::TEXT ELSE '0' END,',') AS long_overdue + FROM action.circulation + WHERE checkin_time IS NULL + GROUP BY 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + ahr.*, + COALESCE(acplo.position, acpl_ordered.fallback_position) AS + copy_location_order_position, + CASE WHEN au.alias IS NOT NULL THEN + au.alias + ELSE + au.first_given_name + END AS usr_alias_or_first_given_name, + au.first_given_name AS usr_first_given_name, + au.second_given_name AS usr_second_given_name, + au.family_name AS usr_family_name, + au.prefix AS usr_prefix, + au.suffix AS usr_suffix, + au.alias AS usr_alias, + CASE WHEN au.alias IS NOT NULL THEN + au.alias + ELSE + REGEXP_REPLACE(ARRAY_TO_STRING(ARRAY[ + COALESCE(au.family_name, ''), + COALESCE(au.suffix, ''), + ', ', + COALESCE(au.prefix, ''), + COALESCE(au.first_given_name, ''), + COALESCE(au.second_given_name, '') + ], ' '), E'\\s+,', ',') + END AS usr_alias_or_display_name, + REGEXP_REPLACE(ARRAY_TO_STRING(ARRAY[ + COALESCE(au.family_name, ''), + COALESCE(au.suffix, ''), + ', ', + COALESCE(au.prefix, ''), + COALESCE(au.first_given_name, ''), + COALESCE(au.second_given_name, '') + ], ' '), E'\\s+,', ',') AS usr_display_name, + TRIM(acnp.label || ' ' || acn.label || ' ' || acns.label) + AS call_number_label, + siss.label AS issuance_label, + (ahr.usr <> ahr.requestor) AS is_staff_hold, + ahcm_1.copy_count AS potential_copies + FROM action.hold_request ahr + JOIN asset.copy acp ON (acp.id = ahr.current_copy) + JOIN asset.call_number acn ON (acp.call_number = acn.id) + JOIN asset.call_number_prefix acnp ON (acn.prefix = acnp.id) + JOIN asset.call_number_suffix acns ON (acn.suffix = acns.id) + JOIN actor.usr au ON (au.id = ahr.usr) + JOIN ( + SELECT *, (ROW_NUMBER() OVER (ORDER BY name) + 1000000) AS fallback_position + FROM asset.copy_location + ) acpl_ordered ON (acpl_ordered.id = acp.location) + LEFT JOIN actor.usr_standing_penalty ausp + ON (ahr.usr = ausp.usr AND (ausp.stop_date IS NULL OR ausp.stop_date > NOW())) + LEFT JOIN config.standing_penalty csp + ON ( + csp.id = ausp.standing_penalty AND + csp.block_list LIKE '%CAPTURE%' AND ( + (csp.org_depth IS NULL AND ahr.pickup_lib = ausp.org_unit) OR + (csp.org_depth IS NOT NULL AND ahr.pickup_lib IN ( + SELECT id FROM actor.org_unit_descendants(ausp.org_unit, csp.org_depth)) + ) + ) + ) + JOIN ( + SELECT COUNT(target_copy) AS copy_count, hold + FROM action.hold_copy_map + GROUP BY 2 + ) ahcm_1 ON (ahcm_1.hold = ahr.id) + LEFT JOIN serial.issuance siss + ON (ahr.hold_type = 'I' AND siss.id = ahr.target) + LEFT JOIN asset.copy_location_order acplo + ON (acp.location = acplo.location AND + acp.circ_lib = acplo.org) + WHERE + ahr.capture_time IS NULL AND + ahr.cancel_time IS NULL AND + csp.id IS NULL AND + (ahr.expire_time is NULL OR ahr.expire_time > NOW()) AND + acp.status IN (0,7) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT ahr.* FROM action.hold_request ahr JOIN (SELECT current_copy, MAX(capture_time) AS capture_time FROM action.hold_request WHERE capture_time IS NOT NULL AND current_copy IS NOT NULL AND fulfillment_time IS NULL GROUP BY current_copy)x USING (current_copy, capture_time) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + acqf.*, + COALESCE(acqfat.amount, 0.00) AS allocated_total, + COALESCE(acqfst.amount, 0.00) AS spent_total, + COALESCE(acqfet.amount, 0.00) AS encumbrance_total, + COALESCE(acqfcb.amount, 0.00) AS combined_balance + FROM + acq.fund acqf + LEFT JOIN acq.fund_allocation_total acqfat ON (acqfat.fund = acqf.id) + LEFT JOIN acq.fund_spent_total acqfst ON (acqfst.fund = acqf.id) + LEFT JOIN acq.fund_encumbrance_total acqfet ON (acqfet.fund = acqf.id) + LEFT JOIN acq.fund_combined_balance acqfcb ON (acqfcb.fund = acqf.id) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT DISTINCT l.* + FROM action.unfulfilled_hold_loops l + JOIN action.unfulfilled_hold_max_loop m USING (hold) + WHERE l.count = m.max + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + cbrebi.id AS id, -- so we can have a pkey in our view + uvs.id AS session, + uvs.owning_lib, + cbrebi.target_biblio_record_entry + FROM url_verify.session uvs + JOIN container.biblio_record_entry_bucket cbreb + ON (uvs.container = cbreb.id) + JOIN container.biblio_record_entry_bucket_item cbrebi + ON (cbrebi.bucket = cbreb.id) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT b.id, + MAX(dcp.edit_date) AS last_delete_date + FROM biblio.record_entry b + JOIN asset.call_number cn ON (cn.record = b.id) + JOIN asset.copy dcp ON (cn.id = dcp.call_number) + WHERE NOT b.deleted + GROUP BY b.id + HAVING SUM( CASE WHEN NOT dcp.deleted THEN 1 ELSE 0 END) = 0 + + + + + + + + + + + + + + + + + + + + -- -- If we uncomment the RIGHT JOIN against biblio.record_entry, then we'll get a row for every non-deleted bib, whether it has active holds or not. + -- -- If we expect to use pcrud to query against specific bibs, we probably want to do this. However, if we're using this to populate a report, we + -- -- may not. + -- SELECT + -- bre.id AS bib_id, + -- COALESCE( z.copy_count, 0 ) AS copy_count, + -- COALESCE( z.hold_count, 0 ) AS hold_count, + -- COALESCE( z.copy_hold_ratio, 0 ) AS hold_copy_ratio + -- FROM ( + SELECT + y.bre AS id, + COALESCE( x.copy_count, 0 ) AS copy_count, + y.hold_count AS hold_count, + (y.hold_count::REAL / (CASE WHEN x.copy_count = 0 OR x.copy_count IS NULL THEN 0.1 ELSE x.copy_count::REAL END)) AS hold_copy_ratio + FROM ( + SELECT + (SELECT bib_record FROM reporter.hold_request_record r WHERE r.id = h.id LIMIT 1) AS bre, + COUNT(*) AS hold_count + FROM action.hold_request h + WHERE + cancel_time IS NULL + AND fulfillment_time IS NULL + -- AND NOT frozen -- a frozen hold is still a desired hold, eh? + GROUP BY 1 + )y LEFT JOIN ( + SELECT + (SELECT id + FROM biblio.record_entry + WHERE id = (SELECT record FROM asset.call_number WHERE id = call_number and deleted is false) + ) AS bre, + COUNT(*) AS copy_count + FROM asset.copy + JOIN asset.copy_location loc ON (copy.location = loc.id AND loc.holdable) + WHERE copy.holdable + AND NOT copy.deleted + AND copy.status IN ( SELECT id FROM config.copy_status WHERE holdable ) + GROUP BY 1 + )x ON x.bre = y.bre + -- )z RIGHT JOIN ( + -- SELECT id + -- FROM biblio.record_entry + -- WHERE NOT deleted + -- )bre ON (z.bib_id = bre.id) + + + + + + + + + + + + + + + + + + + + + + SELECT *, + CASE WHEN copy_count_at_pickup_library = 0 THEN 'Infinity'::FLOAT ELSE holds_at_pickup_library::FLOAT/copy_count_at_pickup_library END AS pickup_library_ratio, + CASE WHEN copy_count_everywhere = 0 THEN 'Infinity'::FLOAT ELSE holds_everywhere::FLOAT/copy_count_everywhere END AS everywhere_ratio + FROM + (SELECT bib_record as id, pickup_lib, count(DISTINCT ahr.id) AS holds_at_pickup_library, COALESCE(count(DISTINCT ac.id),0) as copy_count_at_pickup_library + FROM + action.hold_request ahr + JOIN reporter.hold_request_record rhrr USING (id) + LEFT JOIN action.hold_copy_map ahcm ON (ahr.id = ahcm.hold) + LEFT JOIN asset.copy ac ON (ahcm.target_copy = ac.id AND ahr.pickup_lib = ac.circ_lib) + WHERE + ahr.cancel_time IS NULL + AND ahr.fulfillment_time IS NULL + GROUP BY bib_record, pickup_lib + )x + JOIN + (SELECT bib_record as id, count(DISTINCT ahr.id) AS holds_everywhere, COALESCE(count(DISTINCT target_copy),0) as copy_count_everywhere + FROM + action.hold_request ahr + JOIN reporter.hold_request_record rhrr USING (id) + LEFT JOIN action.hold_copy_map ahcm ON (ahr.id = ahcm.hold) + WHERE + ahr.cancel_time IS NULL + AND ahr.fulfillment_time IS NULL + GROUP BY bib_record + )y + USING (id) + + + + + + + + + + + + + + + + + + + + + + + + + WITH counts_at_ou AS ( + SELECT rhrr.bib_record AS id, + aou.id AS pickup_lib_or_desc, + COUNT(DISTINCT ahr.id) AS holds_at_or_below, + COALESCE(COUNT(DISTINCT ac.id),0) AS copy_count_at_or_below + FROM actor.org_unit aou + JOIN actor.org_unit_type aout ON (aou.ou_type = aout.id), + action.hold_request ahr + JOIN reporter.hold_request_record rhrr USING (id) + LEFT JOIN action.hold_copy_map ahcm ON (ahr.id = ahcm.hold) + LEFT JOIN asset.copy ac ON (ahcm.target_copy = ac.id) + WHERE ahr.cancel_time IS NULL AND ahr.fulfillment_time IS NULL + AND ac.circ_lib IN (SELECT id FROM actor.org_unit_descendants(aou.id)) + AND (actor.org_unit_ancestor_at_depth(ahr.pickup_lib,aout.depth)).id = (actor.org_unit_ancestor_at_depth(ac.circ_lib,aout.depth)).id + GROUP BY 1, 2 + ) + SELECT x.id, x.pickup_lib_or_desc, x.holds_at_or_below, x.copy_count_at_or_below, + y.holds_everywhere, y.copy_count_everywhere, + CASE WHEN copy_count_at_or_below = 0 THEN 'Infinity'::FLOAT ELSE x.holds_at_or_below::FLOAT/x.copy_count_at_or_below END AS hold_copy_ratio_at_or_below_ou, + CASE WHEN copy_count_everywhere = 0 THEN 'Infinity'::FLOAT ELSE y.holds_everywhere::FLOAT/y.copy_count_everywhere END AS everywhere_ratio + FROM counts_at_ou x + JOIN (SELECT bib_record AS id, count(DISTINCT ahr.id) AS holds_everywhere, COALESCE(count(DISTINCT target_copy),0) as copy_count_everywhere + FROM + action.hold_request ahr + JOIN reporter.hold_request_record rhrr USING (id) + LEFT JOIN action.hold_copy_map ahcm ON (ahr.id = ahcm.hold) + WHERE + ahr.cancel_time IS NULL + AND ahr.fulfillment_time IS NULL + GROUP BY bib_record + )y + USING (id) + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + ac.id, + COALESCE(MAX(actac.xact_start), ac.create_date) AS last_circ_or_create, + MAX(actac.xact_start) AS last_circ + FROM asset.copy ac + LEFT JOIN action.all_circulation actac ON ac.id = actac.target_copy + GROUP BY ac.id + + -- Alternate version, say if you have migrated last checkout information in extend_reporter.legacy_circ_timestamp: + --SELECT + -- ac.id, + -- GREATEST(MAX(actac.xact_start), erlct.last_cko_ts, ac.create_date) AS last_circ_or_create, + -- GREATEST(MAX(actac.xact_start), erlct.last_cko_ts) AS last_circ + --FROM asset.copy ac + -- LEFT JOIN action.all_circulation actac ON ac.id = actac.target_copy + -- LEFT JOIN extend_reporter.legacy_circ_timestamp erlct ON ac.id = erlct.id + --GROUP BY ac.id, ac.create_date, erlct.last_cko_ts + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + po.ordering_agency AS ordering_agency, + po.id AS purchase_order, + li.id AS lineitem, + lid.id AS lineitem_detail, + cpa.id AS claim_policy_action + FROM + acq.lineitem_detail lid + JOIN acq.lineitem li ON (li.id = lid.lineitem) + JOIN acq.purchase_order po ON (po.id = li.purchase_order) + JOIN acq.claim_policy cp ON (li.claim_policy = cp.id) + JOIN acq.claim_policy_action cpa ON ( + cpa.claim_policy = cp.id + + -- we only care about claim policy actions whose claim + -- interval we'd reached or exceeded + AND (NOW() - cpa.action_interval) > po.order_date + + -- filter out all claim policy actions where claim events + -- have occurred on or after the action's action_interval + AND NOT EXISTS ( + SELECT 1 + FROM + acq.claim_event evt + JOIN acq.claim claim ON ( + claim.id = evt.claim + AND claim.lineitem_detail = lid.id + ) + WHERE + evt.event_date >= (po.order_date + cpa.action_interval) + ) + ) + WHERE + lid.cancel_reason IS NULL + AND li.cancel_reason IS NULL -- belt/suspenders + AND po.cancel_reason IS NULL -- belt/suspenders + AND lid.recv_time IS NULL + AND po.state = 'on-order' + ORDER BY 1, 2, 3, 4, 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT * FROM acq.lineitem_summary + WHERE item_count > (invoice_count + cancel_count) + + + + + + + + + + + + + + + + + + + + + SELECT t.* + FROM action.transit_copy t + JOIN actor.org_unit AS s ON (t.source = s.id) + JOIN actor.org_unit AS d ON (t.dest = d.id) + WHERE s.parent_ou <> d.parent_ou + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT copy, SUM(count) AS count, year, is_renewal FROM ( + SELECT + cp.id as copy, + COUNT(circ.id), + EXTRACT(YEAR FROM circ.xact_start) AS year, + (phone_renewal OR desk_renewal OR opac_renewal) as is_renewal + FROM + asset.copy cp + JOIN action.circulation circ ON (cp.id = circ.target_copy) + GROUP BY 1, 3, 4 + UNION ALL + SELECT + cp.id as copy, + COUNT(circ.id), + EXTRACT(YEAR FROM circ.xact_start) AS year, + (phone_renewal OR desk_renewal OR opac_renewal) as is_renewal + FROM + asset.copy cp + JOIN action.aged_circulation circ ON (cp.id = circ.target_copy) + GROUP BY 1, 3, 4 + UNION ALL + SELECT + id as copy, + circ_count, + -1 AS year, + false as is_renewal + FROM + extend_reporter.legacy_circ_count + )x GROUP BY 1, 3, 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + poi.purchase_order AS purchase_order, + ii.invoice AS invoice, + NULL AS lineitem, + poi.id AS po_item, + NULL AS picklist + FROM + acq.po_item poi + JOIN acq.invoice_item ii ON (ii.po_item = poi.id) + UNION SELECT + jub.purchase_order AS purchase_order, + ie.invoice AS invoice, + jub.id AS lineitem, + NULL AS po_item, + jub.picklist AS picklist + FROM + acq.lineitem jub + JOIN acq.invoice_entry ie ON (ie.lineitem = jub.id) + UNION SELECT + ii.purchase_order AS purchase_order, + ii.invoice AS invoice, + NULL AS lineitem, + NULL AS po_item, + NULL AS picklist + FROM + acq.invoice_item ii + WHERE ii.po_item IS NULL + UNION SELECT + ie.purchase_order AS purchase_order, + ie.invoice AS invoice, + NULL AS lineitem, + NULL AS po_item, + NULL AS picklist + FROM + acq.invoice_entry ie + WHERE ie.lineitem IS NULL + UNION SELECT + NULL AS purchase_order, + NULL AS invoice, + jub.id AS lineitem, + NULL AS po_item, + jub.picklist AS picklist + FROM + acq.lineitem jub + WHERE jub.purchase_order IS NULL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT ahcm.target_copy AS id,count(*) AS count + FROM + action.hold_request ahr, + action.hold_copy_map ahcm + WHERE + ahr.cancel_time IS NULL AND + ahr.fulfillment_time IS NULL AND + ahr.capture_time IS NULL AND + ahr.id = ahcm.hold + GROUP BY ahcm.target_copy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.11.0