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

pulibrary / orangelight / 62bad3f1-d46d-40af-822c-403d653da2a8

17 Jun 2025 05:30PM UTC coverage: 0.447% (-94.9%) from 95.337%
62bad3f1-d46d-40af-822c-403d653da2a8

push

circleci

maxkadel
Install chrome & chromedriver for smoke specs

43 of 9610 relevant lines covered (0.45%)

0.01 hits per line

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

0.0
/app/services/physical_holdings_markup_builder.rb
1
# frozen_string_literal: false
2

3
class PhysicalHoldingsMarkupBuilder < HoldingRequestsBuilder
×
4
  include ApplicationHelper
×
5

6
  # Generate <span> markup used in links for browsing by call numbers
7
  # @return [String] the markup
8
  def call_number_span
×
9
    %(<span class="link-text">#{I18n.t('blacklight.holdings.browse')}</span>\
×
10
      <span class="icon-bookslibrary"></span>)
×
11
  end
×
12

13
  ##
14
  # Add call number link
15
  # @param [Hash] holding
16
  # @param [String] cn_value - a call number
17
  def call_number_link(holding, cn_value)
×
18
    cn = ''
×
19
    unless cn_value.nil?
×
20
      children = call_number_span
×
21
      cn_browse_link = link_to(children.html_safe,
×
22
                               "/browse/call_numbers?q=#{CGI.escape(cn_value)}",
×
23
                               class: 'browse-cn',
×
24
                               'data-original-title' => "Browse: #{cn_value}")
×
25
      cn = "#{holding['call_number']} #{cn_browse_link}"
×
26
    end
×
27
    content_tag(:td, cn.html_safe, class: 'holding-call-number')
×
28
  end
×
29

30
  def holding_location_repository
×
31
    children = content_tag(:span,
×
32
                           'On-site access',
×
33
                           class: 'availability-icon badge bg-success')
×
34
    content_tag(:td, children.html_safe)
×
35
  end
×
36

37
  def holding_location_scsb_span
×
38
    markup = content_tag(:span, '',
×
39
                         class: 'availability-icon badge')
×
40
    markup
×
41
  end
×
42

43
  def holding_location_scsb(holding, doc_id, holding_id)
×
44
    content_tag(:td, holding_location_scsb_span.html_safe,
×
45
                class: 'holding-status',
×
46
                data: {
×
47
                  'availability_record' => true,
×
48
                  'record_id' => doc_id,
×
49
                  'holding_id' => holding_id,
×
50
                  'scsb-barcode' => holding['items'].first['barcode'],
×
51
                  'aeon' => scsb_supervised_items?(holding)
×
52
                })
×
53
  end
×
54

55
  def holding_location_default(doc_id, holding_id, location_rules, temp_location_code)
×
56
    children = content_tag(:span, '', class: 'availability-icon')
×
57

58
    data = {
×
59
      'availability_record' => true,
×
60
      'record_id' => doc_id,
×
61
      'holding_id' => holding_id,
×
62
      aeon: self.class.aeon_location?(location_rules)
×
63
    }
×
64

65
    data['temp_location_code'] = temp_location_code unless temp_location_code.nil?
×
66

67
    content_tag(:td,
×
68
                 children.html_safe,
×
69
                 class: 'holding-status',
×
70
                 data:)
×
71
  end
×
72

73
  # Holding record with "dspace": false
74
  def holding_location_unavailable
×
75
    children = content_tag(:span,
×
76
                           'Unavailable',
×
77
                           class: 'availability-icon badge bg-danger')
×
78
    content_tag(:td, children.html_safe, class: 'holding-status')
×
79
  end
×
80

81
  def self.holding_label(label)
×
82
    content_tag(:li, label, class: 'holding-label')
×
83
  end
×
84

85
  def self.shelving_titles_list(holding)
×
86
    children = "#{holding_label('Shelving title')} #{listify_array(holding['shelving_title'])}"
×
87
    content_tag(:ul, children.html_safe, class: 'shelving-title')
×
88
  end
×
89

90
  def self.location_notes_list(holding)
×
91
    children = "#{holding_label('Location note')} #{listify_array(holding['location_note'])}"
×
92
    content_tag(:ul, children.html_safe, class: 'location-note')
×
93
  end
×
94

95
  def self.location_has_list(holding)
×
96
    children = "#{holding_label('Location has')} #{listify_array(holding['location_has'])}"
×
97
    content_tag(:ul, children.html_safe, class: 'location-has')
×
98
  end
×
99

100
  def self.multi_item_availability(doc_id, holding_id)
×
101
    content_tag(:ul, '',
×
102
                class: 'item-status',
×
103
                data: {
×
104
                  'record_id' => doc_id,
×
105
                  'holding_id' => holding_id
×
106
                })
×
107
  end
×
108

109
  def self.supplements_list(holding)
×
110
    children = "#{holding_label('Supplements')} #{listify_array(holding['supplements'])}"
×
111
    content_tag(:ul, children.html_safe, class: 'holding-supplements')
×
112
  end
×
113

114
  def self.indexes_list(holding)
×
115
    children = "#{holding_label('Indexes')} #{listify_array(holding['indexes'])}"
×
116
    content_tag(:ul, children.html_safe, class: 'holding-indexes')
×
117
  end
×
118

119
  def self.journal_issues_list(holding_id)
×
120
    content_tag(:ul, '',
×
121
                class: 'journal-current-issues',
×
122
                data: { journal: true, holding_id: })
×
123
  end
×
124

125
  def self.scsb_use_label(restriction)
×
126
    "#{restriction} Only"
×
127
  end
×
128

129
  # Generate the markup for record restrictions
130
  # @param holding [Hash] the restrictions for all holdings
131
  # @return [String] the markup
132
  def self.restrictions_markup(restrictions)
×
133
    restricted_items = restrictions.map do |value|
×
134
      content_tag(:td, scsb_use_label(value))
×
135
    end
×
136
    if restricted_items.length > 1
×
137
      list = restricted_items.map { |value| content_tag(:li, value) }
×
138
      content_tag(:ul, list.join.html_safe, class: 'restrictions-list item-list')
×
139
    else
×
140
      restricted_items.join.html_safe
×
141
    end
×
142
  end
×
143

144
  def self.open_location?(location)
×
145
    location.nil? ? false : location[:open]
×
146
  end
×
147

148
  def self.requestable_location?(location, adapter, holding)
×
149
    return false if adapter.sc_location_with_suppressed_button?(holding)
×
150
    if location.nil?
×
151
      false
×
152
    elsif adapter.unavailable_holding?(holding)
×
153
      false
×
154
    else
×
155
      location[:requestable]
×
156
    end
×
157
  end
×
158

159
  def self.aeon_location?(location)
×
160
    location.nil? ? false : location[:aeon_location]
×
161
  end
×
162

163
  delegate :aeon_location?, to: :class
×
164

165
  def self.scsb_location?(location)
×
166
    location.nil? ? false : /^scsb.+/ =~ location['code']
×
167
  end
×
168

169
  def self.requestable?(adapter, holding_id, location)
×
170
    !adapter.alma_holding?(holding_id) || aeon_location?(location) || scsb_location?(location)
×
171
  end
×
172

173
  def self.thesis?(adapter, holding_id)
×
174
    holding_id == 'thesis' && adapter.pub_date > 2012
×
175
  end
×
176

177
  def self.numismatics?(holding_id)
×
178
    holding_id == 'numismatics'
×
179
  end
×
180

181
  # Generate the CSS class for holding based upon its location and ID
182
  # @param adapter [HoldingRequestsAdapter] adapter for the Solr Document and Bibdata
183
  # @param location [Hash] location information
184
  # @param holding_id [String]
185
  # @return [String] the CSS class
186
  def self.show_request(adapter, location, holding_id)
×
187
    if requestable?(adapter, holding_id, location) && !thesis?(adapter, holding_id) || numismatics?(holding_id)
×
188
      'service-always-requestable'
×
189
    else
×
190
      'service-conditional'
×
191
    end
×
192
  end
×
193

194
  # Generate the location services markup for a holding
195
  # @param adapter [HoldingRequestsAdapter] adapter for the Solr Document and Bibdata
196
  # @param holding_id [String]
197
  # @param location_rules [Hash]
198
  # @param link [String] link markup
199
  # @return [String] block markup
200
  def self.location_services_block(adapter, holding_id, location_rules, link, holding)
×
201
    content_tag(:td, link,
×
202
                class: "location-services #{show_request(adapter, location_rules, holding_id)}",
×
203
                data: {
×
204
                  open: open_location?(location_rules),
×
205
                  requestable: requestable_location?(location_rules, adapter, holding),
×
206
                  aeon: aeon_location?(location_rules),
×
207
                  holding_id:
×
208
                })
×
209
  end
×
210

211
  def self.scsb_supervised_items?(holding)
×
212
    if holding.key? 'items'
×
213
      restricted_items = holding['items'].select do |item|
×
214
        item['use_statement'] == 'Supervised Use'
×
215
      end
×
216
      restricted_items.count == holding['items'].count
×
217
    else
×
218
      false
×
219
    end
×
220
  end
×
221

222
  delegate :scsb_supervised_items?, to: :class
×
223

224
  ##
225
  def self.listify_array(arr)
×
226
    arr = arr.map do |e|
×
227
      content_tag(:li, e)
×
228
    end
×
229
    arr.join
×
230
  end
×
231

232
  def doc_id(holding)
×
233
    holding.dig("mms_id") || adapter.doc_id
×
234
  end
×
235

236
  # Example of a temporary holding, in this case holding_id is : firestone$res3hr
237
  # {\"firestone$res3hr\":{\"location_code\":\"firestone$res3hr\",
238
  # \"current_location\":\"Circulation Desk (3 Hour Reserve)\",\"current_library\":\"Firestone Library\",
239
  # \"call_number\":\"HT1077 .M87\",\"call_number_browse\":\"HT1077 .M87\",
240
  # \"items\":[{\"holding_id\":\"22740601020006421\",\"id\":\"23740600990006421\",
241
  # \"status_at_load\":\"1\",\"barcode\":\"32101005621469\",\"copy_number\":\"1\"}]}}
242
  def self.temporary_holding_id?(holding_id)
×
243
    /[a-zA-Z]\$[a-zA-Z]/.match?(holding_id)
×
244
  end
×
245

246
  # When it is a temporary location and is requestable, use the first holding_id of this temporary location items.
247
  def self.temporary_location_holding_id_first(holding)
×
248
    holding["items"][0]["holding_id"]
×
249
  end
×
250

251
  # Generate the links for a given holding
252
  # TODO: Come back and remove class method calls
253
  def request_placeholder(adapter, holding_id, location_rules, holding)
×
254
    doc_id = doc_id(holding)
×
255
    view_base = ActionView::Base.new(ActionView::LookupContext.new([]), {}, nil)
×
256
    link = request_link_component(adapter:, holding_id:, doc_id:, holding:, location_rules:).render_in(view_base)
×
257
    markup = self.class.location_services_block(adapter, holding_id, location_rules, link, holding)
×
258
    markup
×
259
  end
×
260

261
  def request_link_component(adapter:, holding_id:, doc_id:, holding:, location_rules:)
×
262
    holding_object = Requests::Holding.new(mfhd_id: holding_id, holding_data: holding)
×
263
    if holding_id == 'thesis' || self.class.numismatics?(holding_id)
×
264
      AeonRequestButtonComponent.new(document: adapter.document, holding: holding_object.to_h, url_class: Requests::NonAlmaAeonUrl)
×
265
    elsif holding['items'] && holding['items'].length > 1
×
266
      RequestButtonComponent.new(doc_id:, holding_id:, location: location_rules)
×
267
    elsif aeon_location?(location_rules)
×
268
      AeonRequestButtonComponent.new(document: adapter.document, holding: holding_object.to_h)
×
269
    elsif self.class.scsb_location?(location_rules)
×
270
      RequestButtonComponent.new(doc_id:, location: location_rules, holding:)
×
271
    elsif self.class.temporary_holding_id?(holding_id)
×
272
      holding_identifier = self.class.temporary_location_holding_id_first(holding)
×
273
      RequestButtonComponent.new(doc_id:, holding_id: holding_identifier, location: location_rules)
×
274
    else
×
275
      RequestButtonComponent.new(doc_id:, holding_id:, location: location_rules)
×
276
    end
×
277
  end
×
278

279
  attr_reader :adapter
×
280
  delegate :content_tag, :link_to, to: :class
×
281

282
  # Constructor
283
  # @param adapter [HoldingRequestsAdapter] adapter for the SolrDocument and Bibdata API
284
  def initialize(adapter)
×
285
    @adapter = adapter
×
286
  end
×
287

288
  # Builds the markup for online and physical holdings for a given record
289
  # @return [String] the markup for the online and physical holdings
290
  def build
×
291
    physical_holdings_block
×
292
  end
×
293

294
  # Generate a <span> element for a holding location
295
  # @param location [String] the location value
296
  # @param holding_id [String] the ID for the holding
297
  # @return [String] <span> markup
298
  def holding_location_span(location, holding_id)
×
299
    content_tag(:span, location,
×
300
                class: 'location-text',
×
301
                data: { location: true, holding_id: })
×
302
  end
×
303

304
  # Generate the link for a specific holding
305
  # @param holding [Hash] the information for the holding
306
  # @param location [Hash] the location information for the holding
307
  # @param holding_id [String] the ID for the holding
308
  # @param call_number [String] the call number
309
  # @param library [String] the library in which the holding resides
310
  # @param [String] the markup
311
  def locate_link(location, call_number, library, holding)
×
312
    locator = StackmapLocationFactory.new(resolver_service: ::StackmapService::Url)
×
313
    return '' if locator.exclude?(call_number:, library:)
×
314

315
    markup = ''
×
316
    markup = stackmap_span_markup(location, library, holding) if find_it_location?(location)
×
317
    ' ' + markup
×
318
  end
×
319

320
  def stackmap_span_markup(location, library, holding)
×
321
    content_tag(:span, '',
×
322
                data: {
×
323
                  'map-location': location.to_s,
×
324
                  'location-library': library,
×
325
                  'location-name': holding['location']
×
326
                })
×
327
  end
×
328

329
  # Generate the links for a specific holding
330
  # @param holding [Hash] the information for the holding
331
  # @param location [Hash] the location information for the holding
332
  # @param holding_id [String] the ID for the holding
333
  # @param call_number [String] the call number
334
  # @param [String] the markup
335
  def holding_location_container(holding, location, holding_id, call_number)
×
336
    markup = holding_location_span(location, holding_id)
×
337
    link_markup = locate_link(holding['location_code'], call_number, holding['library'], holding)
×
338
    markup << link_markup.html_safe
×
339
    markup
×
340
  end
×
341

342
  # Generate the markup block for a specific holding
343
  # @param holding [Hash] the information for the holding
344
  # @param location [Hash] the location information for the holding
345
  # @param holding_id [String] the ID for the holding
346
  # @param call_number [String] the call number
347
  # @param [String] the markup
348
  def holding_location(holding, location, holding_id, call_number)
×
349
    location = holding_location_container(holding, location, holding_id, call_number)
×
350
    markup = ''
×
351
    markup << content_tag(:td, location.html_safe,
×
352
                          class: 'library-location',
×
353
                          data: { holding_id: })
×
354
    markup
×
355
  end
×
356

357
  private
×
358

359
    # Generate the markup for a physical holding record
360
    # @param holding [Hash] holding information from a Solr Document
361
    # @param holding_id [String] the ID for the holding record
362
    # @return [String] the markup
363
    def process_physical_holding(holding, holding_id)
×
364
      markup = ''
×
365
      doc_id = doc_id(holding)
×
366
      temp_location_code = @adapter.temp_location_code(holding)
×
367

368
      location_rules = @adapter.holding_location_rules(holding)
×
369
      cn_value = @adapter.call_number(holding)
×
370

371
      holding_loc = @adapter.holding_location_label(holding)
×
372
      if holding_loc.present?
×
373
        markup = holding_location(
×
374
          holding,
×
375
          holding_loc,
×
376
          holding_id,
×
377
          cn_value
×
378
        )
×
379
      end
×
380
      markup << call_number_link(holding, cn_value)
×
381
      markup << if @adapter.repository_holding?(holding)
×
382
                  holding_location_repository
×
383
                elsif @adapter.scsb_holding?(holding) && !@adapter.empty_holding?(holding)
×
384
                  holding_location_scsb(holding, doc_id, holding_id)
×
385
                elsif @adapter.unavailable_holding?(holding)
×
386
                  holding_location_unavailable
×
387
                else
×
388
                  holding_location_default(doc_id,
×
389
                                           holding_id,
×
390
                                           location_rules,
×
391
                                           temp_location_code)
×
392
                end
×
393

394
      request_placeholder_markup = request_placeholder(@adapter, holding_id, location_rules, holding)
×
395
      markup << request_placeholder_markup.html_safe
×
396

397
      markup << build_holding_notes(holding, holding_id)
×
398

399
      markup = self.class.holding_block(markup) unless markup.empty?
×
400
      markup
×
401
    end
×
402

403
    def build_holding_notes(holding, holding_id)
×
404
      holding_notes = ''
×
405

406
      holding_notes << self.class.shelving_titles_list(holding) if @adapter.shelving_title?(holding)
×
407
      holding_notes << self.class.location_notes_list(holding) if @adapter.location_note?(holding)
×
408
      holding_notes << self.class.location_has_list(holding) if @adapter.location_has?(holding)
×
409
      holding_notes << self.class.multi_item_availability(doc_id(holding), holding_id)
×
410
      holding_notes << self.class.supplements_list(holding) if @adapter.supplements?(holding)
×
411
      holding_notes << self.class.indexes_list(holding) if @adapter.indexes?(holding)
×
412
      holding_notes << self.class.journal_issues_list(holding_id) if @adapter.journal?
×
413

414
      self.class.holding_details(holding_notes) unless holding_notes.empty?
×
415
    end
×
416

417
    # Generate the markup for physical holdings
418
    # @return [String] the markup
419
    def physical_holdings
×
420
      markup = ''
×
421
      @adapter.sorted_physical_holdings.each do |holding_id, holding|
×
422
        markup << process_physical_holding(holding, holding_id)
×
423
      end
×
424
      markup
×
425
    end
×
426

427
    # Generate the markup block for physical holdings
428
    # @return [String] the markup
429
    def physical_holdings_block
×
430
      markup = ''
×
431
      children = physical_holdings
×
432
      markup = self.class.content_tag(:tbody, children.html_safe) unless children.empty?
×
433
      markup
×
434
    end
×
435
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