• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / orangelight / 033f0565-37fb-4350-bd4a-68e11c1bcc99

pending completion
033f0565-37fb-4350-bd4a-68e11c1bcc99

Pull #3431

circleci

Jane Sandberg
Remove borrowdirect from requests and orangelight
Pull Request #3431: Remove borrowdirect from requests and orangelight

12 of 12 new or added lines in 5 files covered. (100.0%)

7 existing lines in 3 files now uncovered.

5266 of 5554 relevant lines covered (94.81%)

1516.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.09
/app/models/requests/request.rb
1
# frozen_string_literal: true
2
require 'faraday'
3✔
3

4
module Requests
3✔
5
  class Request
3✔
6
    attr_accessor :email
3✔
7
    attr_accessor :user_name
3✔
8
    attr_reader :system_id
3✔
9
    attr_reader :source
3✔
10
    attr_reader :mfhd
3✔
11
    attr_reader :patron
3✔
12
    attr_reader :doc
3✔
13
    attr_reader :requestable
3✔
14
    attr_reader :requestable_unrouted
3✔
15
    attr_reader :holdings
3✔
16
    attr_reader :location
3✔
17
    attr_reader :location_code
3✔
18
    attr_reader :items
3✔
19
    attr_reader :pick_ups
3✔
20
    alias default_pick_ups pick_ups
3✔
21
    delegate :ctx, :openurl_ctx_kev, to: :@ctx_obj
3✔
22
    delegate :eligible_for_library_services?, to: :patron
3✔
23

24
    include Requests::Bibdata
3✔
25
    include Requests::Scsb
3✔
26

27
    # @option opts [String] :system_id A bib record id or a special collection ID value
28
    # @option opts [Fixnum] :mfhd alma holding id
29
    # @option opts [Patron] :patron current Patron object
30
    # @option opts [String] :source represents system that directed user to request form. i.e.
31
    def initialize(system_id:, mfhd:, patron: nil, source: nil)
3✔
32
      @system_id = system_id
380✔
33
      @doc = solr_doc(system_id)
380✔
34
      @holdings = JSON.parse(doc[:holdings_1display] || '{}')
380✔
35
      # scsb items are the only ones that come in without a MFHD parameter from the catalog now
36
      # set it for them, because they only ever have one location
37
      @mfhd = mfhd || @holdings.keys.first
380✔
38
      @patron = patron
380✔
39
      @source = source
380✔
40
      ### These should be re-factored
41
      @location_code = @holdings[@mfhd]["location_code"] if @holdings[@mfhd].present?
380✔
42
      @location = load_location
380✔
43
      @items = load_items
380✔
44
      @pick_ups = build_pick_ups
380✔
45
      @requestable_unrouted = build_requestable
380✔
46
      @requestable = route_requests(@requestable_unrouted)
380✔
47
      @ctx_obj = Requests::SolrOpenUrlContext.new(solr_doc: @doc)
380✔
48
    end
49

50
    delegate :user, to: :patron
3✔
51

52
    # Is this a partner system id
53
    def partner_system_id?
3✔
54
      return true if /^SCSB-\d+/.match?(system_id.to_s)
377✔
55
    end
56

57
    def requestable?
3✔
58
      requestable.size.positive?
93✔
59
    end
60

61
    def single_aeon_requestable?
3✔
62
      requestable.size == 1 && first_filtered_requestable&.services&.include?('aeon')
103✔
63
    end
64

65
    def first_filtered_requestable
3✔
66
      requestable&.first
184✔
67
    end
68

69
    # Does this request object have any pageable items?
70
    def any_pageable?
3✔
71
      services = requestable.map(&:services).flatten
4✔
72
      services.uniq!
4✔
73
      services.include? 'paging'
4✔
74
    end
75

76
    # Does this request object have any available copies?
77
    def any_loanable_copies?
3✔
78
      requestable_unrouted.any? do |requestable|
371✔
79
        !(requestable.charged? || (requestable.aeon? || !requestable.circulates? || requestable.partner_holding? || requestable.on_reserve?))
1,515✔
80
      end
81
    end
82

83
    def any_enumerated?
3✔
84
      requestable_unrouted.any?(&:enumerated?)
1✔
85
    end
86

87
    def route_requests(requestable_items)
3✔
88
      routed_requests = []
380✔
89
      return [] if requestable_items.blank?
380✔
90
      any_loanable = any_loanable_copies?
367✔
91
      requestable_items.each do |requestable|
367✔
92
        router = Requests::Router.new(requestable:, user: patron.user, any_loanable:)
3,337✔
93
        routed_requests << router.routed_request
3,337✔
94
      end
95
      routed_requests
367✔
96
    end
97

98
    def serial?
3✔
99
      doc[:format].present? && doc[:format].include?('Journal')
336✔
100
    end
101

102
    def recap?
3✔
103
      return false if location.blank?
287✔
104
      location[:remote_storage] == "recap_rmt"
275✔
105
    end
106

107
    def all_items_online?
3✔
108
      requestable.map(&:online?).reduce(:&)
83✔
109
    end
110

111
    # returns nil if there are no attached items
112
    # if mfhd set returns only items associated with that mfhd
113
    # if no mfhd returns items sorted by mfhd
114
    def load_items
3✔
115
      return nil if thesis? || numismatics?
380✔
116
      mfhd_items = if @mfhd && serial?
337✔
117
                     load_serial_items
64✔
118
                   else
119
                     # load_items_by_bib_id
120
                     load_items_by_mfhd
273✔
121
                   end
122
      mfhd_items.empty? ? nil : mfhd_items.with_indifferent_access
337✔
123
    end
124

125
    def thesis?
3✔
126
      doc[:holdings_1display].present? && parse_json(doc[:holdings_1display]).key?('thesis')
541✔
127
    end
128

129
    def numismatics?
3✔
130
      doc[:holdings_1display].present? && parse_json(doc[:holdings_1display]).key?('numismatics')
519✔
131
    end
132

133
    # returns basic metadata for display on the request from via solr_doc values
134
    # Fields to return all keys are arrays
135
    ## Add more fields here as needed
136
    def display_metadata
3✔
137
      {
138
        title: doc["title_citation_display"],
183✔
139
        author: doc["author_citation_display"],
140
        isbn: doc["isbn_s"]
141
      }
142
    end
143

144
    def language
3✔
145
      doc["language_iana_s"]&.first
169✔
146
    end
147

148
    # should probably happen in the initializer
149
    def build_pick_ups
3✔
150
      pick_up_locations = []
380✔
151
      Requests::BibdataService.delivery_locations.each_value do |pick_up|
380✔
152
        pick_up_locations << { label: pick_up["label"], gfa_pickup: pick_up["gfa_pickup"], pick_up_location_code: pick_up["library"]["code"] || 'firestone', staff_only: pick_up["staff_only"] } if pick_up["pickup_location"] == true
8,717✔
153
      end
154
      # pick_up_locations.sort_by! { |loc| loc[:label] }
155
      sort_pick_ups(pick_up_locations)
380✔
156
    end
157

158
    def ill_eligible?
3✔
UNCOV
159
      requestable.any? { |r| r.services.include? 'ill' }
×
160
    end
161

162
    def isbn_numbers?
3✔
163
      if doc.key? 'isbn_s'
2✔
164
        true
1✔
165
      else
166
        false
1✔
167
      end
168
    end
169

170
    def isbn_numbers
3✔
UNCOV
171
      doc['isbn_s']
×
172
    end
173

174
    def other_id
3✔
175
      doc['other_id_s'].first
35✔
176
    end
177

178
    def scsb_location
3✔
179
      doc['location_code_s'].first
112✔
180
    end
181

182
    def off_site?
3✔
183
      return false if location['library'].nil? || location['library']['code'].nil?
10✔
184
      library_code = location[:library][:code]
10✔
185
      library_code == 'recap' || library_code == 'marquand' || library_code == 'annex'
10✔
186
    end
187

188
    private
3✔
189

190
      ### builds a list of possible requestable items
191
      # returns a collection of requestable objects or nil
192
      def build_requestable
3✔
193
        return [] if doc.blank?
380✔
194
        if partner_system_id?
377✔
195
          build_scsb_requestable
34✔
196
        elsif !items.nil?
343✔
197
          build_requestable_with_items
287✔
198
        else
199
          build_requestable_from_data
56✔
200
        end
201
      end
202

203
      def availability_data(id)
3✔
204
        @availability_data ||= items_by_id(id, scsb_owning_institution(scsb_location))
112✔
205
      end
206

207
      def build_scsb_requestable
3✔
208
        requestable_items = []
34✔
209
        ## scsb processing
210
        ## If mfhd present look for only that
211
        ## sort items by keys
212
        ## send query for availability by barcode
213
        ## overlay availability to the 'status' field
214
        ## make sure other fields map to the current data model for item in requestable
215
        ## adjust router to understand SCSB status
216
        holdings.each do |id, values|
34✔
217
          requestable_items = build_holding_scsb_items(id:, values:, availability_data: availability_data(other_id), requestable_items:)
34✔
218
        end
219
        requestable_items
34✔
220
      end
221

222
      def build_holding_scsb_items(id:, values:, availability_data:, requestable_items:)
3✔
223
        return requestable_items if values['items'].nil?
34✔
224
        barcodesort = build_barcode_sort(items: values['items'], availability_data:)
34✔
225
        barcodesort.each_value do |item|
34✔
226
          item['location_code'] = location_code
65✔
227
          params = build_requestable_params(item: item.with_indifferent_access, holding: { id.to_sym.to_s => holdings[id] },
65✔
228
                                            location:)
229
          requestable_items << Requests::Requestable.new(**params)
65✔
230
        end
231
        requestable_items
34✔
232
      end
233

234
      def build_barcode_sort(items:, availability_data:)
3✔
235
        barcodesort = {}
112✔
236
        items.each do |item|
112✔
237
          item[:status_label] = status_label(item:, availability_data:)
1,145✔
238
          barcodesort[item['barcode']] = item
1,145✔
239
        end
240
        availability_data.each do |item|
112✔
241
          barcode_item = barcodesort[item['itemBarcode']]
815✔
242
          next if barcode_item.nil? || barcode_item["status_source"] == "work_order" || item['errorMessage'].present?
815✔
243
          barcode_item['status_label'] = item['itemAvailabilityStatus']
768✔
244
          barcode_item['status'] = nil
768✔
245
        end
246
        barcodesort
112✔
247
      end
248

249
      def status_label(item:, availability_data:)
3✔
250
        if item["status_source"] != "work_order" && availability_data.empty?
1,145✔
251
          "Not Available"
319✔
252
        elsif item["status_source"] != "work_order" && item[:status_label] == 'Item in place' && availability_data.size == 1 && availability_data.first['errorMessage'] == "Bib Id doesn't exist in SCSB database."
826✔
253
          "In Process"
2✔
254
        else
255
          item[:status_label]
824✔
256
        end
257
      end
258

259
      def build_requestable_with_items
3✔
260
        requestable_items = []
287✔
261
        barcodesort = {}
287✔
262
        barcodesort = build_barcode_sort(items: items[mfhd], availability_data: availability_data(system_id)) if recap?
287✔
263
        items.each do |holding_id, mfhd_items|
287✔
264
          next if mfhd != holding_id
287✔
265
          requestable_items = build_requestable_from_mfhd_items(requestable_items:, holding_id:, mfhd_items:, barcodesort:)
287✔
266
        end
267
        requestable_items.compact
287✔
268
      end
269

270
      def build_requestable_from_data
3✔
271
        return if doc[:holdings_1display].nil?
56✔
272
        @mfhd ||= 'thesis' if thesis?
56✔
273
        @mfhd ||= 'numismatics' if numismatics?
56✔
274
        return [] if holdings[@mfhd].blank?
56✔
275

276
        [build_requestable_from_holding(@mfhd, holdings[@mfhd].with_indifferent_access)]
55✔
277
      end
278

279
      def build_requestable_from_mfhd_items(requestable_items:, holding_id:, mfhd_items:, barcodesort:)
3✔
280
        if !mfhd_items.empty?
287✔
281
          mfhd_items.each do |item|
254✔
282
            requestable_items << build_requestable_mfhd_item(requestable_items, holding_id, item, barcodesort)
3,199✔
283
          end
284
        else
285
          requestable_items << build_requestable_from_holding(holding_id, holdings[holding_id])
33✔
286
        end
287
        requestable_items.compact
287✔
288
      end
289

290
      def build_requestable_mfhd_item(_requestable_items, holding_id, item, barcodesort)
3✔
291
        return if item['on_reserve'] == 'Y'
3,199✔
292

293
        item_loc = item_current_location(item)
3,192✔
294
        current_location = get_current_location(item_loc:)
3,192✔
295
        item['status_label'] = barcodesort[item['barcode']][:status_label] unless barcodesort.empty?
3,192✔
296
        calculate_holding = if item["in_temp_library"] && item["temp_location_code"] != "RES_SHARE$IN_RS_REQ"
3,192✔
297
                              { holding_id.to_sym.to_s => holdings[item_loc] }
9✔
298
                            else
299
                              { holding_id.to_sym.to_s => holdings[holding_id] }
3,183✔
300
                            end
301
        params = build_requestable_params(
3,192✔
302
          item: item.with_indifferent_access,
303
          holding: calculate_holding,
304
          location: current_location
305
        )
306
        Requests::Requestable.new(**params)
3,192✔
307
      end
308

309
      def get_current_location(item_loc:)
3✔
310
        if item_loc != location_code
3,192✔
311
          @temp_locations ||= {}
11✔
312
          @temp_locations[item_loc] = get_location_data(item_loc) if @temp_locations[item_loc].blank?
11✔
313
          @temp_locations[item_loc]
11✔
314
        else
315
          location
3,181✔
316
        end
317
      end
318

319
      def build_requestable_from_holding(holding_id, holding)
3✔
320
        return if holding.blank?
88✔
321
        params = build_requestable_params(holding: { holding_id.to_sym.to_s => holding }, location:)
80✔
322
        Requests::Requestable.new(**params)
80✔
323
      end
324

325
      def load_location
3✔
326
        return if location_code.nil?
380✔
327
        location = get_location_data(location_code)
364✔
328
        location[:delivery_locations] = sort_pick_ups(location[:delivery_locations]) if location[:delivery_locations]&.present?
364✔
329
        location
364✔
330
      end
331

332
      def build_requestable_params(params)
3✔
333
        {
334
          bib: doc.with_indifferent_access,
3,337✔
335
          holding: params[:holding],
336
          item: params[:item],
337
          location: build_requestable_location(params),
338
          patron:
339
        }
340
      end
341

342
      def build_requestable_location(params)
3✔
343
        location = params[:location]
3,337✔
344
        location["delivery_locations"] = build_delivery_locations(location["delivery_locations"]) if location["delivery_locations"].present?
3,337✔
345
        location
3,337✔
346
      end
347

348
      def build_delivery_locations(delivery_locations)
3✔
349
        delivery_locations.map do |loc|
3,199✔
350
          pick_up_code = loc["library"]["code"] if loc["library"].present?
11,447✔
351
          pick_up_code ||= 'firestone'
11,447✔
352
          loc.merge("pick_up_location_code" => pick_up_code) { |_key, v1, _v2| v1 }
21,954✔
353
        end
354
      end
355

356
      # Not sure why this method exists
357
      def load_serial_items
3✔
358
        mfhd_items = {}
64✔
359
        items_as_json = items_by_mfhd(@system_id, @mfhd)
64✔
360
        unless items_as_json.empty?
64✔
361
          items_with_symbols = items_to_symbols(items_as_json)
51✔
362
          mfhd_items[@mfhd] = items_with_symbols
51✔
363
        end
364
        # else
365
        #   empty_mfhd = items_by_bib(@system_id)
366
        #   mfhd_items[@mfhd] = [empty_mfhd[@mfhd]]
367
        # end
368
        mfhd_items
64✔
369
      end
370

371
      ## this method should be the only place we load item availability
372
      def load_items_by_mfhd
3✔
373
        mfhd_items = {}
273✔
374
        mfhd_items[@mfhd] = items_by_mfhd(@system_id, @mfhd)
273✔
375
        # items_by_mfhd(@system_id, @mfhd).each do |item_info|
376
        #  mfhd_items[item_info['id']] = load_item_for_holding(holding_id: @mfhd, item_info: item_info)
377
        # end
378
        mfhd_items
273✔
379
      end
380

381
      # def load_items_by_bib_id
382
      #   mfhd_items = {}
383
      #   items_by_bib(@system_id).each do |holding_id, item_info|
384
      #     next if @mfhd != holding_id
385
      #     mfhd_items[holding_id] = load_item_for_holding(holding_id: holding_id, item_info: item_info)
386
      #   end
387
      #   mfhd_items
388
      # end
389

390
      # def load_item_for_holding(holding_id:, item_info:)
391
      #   # new check needed here
392
      #   if item_info[:more_items] == false
393
      #     if item_info[:status].starts_with?('On-Order') || item_info[:status].starts_with?('Pending Order')
394
      #       [item_info]
395
      #     elsif item_info[:status].starts_with?('Online')
396
      #       [item_info]
397
      #     else
398
      #       ## we don't need to call this again
399
      #       items_to_symbols(items_by_mfhd(@system_id, holding_id))
400
      #     end
401
      #   else
402
      #     ## we don't need to call this again
403
      #     # items_to_symbols(items_by_mfhd(@system_id, holding_id))
404
      #     items_to_symbols([item_info])
405
      #   end
406
      # end
407

408
      def items_to_symbols(items = [])
3✔
409
        items_with_symbols = []
51✔
410
        items.each do |item|
51✔
411
          items_with_symbols << item.with_indifferent_access
2,762✔
412
        end
413
        items_with_symbols
51✔
414
      end
415

416
      def item_current_location(item)
3✔
417
        if item['in_temp_library']
3,192✔
418
          item['temp_location_code']
11✔
419
        else
420
          item['location']
3,181✔
421
        end
422
      end
423
  end
424
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc