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

pulibrary / orangelight / 30e4e757-8565-4a15-a8db-bfa223eb1b0f

10 Nov 2025 06:33PM UTC coverage: 95.247% (-0.2%) from 95.41%
30e4e757-8565-4a15-a8db-bfa223eb1b0f

Pull #5339

circleci

christinach
when annex no item service is used the patron should
receive a confirmation email
with the annex confirmation subject

related to [#4183]
Pull Request #5339: Annex requests with no items should email forranx@princeton.edu

50 of 63 new or added lines in 8 files covered. (79.37%)

1 existing line in 1 file now uncovered.

6232 of 6543 relevant lines covered (95.25%)

1448.53 hits per line

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

89.62
/app/helpers/requests/application_helper.rb
1
# frozen_string_literal: true
2
# rubocop:disable Metrics/ModuleLength
3
module Requests
3✔
4
  module ApplicationHelper
3✔
5
    def format_label(key)
3✔
6
      label = key.to_s
×
7
      human_label = label.tr('_', ' ')
×
8
      formatted = human_label.split.map(&:capitalize).join(' ')
×
9
      formatted
×
10
    end
11

12
    def error_key_format(key)
3✔
13
      keys_to_ignore = ['items']
×
14
      format_label(key) unless keys_to_ignore.include? key.to_s
×
15
    end
16

17
    # array of error_keys
18
    def guest_user_error?(error_keys)
3✔
19
      user_errors = [:email, :user_name, :barcode]
×
20
      error_keys.any? { |item| user_errors.include? item }
×
21
    end
22

23
    def show_pick_up_service_options(requestable, mfhd_id)
3✔
24
      if requestable.on_shelf?
138✔
25
        display_on_shelf(requestable, mfhd_id)
70✔
26
      else
27
        display_requestable_list(requestable)
68✔
28
      end
29
    end
30

31
    # :reek:FeatureEnvy
32
    def show_service_options(requestable, _mfhd_id)
3✔
33
      if requestable.charged? && !requestable.aeon? && !requestable.ask_me?
3✔
34
        render partial: 'checked_out_options', locals: { requestable: }
1✔
35
      else
36
        display_requestable_list(requestable)
2✔
37
      end
38
    end
39

40
    def hidden_service_options(requestable, fill_in: false)
3✔
41
      return hidden_service_options_fill_in(requestable) if fill_in
128✔
42
      hidden = output_request_input(requestable)
115✔
43
      return hidden if hidden.present?
115✔
44
      if requestable.services.include? 'recap'
44✔
45
        recap_print_only_input requestable
35✔
46
      else
47
        request_input(requestable.services.first)
9✔
48
      end
49
    end
50

51
    def output_request_input(requestable)
3✔
52
      output = ""
115✔
53
      ['annex', 'pres', 'ppl', 'lewis', 'paging', 'on_order', 'on_shelf'].each do |type|
115✔
54
        next unless requestable.services.include?(type)
751✔
55
        output = request_input(type)
71✔
56
        break
71✔
57
      end
58
      output
115✔
59
    end
60

61
    # only requestable services that support "user-supplied volume info"
62
    def hidden_service_options_fill_in(requestable)
3✔
63
      if requestable.services.include? 'annex_no_items'
13✔
NEW
64
        request_input('annex_no_items')
×
65
      elsif requestable.annex?
13✔
66
        request_input('annex')
4✔
67
      elsif requestable.services.include? 'recap_no_items'
9✔
68
        request_input('recap_no_items')
1✔
69
      else
70
        request_input('paging')
8✔
71
      end
72
    end
73

74
    def recap_print_only_input(requestable)
3✔
75
      content_tag(:fieldset, class: 'recap--print', id: "recap_group_#{requestable.preferred_request_id}") do
35✔
76
        concat hidden_field_tag "requestable[][type]", "", value: 'recap'
35✔
77
      end
78
    end
79

80
    # rubocop:disable Style/NumericPredicate
81
    def enum_copy_display(item)
3✔
82
      return "" if item.blank?
523✔
83
      [item.description, item.copy_value].join(" ").strip
521✔
84
    end
85
    # rubocop:enable Style/NumericPredicate
86

87
    def request_input(type)
3✔
88
      hidden_field_tag "requestable[][type]", "", value: type
99✔
89
    end
90

91
    def gfa_lookup(lib_code)
3✔
92
      if lib_code == "firestone"
6✔
93
        "PA"
×
94
      else
95
        lib = Requests::BibdataService.delivery_locations.select { |_key, hash| hash["library"]["code"] == lib_code }
144✔
96
        lib.keys.first.to_s
6✔
97
      end
98
    end
99

100
    def pick_up_classlist(requestable, collapse)
3✔
101
      class_list = "collapse request--print"
147✔
102
      class_list += " show" if !requestable.digitize? && !collapse
147✔
103
      class_list
147✔
104
    end
105

106
    # move this to requestable object
107
    # Default pick-ups should be available
108
    def pick_up_choices(requestable, default_pick_ups, collapse = false)
3✔
109
      content_tag(:div, id: "fields-print__#{requestable.preferred_request_id}", class: pick_up_classlist(requestable, collapse)) do
136✔
110
        preferred_request_content_tag(requestable, requestable.pick_up_locations || default_pick_ups)
136✔
111
      end
112
    end
113

114
    # :reek:NilCheck
115
    def preferred_request_content_tag(requestable, default_pick_ups)
3✔
116
      (show_pick_up_service_options(requestable, nil) || "".html_safe) +
141✔
117
        content_tag(:div, id: "fields-print__#{requestable.preferred_request_id}_card", class: "card card-body bg-light") do
118
          locs = pick_up_locations(requestable, default_pick_ups)
141✔
119

120
          name = 'requestable[][pick_up]'
141✔
121
          id = "requestable__pick_up_#{requestable.preferred_request_id}"
141✔
122
          if locs.size > 1
141✔
123
            prompt_text = custom_pickup_prompt(requestable, locs) || I18n.t("requests.default.pick_up_placeholder")
61✔
124
            selected_value = find_selected_pickup_value(requestable, locs)
61✔
125
            # For ReCAP items, select the empty prompt instead of any actual option
126
            selected_value = '' if requestable.recap? && selected_value.nil?
61✔
127
            options = [[prompt_text, '', { disabled: true, selected: false }]] + locs.map { |loc| [loc[:label], { 'pick_up' => loc[:gfa_pickup], 'pick_up_location_code' => loc[:pick_up_location_code] }.to_json] }
573✔
128
            select_tag name.to_s, options_for_select(options, selected_value), id: id
61✔
129
          else
130

131
            single_pickup(requestable.charged?, name, id, locs[0])
80✔
132
          end
133
        end
134
    end
135

136
    # rubocop:disable Rails/OutputSafety
137
    def hidden_fields_mfhd(mfhd)
3✔
138
      hidden = ""
76✔
139
      return hidden if mfhd.nil?
76✔
140
      hidden += hidden_field_tag "mfhd[][call_number]", "", value: (mfhd['call_number']).to_s unless mfhd["call_number"].nil?
75✔
141
      hidden += hidden_field_tag "mfhd[][location]", "", value: (mfhd['location']).to_s unless mfhd["location"].nil?
75✔
142
      hidden += hidden_field_tag "mfhd[][library]", "", value: (mfhd['library']).to_s
75✔
143
      hidden.html_safe
75✔
144
    end
145
    # rubocop:enable Rails/OutputSafety
146

147
    def suppress_login?(request)
3✔
148
      request.only_aeon?
80✔
149
    end
150

151
    def item_checkbox(requestable, single_item_form)
3✔
152
      disabled = !requestable.will_submit_via_form?
509✔
153
      check_box_tag "requestable[][selected]", true, check_box_selected?(disabled, single_item_form), class: 'request--select', disabled:, aria: { labelledby: "title enum_#{requestable.preferred_request_id}" }, id: "requestable_selected_#{requestable.preferred_request_id}"
509✔
154
    end
155

156
    ## If any requestable items have a temp location assume everything at the holding is in a temp loc?
157
    def current_location_label(holding_location_label, requestable_list)
3✔
158
      first_location = requestable_list.first.location
149✔
159
      location_label = first_location.short_label.blank? ? "" : "- #{first_location.short_label}"
149✔
160
      label = if requestable_list.first.temp_loc_other_than_resource_sharing?
149✔
161
                "#{first_location.library_label}#{location_label}"
4✔
162
              else
163
                holding_location_label
145✔
164
              end
165
      "#{label} #{requestable_list.first.call_number}"
149✔
166
    end
167

168
    def check_box_selected?(disabled, single_item_form)
3✔
169
      if single_item_form
509✔
170
        !disabled
41✔
171
      else
172
        false
468✔
173
      end
174
    end
175

176
    def submit_button_disabled?(requestable_list)
3✔
177
      # temporary chane issue 438 guest can no longer check out materials
178
      return true if @user.blank? || @user.guest
53✔
179
      return unsubmittable? requestable_list unless requestable_list.size == 1
51✔
180
      # temporary changes issue 438 do not disable the button for circulating items
181
      # requestable_list.first.services.empty? || requestable_list.first.on_reserve? || (requestable_list.first.services.include? 'on_shelf') || requestable_list.first.ask_me?
182
      requestable_list.first.services.empty? || requestable_list.first.on_reserve?
42✔
183
    end
184

185
    def unsubmittable?(requestable_list)
3✔
186
      !requestable_list.any? { |requestable| (requestable.services | submitable_services).present? }
18✔
187
    end
188

189
    def submitable_services
3✔
190
      ['on_shelf', 'in_process', 'on_order', 'annex', 'annex_no_items', 'recap', 'recap_edd', 'paging', 'recap_no_items', 'ppl', 'lewis']
9✔
191
    end
192

193
    def submit_message(requestable_list)
3✔
194
      single_item = "Request this Item"
49✔
195
      multi_item = "Request Selected Items"
49✔
196
      no_item = "No Items Available"
49✔
197
      return multi_item unless requestable_list.size == 1
49✔
198
      if requestable_list.first.services.empty?
40✔
199
        no_item
×
200
      elsif requestable_list.first.annex?
40✔
201
        # Annex items have the potential to display the
202
        # use the fill-in form, where a user could potentially
203
        # request multiple volumes.  For that reason, we show
204
        # the plural form "Request Selected Items" in this case
205
        multi_item
3✔
206
      else
207
        single_item
37✔
208
      end
209
    end
210

211
    # only show the table sort if there are enough items
212
    # to make it worthwhile
213
    def show_tablesorter(requestable_list)
3✔
214
      return "tablesorter" if table_sorter_present?(requestable_list)
75✔
215
      ""
67✔
216
    end
217

218
    def table_sorter_present?(requestable_list)
3✔
219
      requestable_list.size > 5
114✔
220
    end
221

222
    def display_label
3✔
223
      {
×
224
        author: "Author/Artist",
225
        title: "Title",
226
        date: "Published/Created",
227
        id: "Bibliographic ID",
228
        mfhd: "Holding ID (mfhd)"
229
      }.with_indifferent_access
230
    end
231

232
    def display_status(requestable)
3✔
233
      content_tag(:span, requestable.item['status']) unless requestable.item.nil?
×
234
    end
235

236
    def system_status_label(requestable)
3✔
237
      return "" if requestable.item.blank?
×
238
      content_tag(:div, requestable.item[:status], class: 'system-status')
×
239
    end
240

241
    def display_urls(requestable)
3✔
242
      content_tag :ol do
×
243
        requestable.urls.each do |key, value|
×
244
          unless key == 'iiif_manifest_paths'
×
245
            value.reverse!
×
246
            concat content_tag(:li, link_to(value.join(": "), key), class: 'link')
×
247
          end
248
        end
249
      end
250
    end
251

252
    def self.recap_annex_available_pick_ups(requestable, default_pick_ups)
3✔
253
      locations = requestable.pick_up_locations || default_pick_ups
62✔
254
      pick_ups = locations.select { |loc| Requests::Location.valid_recap_annex_pickup?(loc) }
709✔
255
      pick_ups << default_pick_ups[0] if pick_ups.empty?
62✔
256
      pick_ups
62✔
257
    end
258

259
    private
3✔
260

261
      def custom_pickup_prompt(requestable, locs)
3✔
262
        # For ReCAP items, return nil to use default prompt
263
        return nil if requestable.recap?
68✔
264

265
        holding_library = normalize_holding_library(requestable)
28✔
266
        return nil if holding_library.blank?
28✔
267

268
        find_prompt_for_holding_library(holding_library, locs)
28✔
269
      end
270

271
      # :reek:UtilityFunction
272
      def normalize_holding_library(requestable)
3✔
273
        requestable.holding_library&.downcase
49✔
274
      end
275

276
      def find_prompt_for_holding_library(holding_library, locs)
3✔
277
        # Check for special engineering library cases first
278
        engineering_prompt = engineering_library_prompt(holding_library, locs)
28✔
279
        return engineering_prompt if engineering_prompt
28✔
280

281
        # Find matching library by code and suggest it in the prompt
282
        suggested_location = find_matching_location_label(holding_library, locs)
24✔
283
        return unless suggested_location
24✔
284
        I18n.t('requests.pick_up_suggested.holding_library', holding_library: suggested_location)
4✔
285
        # "Select a Delivery Location (Recommended: #{suggested_location})"
286
      end
287

288
      def find_matching_location_label(holding_library, locs)
3✔
289
        matching_loc = find_matching_location_by_code(holding_library, locs)
24✔
290
        matching_loc&.dig(:label)
24✔
291
      end
292

293
      # :reek:UtilityFunction
294
      def find_matching_location_by_code(holding_library, locs)
3✔
295
        locs.find do |loc|
43✔
296
          # Extract library code and compare with holding library
297
          location = Requests::Location.new(loc)
249✔
298
          location.library_code&.downcase == holding_library
249✔
299
        end
300
      end
301

302
      # :reek:UtilityFunction
303
      def engineering_library_prompt(holding_library, locs)
3✔
304
        # Special case: lewis, plasma should default to Engineering Library
305
        if ['lewis', 'plasma'].include?(holding_library)
28✔
306
          engineering_loc = locs.find { |loc| loc[:label] == "Engineering Library" }
16✔
307
          return I18n.t('requests.pick_up_suggested.engineering_holding_library', engineering_holding_library: engineering_loc[:label]) if engineering_loc
5✔
308
        end
309
        nil
24✔
310
      end
311

312
      def find_selected_pickup_value(requestable, locs)
3✔
313
        return nil if should_skip_form_preselection?(requestable)
61✔
314

315
        holding_library = normalize_holding_library(requestable)
21✔
316
        return nil if holding_library.blank?
21✔
317

318
        find_form_preselected_location_json(holding_library, locs)
21✔
319
      end
320

321
      # :reek:UtilityFunction
322
      def should_skip_form_preselection?(requestable)
3✔
323
        requestable.recap?
61✔
324
      end
325

326
      def find_form_preselected_location_json(holding_library, locs)
3✔
327
        selected_location = find_engineering_location(holding_library, locs) ||
21✔
328
                            find_matching_location_by_code(holding_library, locs)
329
        return nil unless selected_location
21✔
330

331
        location_to_json(selected_location)
3✔
332
      end
333

334
      # :reek:UtilityFunction
335
      def find_engineering_location(holding_library, locs)
3✔
336
        return nil unless ['lewis', 'plasma'].include?(holding_library)
21✔
337
        locs.find { |loc| Requests::Location.new(loc).engineering_library? }
6✔
338
      end
339

340
      # :reek:UtilityFunction
341
      def location_to_json(location)
3✔
342
        { 'pick_up' => location[:gfa_pickup], 'pick_up_location_code' => location[:pick_up_location_code] }.to_json
3✔
343
      end
344

345
      def display_requestable_list(requestable)
3✔
346
        content_tag(:ul, class: "service-list") do
140✔
347
          if requestable.ill_eligible?
140✔
348
            concat content_tag(:li, sanitize(I18n.t("requests.ill.brief_msg")), class: "service-item")
1✔
349
          else
350
            filtered_services = if requestable.services.size == 1 && requestable.services.first.include?("edd")
139✔
UNCOV
351
                                  requestable.services
×
352
                                else
353
                                  requestable.services.reject { |service_name| service_name.include?("edd") }
393✔
354
                                end
355
            # if there is not a valid service this will evaluate to `requests.brief_msg` and display an error message.
356
            brief_msg = if filtered_services.first
139✔
357
                          I18n.t("requests.#{filtered_services.first}.brief_msg")
137✔
358
                        else
359
                          I18n.t("requests.alma_login.no_access")
2✔
360
                        end
361
            concat content_tag(:li, sanitize(brief_msg), class: "service-item")
139✔
362
          end
363
        end
364
      end
365

366
      def display_on_shelf(requestable, _mfhd_id)
3✔
367
        content_tag(:div) do
70✔
368
          display_requestable_list(requestable)
70✔
369
        end
370
      end
371

372
      def pick_up_locations(requestable, default_pick_ups)
3✔
373
        # we don't want to change the ill_eligible rules
374
        return ill_eligible_pick_up_location(default_pick_ups) if requestable.ill_eligible?
143✔
375
        return Requests::ApplicationHelper.recap_annex_available_pick_ups(requestable, default_pick_ups) if requestable.recap? || requestable.annex?
141✔
376
        return default_pick_ups if requestable.location&.standard_circ_location?
82✔
377
        if requestable.delivery_location_label.present?
79✔
378
          [{ label: requestable.delivery_location_label, gfa_pickup: requestable.delivery_location_code, pick_up_location_code: requestable.pick_up_location_code, staff_only: false }]
72✔
379
        else
380
          [{ label: requestable.location.library_label, gfa_pickup: gfa_lookup(requestable.location.library_code), staff_only: false }]
7✔
381
        end
382
      end
383

384
      # :reek:UtilityFunction
385
      def ill_eligible_pick_up_location(default_pick_ups)
3✔
386
        # currently for resource sharing items through Illiad we use firestone Library with gfa_pickup of PA
387
        location = default_pick_ups.find { |location| location[:gfa_pickup] == "PA" }
7✔
388
        [location].compact
2✔
389
      end
390

391
      def single_pickup(is_charged, name, id, location)
3✔
392
        style = if is_charged
80✔
393
                  'margin-top:10px;'
×
394
                else
395
                  ''
80✔
396
                end
397
        hidden = hidden_field_tag name.to_s, "", value: { 'pick_up' => location[:gfa_pickup], 'pick_up_location_code' => location[:pick_up_location_code] }.to_json, class: 'single-pick-up-hidden', id: id
80✔
398
        label = label_tag id, "Pick-up location: #{location[:label]}", class: 'single-pick-up', style: style.to_s
80✔
399
        hidden + label
80✔
400
      end
401
  end
402
end
403
# rubocop:enable Metrics/ModuleLength
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