diff --git a/config/boot.rb b/config/boot.rb index 70f90871e..5d92bdba4 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -38,6 +38,7 @@ require 'folio_record' require 'folio/eresource_holdings_builder' require 'folio/mhld_builder' +require 'folio/status_current_location' require 'locations_map' require 'marc_links' require 'mhld_field' diff --git a/lib/folio/status_current_location.rb b/lib/folio/status_current_location.rb new file mode 100644 index 000000000..bffd88f18 --- /dev/null +++ b/lib/folio/status_current_location.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Folio + # Folio::StatusCurrentLocation takes an item + # and any requests associated with the item's instance record + # and creates an equivalent Symphony current location + class StatusCurrentLocation + attr_reader :item + + def initialize(item) + @item = item + end + + def current_location + case status + when 'Checked out', 'Claimed returned', 'Aged to lost' + 'CHECKEDOUT' + when 'Awaiting pickup' + symphony_pickup_location_code + when 'In process', 'In process (non-requestable)' + 'INPROCESS' + when 'In transit', 'Awaiting delivery' + 'INTRANSIT' + when 'Missing', 'Long missing' + 'MISSING' + when 'On order' + 'ON-ORDER' + end + end + + private + + def status + item['status'] + end + + # ARS-LOAN, RUM-LOAN, and SPE-LOAN each receives a + # special label in SearchWorks. Other codes are generically + # mapped to GRE-LOAN since they all receive the same label. + def symphony_pickup_location_code + case service_point_code + when 'ARS' + 'ARS-LOAN' + when 'RUMSEY-MAP' + 'RUM-LOAN' + when 'SPEC' + 'SPE-LOAN' + else + 'GRE-LOAN' + end + end + + def service_point_code + item&.dig('request', 'pickupServicePoint', 'code') + end + end +end diff --git a/lib/folio_record.rb b/lib/folio_record.rb index bff276035..3412a2f70 100644 --- a/lib/folio_record.rb +++ b/lib/folio_record.rb @@ -86,7 +86,7 @@ def sirsi_holdings library_code, home_location_code = LocationsMap.for(item_location_code) _current_library, current_location = LocationsMap.for(item.dig('location', 'temporaryLocation', 'code')) - current_location ||= folio_status_to_location(item['status']) + current_location ||= Folio::StatusCurrentLocation.new(item).current_location SirsiHolding.new( id: item['id'], @@ -114,7 +114,7 @@ def bound_with_holdings library_code, home_location_code = LocationsMap.for(item_location_code) _current_library, current_location = LocationsMap.for(parent_item.dig('location', 'temporaryLocation', 'code')) - current_location ||= folio_status_to_location(parent_item['status']) + current_location ||= Folio::StatusCurrentLocation.new(parent_item).current_location SirsiHolding.new( id: parent_item['id'], call_number: holding['callNumber'], @@ -343,22 +343,5 @@ def folio_electronic_access_marc_field(eresource) MARC::DataField.new('856', '4', ind2, ['u', eresource['uri']], ['y', eresource['linkText']], ['z', eresource['publicNote']]) end - - def folio_status_to_location(status) - case status - when 'Checked out', 'Claimed returned', 'Aged to lost' - 'CHECKEDOUT' - when 'Awaiting pickup', 'Awaiting delivery' - 'GRE-LOAN' - when 'In process', 'In process (non-requestable)' - 'INPROCESS' - when 'In transit' - 'INTRANSIT' - when 'Missing', 'Long missing' - 'MISSING' - when 'On order' - 'ON-ORDER' - end - end end # rubocop:enable Metrics/ClassLength diff --git a/lib/traject/readers/folio_postgres_reader.rb b/lib/traject/readers/folio_postgres_reader.rb index 20c3d3a55..99fd35576 100644 --- a/lib/traject/readers/folio_postgres_reader.rb +++ b/lib/traject/readers/folio_postgres_reader.rb @@ -87,6 +87,24 @@ def locations end end + def service_points + @service_points ||= begin + response = @connection.exec <<-SQL + SELECT service_point.id AS id, + jsonb_build_object( + 'id', service_point.id, + 'code', service_point.jsonb ->> 'code', + 'name', service_point.jsonb ->> 'name', + 'pickupLocation', COALESCE((service_point.jsonb ->> 'pickupLocation')::bool, false), + 'discoveryDisplayName', service_point.jsonb ->> 'discoveryDisplayName' + ) AS jsonb + FROM sul_mod_inventory_storage.service_point service_point + SQL + + response.map { |row| JSON.parse(row['jsonb']) }.index_by { |service_point| service_point['id'] } + end + end + def each return to_enum(:each) unless block_given? @@ -116,6 +134,8 @@ def each 'permanentLocation' => locations[item['permanentLocationId']], 'temporaryLocation' => locations[item['temporaryLocationId']] }.compact + + item['request']['pickupServicePoint'] = service_points[item['request']['pickupServicePointId']] if item['request'] end data['holdings'].each do |holding| @@ -187,7 +207,14 @@ def sql_query(conditions, addl_from: nil) 'electronicAccess', COALESCE(sul_mod_inventory_storage.getElectronicAccessName(COALESCE(item.jsonb #> '{electronicAccess}', '[]'::jsonb)), '[]'::jsonb), 'administrativeNotes', '[]'::jsonb, 'circulationNotes', COALESCE((SELECT jsonb_agg(e) FROM jsonb_array_elements(item.jsonb -> 'circulationNotes') AS e WHERE NOT COALESCE((e ->> 'staffOnly')::bool, false)), '[]'::jsonb), - 'notes', COALESCE((SELECT jsonb_agg(e || jsonb_build_object('itemNoteTypeName', ( SELECT jsonb ->> 'name' FROM sul_mod_inventory_storage.item_note_type WHERE id = nullif(e ->> 'itemNoteTypeId','')::uuid ))) FROM jsonb_array_elements(item.jsonb -> 'notes') AS e WHERE NOT COALESCE((e ->> 'staffOnly')::bool, false)), '[]'::jsonb) + 'notes', COALESCE((SELECT jsonb_agg(e || jsonb_build_object('itemNoteTypeName', ( SELECT jsonb ->> 'name' FROM sul_mod_inventory_storage.item_note_type WHERE id = nullif(e ->> 'itemNoteTypeId','')::uuid ))) FROM jsonb_array_elements(item.jsonb -> 'notes') AS e WHERE NOT COALESCE((e ->> 'staffOnly')::bool, false)), '[]'::jsonb), + 'request', CASE WHEN request.id IS NOT NULL THEN + jsonb_build_object( + 'id', request.id, + 'status', request.jsonb ->> 'status', + 'pickupServicePointId', request.jsonb ->> 'pickupServicePointId' + ) + END ) ) FILTER (WHERE item.id IS NOT NULL), '[]'::jsonb), @@ -317,6 +344,10 @@ def sql_query(conditions, addl_from: nil) ON parentItem.holdingsRecordId = parentHolding.id LEFT JOIN sul_mod_inventory_storage.instance parentInstance ON parentHolding.instanceid = parentInstance.id + -- Requests relation + LEFT JOIN sul_mod_circulation_storage.request request + ON (request.jsonb ->> 'itemId')::uuid = item.id + AND request.jsonb ->> 'status' = 'Open - Awaiting pickup' #{addl_from} WHERE #{conditions.join(' AND ')} GROUP BY vi.id diff --git a/spec/integration/folio_config_spec.rb b/spec/integration/folio_config_spec.rb index 3aa893903..a0f68123a 100644 --- a/spec/integration/folio_config_spec.rb +++ b/spec/integration/folio_config_spec.rb @@ -476,6 +476,135 @@ it { expect(result['item_display'].find { |h| h.match?(/INTRANSIT/) }).to be_present } end + + context 'item is awaiting pickup' do + let(:items) do + [{ 'id' => '7fdf7094-d30a-5f70-b23e-bc420a82a1d7', + 'hrid' => 'ai645341_1_1', + 'notes' => [], + 'status' => 'Awaiting pickup', + 'barcode' => '36105080746311', + '_version' => 3, + 'request' => { 'id' => '7c8e3f57-6f1b-4d59-a8c6-9b51e32edd38', + 'status' => 'Open - Awaiting pickup', + 'pickupServicePoint' => + { 'pickupServicePointId' => 'b6987737-1e63-44cc-bfb1-2bcf044adcd7', + 'code' => 'RUMSEY-MAP', + 'name' => 'David Rumsey Map Center', + 'pickupLocation' => true, + 'discoveryDisplayName' => 'David Rumsey Map Center' } }, + 'location' => + { 'effectiveLocation' => + { 'id' => 'bb7bd5d2-5b97-4fc6-9dfd-b26a1c14e43f', + 'code' => 'SAL-PAGE', + 'name' => 'SAL Stacks', + 'campus' => + { 'id' => 'c365047a-51f2-45ce-8601-e421ca3615c5', + 'code' => 'SUL', + 'name' => 'Stanford Libraries' }, + 'details' => { 'scanServicePointCode' => 'GREEN' }, + 'library' => + { 'id' => '00d012b4-d5ee-422c-9f38-3457e0ddd1ed', + 'code' => 'SAL', + 'name' => 'Stanford Auxiliary Library 1&2' }, + 'isActive' => true, + 'institution' => + { 'id' => '8d433cdd-4e8f-4dc1-aa24-8a4ddb7dc929', + 'code' => 'SU', + 'name' => 'Stanford University' } }, + 'permanentLocation' => + { 'id' => 'bb7bd5d2-5b97-4fc6-9dfd-b26a1c14e43f', + 'code' => 'SAL-PAGE', + 'name' => 'SAL Stacks', + 'campus' => + { 'id' => 'c365047a-51f2-45ce-8601-e421ca3615c5', + 'code' => 'SUL', + 'name' => 'Stanford Libraries' }, + 'details' => { 'scanServicePointCode' => 'GREEN' }, + 'library' => + { 'id' => '00d012b4-d5ee-422c-9f38-3457e0ddd1ed', + 'code' => 'SAL', + 'name' => 'Stanford Auxiliary Library 1&2' }, + 'isActive' => true, + 'institution' => + { 'id' => '8d433cdd-4e8f-4dc1-aa24-8a4ddb7dc929', + 'code' => 'SU', + 'name' => 'Stanford University' } }, + 'temporaryLocation' => nil } }] + end + let(:holdings) do + [{ 'id' => '9c7b3dca-1619-5210-9bd1-6df775986b81', + 'hrid' => 'ah645341_1', + 'notes' => [], + '_version' => 1, + 'location' => + { 'effectiveLocation' => + { 'id' => 'bb7bd5d2-5b97-4fc6-9dfd-b26a1c14e43f', + 'code' => 'SAL-PAGE', + 'name' => 'SAL Stacks', + 'campus' => + { 'id' => 'c365047a-51f2-45ce-8601-e421ca3615c5', + 'code' => 'SUL', + 'name' => 'Stanford Libraries' }, + 'details' => { 'scanServicePointCode' => 'GREEN' }, + 'library' => + { 'id' => '00d012b4-d5ee-422c-9f38-3457e0ddd1ed', + 'code' => 'SAL', + 'name' => 'Stanford Auxiliary Library 1&2' }, + 'isActive' => true, + 'institution' => + { 'id' => '8d433cdd-4e8f-4dc1-aa24-8a4ddb7dc929', + 'code' => 'SU', + 'name' => 'Stanford University' } }, + 'permanentLocation' => + { 'id' => 'bb7bd5d2-5b97-4fc6-9dfd-b26a1c14e43f', + 'code' => 'SAL-PAGE', + 'name' => 'SAL Stacks', + 'campus' => + { 'id' => 'c365047a-51f2-45ce-8601-e421ca3615c5', + 'code' => 'SUL', + 'name' => 'Stanford Libraries' }, + 'details' => { 'scanServicePointCode' => 'GREEN' }, + 'library' => + { 'id' => '00d012b4-d5ee-422c-9f38-3457e0ddd1ed', + 'code' => 'SAL', + 'name' => 'Stanford Auxiliary Library 1&2' }, + 'isActive' => true, + 'institution' => + { 'id' => '8d433cdd-4e8f-4dc1-aa24-8a4ddb7dc929', + 'code' => 'SU', + 'name' => 'Stanford University' } }, + 'temporaryLocation' => nil }, + 'formerIds' => [], + 'callNumber' => 'D810.S8 C31 A32', + 'instanceId' => 'c08db92b-c343-5955-abb1-b739ab186ecb', + 'holdingsType' => + { 'id' => '03c9c400-b9e3-4a07-ac0e-05ab470233ed', + 'name' => 'Monograph', + 'source' => 'folio' }, + 'holdingsItems' => [], + 'callNumberType' => + { 'id' => '95467209-6d7b-468b-94df-0f5d7ad2747d', + 'name' => 'Library of Congress classification', + 'source' => 'folio' }, + 'holdingsStatements' => [], + 'suppressFromDiscovery' => false, + 'holdingsStatementsForIndexes' => [], + 'holdingsStatementsForSupplements' => [] }] + end + let(:items_and_holdings) do + { 'items' => items, + 'holdings' => holdings } + end + + before do + allow(client).to receive(:pieces).and_return([]) + end + + it 'uses the pickup location of the request to generate a current location value' do + expect(result['item_display'].find { |h| h.match?(/RUM-LOAN/) }).to be_present + end + end end describe 'mhld_display' do