From c8b772529d10b3007738dc94bd09b9fc31613525 Mon Sep 17 00:00:00 2001 From: Cory Lown Date: Tue, 25 Jul 2023 14:40:25 -0400 Subject: [PATCH 1/3] Set current location from request service point for 'awaiting pickup' items --- config/boot.rb | 1 + lib/folio/status_current_location.rb | 65 +++++++++ lib/folio_record.rb | 25 +--- lib/traject/readers/folio_postgres_reader.rb | 36 +++++ spec/integration/folio_config_spec.rb | 133 +++++++++++++++++++ 5 files changed, 241 insertions(+), 19 deletions(-) create mode 100644 lib/folio/status_current_location.rb 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..09847355c --- /dev/null +++ b/lib/folio/status_current_location.rb @@ -0,0 +1,65 @@ +# 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, :requests + + def initialize(item, requests) + @item = item + @requests = requests + 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 + request_awaiting_pickup&.dig('pickupServicePoint', 'code') + end + + def request_awaiting_pickup + requests.find do |request| + request['itemId'] == item['id'] && + request['status'] == 'Open - Awaiting pickup' + end + end + end +end diff --git a/lib/folio_record.rb b/lib/folio_record.rb index bff276035..6ecafc426 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, requests).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, requests).current_location SirsiHolding.new( id: parent_item['id'], call_number: holding['callNumber'], @@ -205,6 +205,10 @@ def pieces @pieces ||= record.fetch('pieces') { client.pieces(instance_id:) }.compact end + def requests + record['requests'] || [] + end + def statistical_codes @statistical_codes ||= instance.fetch('statisticalCodes') do my_ids = client.instance(instance_id:).fetch('statisticalCodeIds') @@ -343,22 +347,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..722a3fe9c 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? @@ -130,6 +148,10 @@ def each } if holding.dig('boundWith', 'holding', 'effectiveLocationId') end + data['requests'].each do |request| + request['pickupServicePoint'] = service_points[request['pickupServicePointId']] + end + yield FolioRecord.new(data) end end @@ -259,6 +281,17 @@ def sql_query(conditions, addl_from: nil) 'instructorObjects', cl.jsonb #> '{instructorObjects}' ) ) FILTER (WHERE cc.id IS NOT NULL), + '[]'::jsonb), + 'requests', + COALESCE( + jsonb_agg( + DISTINCT jsonb_build_object( + 'id', request.id, + 'itemId', request.jsonb ->> 'itemId', + 'status', request.jsonb ->> 'status', + 'pickupServicePointId', request.jsonb ->> 'pickupServicePointId' + ) + ) FILTER (WHERE request.id IS NOT NULL), '[]'::jsonb) ) FROM sul_mod_inventory_storage.instance vi @@ -317,6 +350,9 @@ 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 ->> 'instanceId')::uuid = vi.id #{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..8de8ac3b8 100644 --- a/spec/integration/folio_config_spec.rb +++ b/spec/integration/folio_config_spec.rb @@ -41,11 +41,13 @@ let(:client) { instance_double(FolioClient, instance: client_instance_response, statistical_codes: statistical_codes_response) } let(:items_and_holdings) { {} } let(:holding_summaries) { [] } + let(:requests) { [] } before do allow(folio_record).to receive(:items_and_holdings).and_return(items_and_holdings) allow(folio_record).to receive(:courses).and_return([]) allow(folio_record).to receive(:holding_summaries).and_return(holding_summaries) + allow(folio_record).to receive(:requests).and_return(requests) end it 'maps the record with sirsi fields' do @@ -476,6 +478,137 @@ 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, + '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 + let(:requests) do + [{ 'id' => '7c8e3f57-6f1b-4d59-a8c6-9b51e32edd38', + 'itemId' => '7fdf7094-d30a-5f70-b23e-bc420a82a1d7', + 'status' => 'Open - Awaiting pickup', + 'pickupServicePoint' => + { 'code' => 'RUMSEY-MAP', + 'name' => 'David Rumsey Map Center', + 'discoveryDisplayName' => 'David Rumsey Map Center' }, + 'pickupServicePointId' => 'b6987737-1e63-44cc-bfb1-2bcf044adcd7' }] + 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 From 5ca82cee51130c4ab0518cf9ec47076b0d448196 Mon Sep 17 00:00:00 2001 From: Cory Lown Date: Thu, 27 Jul 2023 10:54:57 -0400 Subject: [PATCH 2/3] Filter for requests where status is Open - Awaiting pickup --- lib/traject/readers/folio_postgres_reader.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/traject/readers/folio_postgres_reader.rb b/lib/traject/readers/folio_postgres_reader.rb index 722a3fe9c..5787dca83 100644 --- a/lib/traject/readers/folio_postgres_reader.rb +++ b/lib/traject/readers/folio_postgres_reader.rb @@ -353,6 +353,7 @@ def sql_query(conditions, addl_from: nil) -- Requests relation LEFT JOIN sul_mod_circulation_storage.request request ON (request.jsonb ->> 'instanceId')::uuid = vi.id + AND request.jsonb ->> 'status' = 'Open - Awaiting pickup' #{addl_from} WHERE #{conditions.join(' AND ')} GROUP BY vi.id From c6a27c7b40bc83205db231c0658b76fd25c2c5dd Mon Sep 17 00:00:00 2001 From: Cory Lown Date: Thu, 27 Jul 2023 16:34:14 -0400 Subject: [PATCH 3/3] Move request to item --- lib/folio/status_current_location.rb | 14 +++------- lib/folio_record.rb | 8 ++---- lib/traject/readers/folio_postgres_reader.rb | 28 ++++++++------------ spec/integration/folio_config_spec.rb | 20 ++++++-------- 4 files changed, 24 insertions(+), 46 deletions(-) diff --git a/lib/folio/status_current_location.rb b/lib/folio/status_current_location.rb index 09847355c..bffd88f18 100644 --- a/lib/folio/status_current_location.rb +++ b/lib/folio/status_current_location.rb @@ -5,11 +5,10 @@ module Folio # and any requests associated with the item's instance record # and creates an equivalent Symphony current location class StatusCurrentLocation - attr_reader :item, :requests + attr_reader :item - def initialize(item, requests) + def initialize(item) @item = item - @requests = requests end def current_location @@ -52,14 +51,7 @@ def symphony_pickup_location_code end def service_point_code - request_awaiting_pickup&.dig('pickupServicePoint', 'code') - end - - def request_awaiting_pickup - requests.find do |request| - request['itemId'] == item['id'] && - request['status'] == 'Open - Awaiting pickup' - end + item&.dig('request', 'pickupServicePoint', 'code') end end end diff --git a/lib/folio_record.rb b/lib/folio_record.rb index 6ecafc426..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::StatusCurrentLocation.new(item, requests).current_location + 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::StatusCurrentLocation.new(parent_item, requests).current_location + current_location ||= Folio::StatusCurrentLocation.new(parent_item).current_location SirsiHolding.new( id: parent_item['id'], call_number: holding['callNumber'], @@ -205,10 +205,6 @@ def pieces @pieces ||= record.fetch('pieces') { client.pieces(instance_id:) }.compact end - def requests - record['requests'] || [] - end - def statistical_codes @statistical_codes ||= instance.fetch('statisticalCodes') do my_ids = client.instance(instance_id:).fetch('statisticalCodeIds') diff --git a/lib/traject/readers/folio_postgres_reader.rb b/lib/traject/readers/folio_postgres_reader.rb index 5787dca83..99fd35576 100644 --- a/lib/traject/readers/folio_postgres_reader.rb +++ b/lib/traject/readers/folio_postgres_reader.rb @@ -134,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| @@ -148,10 +150,6 @@ def each } if holding.dig('boundWith', 'holding', 'effectiveLocationId') end - data['requests'].each do |request| - request['pickupServicePoint'] = service_points[request['pickupServicePointId']] - end - yield FolioRecord.new(data) end end @@ -209,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), @@ -281,17 +286,6 @@ def sql_query(conditions, addl_from: nil) 'instructorObjects', cl.jsonb #> '{instructorObjects}' ) ) FILTER (WHERE cc.id IS NOT NULL), - '[]'::jsonb), - 'requests', - COALESCE( - jsonb_agg( - DISTINCT jsonb_build_object( - 'id', request.id, - 'itemId', request.jsonb ->> 'itemId', - 'status', request.jsonb ->> 'status', - 'pickupServicePointId', request.jsonb ->> 'pickupServicePointId' - ) - ) FILTER (WHERE request.id IS NOT NULL), '[]'::jsonb) ) FROM sul_mod_inventory_storage.instance vi @@ -352,7 +346,7 @@ def sql_query(conditions, addl_from: nil) ON parentHolding.instanceid = parentInstance.id -- Requests relation LEFT JOIN sul_mod_circulation_storage.request request - ON (request.jsonb ->> 'instanceId')::uuid = vi.id + ON (request.jsonb ->> 'itemId')::uuid = item.id AND request.jsonb ->> 'status' = 'Open - Awaiting pickup' #{addl_from} WHERE #{conditions.join(' AND ')} diff --git a/spec/integration/folio_config_spec.rb b/spec/integration/folio_config_spec.rb index 8de8ac3b8..a0f68123a 100644 --- a/spec/integration/folio_config_spec.rb +++ b/spec/integration/folio_config_spec.rb @@ -41,13 +41,11 @@ let(:client) { instance_double(FolioClient, instance: client_instance_response, statistical_codes: statistical_codes_response) } let(:items_and_holdings) { {} } let(:holding_summaries) { [] } - let(:requests) { [] } before do allow(folio_record).to receive(:items_and_holdings).and_return(items_and_holdings) allow(folio_record).to receive(:courses).and_return([]) allow(folio_record).to receive(:holding_summaries).and_return(holding_summaries) - allow(folio_record).to receive(:requests).and_return(requests) end it 'maps the record with sirsi fields' do @@ -487,6 +485,14 @@ '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', @@ -590,16 +596,6 @@ { 'items' => items, 'holdings' => holdings } end - let(:requests) do - [{ 'id' => '7c8e3f57-6f1b-4d59-a8c6-9b51e32edd38', - 'itemId' => '7fdf7094-d30a-5f70-b23e-bc420a82a1d7', - 'status' => 'Open - Awaiting pickup', - 'pickupServicePoint' => - { 'code' => 'RUMSEY-MAP', - 'name' => 'David Rumsey Map Center', - 'discoveryDisplayName' => 'David Rumsey Map Center' }, - 'pickupServicePointId' => 'b6987737-1e63-44cc-bfb1-2bcf044adcd7' }] - end before do allow(client).to receive(:pieces).and_return([])