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

pulibrary / bibdata / f04bc944-f9b4-4a42-8b26-dcacd0e3e688

11 Mar 2025 10:27PM UTC coverage: 34.017% (-58.1%) from 92.162%
f04bc944-f9b4-4a42-8b26-dcacd0e3e688

Pull #2653

circleci

christinach
Add new lc_subject_facet field.
Helps with the vocabulary work https://github.com/pulibrary/orangelight/pull/3386
In this new field we index only the lc subject heading and the subdivisions
So that when the user searches using the Details section, they can query solr for
all the subject headings and their divisions.

This is needed for the Subject browse Vocabulary work.
example: "lc_subject_facet": [
             "Booksellers and bookselling—Italy—Directories",
             "Booksellers and bookselling-Italy",
             "Booksellers and bookselling"
              ]
Pull Request #2653: Add new lc_subject_facet field.

1 of 3 new or added lines in 1 file covered. (33.33%)

2215 existing lines in 93 files now uncovered.

1294 of 3804 relevant lines covered (34.02%)

0.99 hits per line

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

77.42
/app/adapters/alma_adapter/alma_item.rb
1
class AlmaAdapter
1✔
2
  class AlmaItem < SimpleDelegator
1✔
3
    attr_reader :item
1✔
4

5
    # @param item [Alma::BibItem]
6
    def initialize(item)
1✔
7
      @item = item
100✔
8
      super(item)
100✔
9
    end
10

11
    def self.reserve_location?(library_code, location_code)
1✔
12
      return false if library_code.nil? || location_code.nil?
11✔
13

14
      Rails.cache.fetch("library_#{library_code}_#{location_code}", expires_in: 30.minutes) do
11✔
15
        # We could get this information from our location table if we want to avoid the Alma API call.
16
        record = Alma::Location.find(library_code:, location_code:)
3✔
17
        record.response.dig('fulfillment_unit', 'value') == 'Reserves'
3✔
18
      end
19
    end
20

21
    def composite_location
1✔
22
      "#{library}$#{location}"
15✔
23
    end
24

25
    def composite_temp_location
1✔
26
      return unless in_temp_location?
14✔
27

28
      "#{temp_library}$#{temp_location}"
12✔
29
    end
30

31
    def composite_perm_location
1✔
32
      "#{holding_library}$#{holding_location}"
2✔
33
    end
34

35
    def composite_location_display
1✔
36
      if in_temp_location?
9✔
37
        composite_temp_location
6✔
38
      else
39
        composite_location
3✔
40
      end
41
    end
42

43
    def composite_location_label_display
1✔
44
      if in_temp_location?
9✔
45
        holding_location_label(composite_temp_location)
6✔
46
      else
47
        holding_location_label(composite_location)
3✔
48
      end
49
    end
50

51
    def composite_library_label_display
1✔
52
      if in_temp_location?
9✔
53
        holding_data.dig('temp_library', 'desc')
6✔
54
      else
55
        item_data.dig('library', 'desc')
3✔
56
      end
57
    end
58

59
    def on_reserve?
1✔
60
      AlmaItem.reserve_location?(library, location)
9✔
61
    end
62

63
    # @note This is called type because item_type is the value used in the
64
    #   /items endpoint. In migrating to Alma this is largely the policy value
65
    #   with a fallback.
66
    def type
1✔
67
      return 'Gen' if item_data['policy']['value'].blank?
2✔
68

UNCOV
69
      item_data['policy']['value']
×
70
    end
71

72
    # 876 field used for enrichment in
73
    # AlmaAdapter::MarcRecord#enrich_with_item
74
    def enrichment_876
1✔
UNCOV
75
      MARC::DataField.new(
×
76
        '876', ' ', ' ',
77
        *subfields_for_876
78
      )
79
    end
80

81
    def subfields_for_876
1✔
82
      [
UNCOV
83
        MARC::Subfield.new('0', holding_id),
×
84
        MARC::Subfield.new('3', enum_cron),
85
        MARC::Subfield.new('a', item_id),
86
        MARC::Subfield.new('p', barcode),
87
        MARC::Subfield.new('t', copy_number)
88
      ] + recap_876_fields
89
    end
90

91
    def recap_876_fields
1✔
UNCOV
92
      return [] unless recap_item?
×
93

94
      [
UNCOV
95
        MARC::Subfield.new('h', recap_use_restriction),
×
96
        MARC::Subfield.new('x', group_designation),
97
        MARC::Subfield.new('z', recap_customer_code),
98
        MARC::Subfield.new('j', recap_status),
99
        MARC::Subfield.new('l', 'RECAP'),
100
        MARC::Subfield.new('k', item.holding_library)
101
      ]
102
    end
103

104
    # Status isn't used for recap records, but 876j is a required field.
105
    def recap_status
1✔
106
      'Not Used'
1✔
107
    end
108

109
    def enum_cron
1✔
UNCOV
110
      return if enumeration.blank? && chronology.blank?
×
UNCOV
111
      return enumeration if chronology.blank?
×
UNCOV
112
      return chronology if enumeration.blank?
×
113

UNCOV
114
      "#{enumeration} (#{chronology})"
×
115
    end
116

117
    def enumeration
1✔
UNCOV
118
      enums = []
×
UNCOV
119
      enums << item.item_data['enumeration_a']
×
UNCOV
120
      enums << item.item_data['enumeration_b']
×
UNCOV
121
      enums << item.item_data['enumeration_c']
×
UNCOV
122
      enums << item.item_data['enumeration_d']
×
UNCOV
123
      enums << item.item_data['enumeration_e']
×
UNCOV
124
      enums << item.item_data['enumeration_f']
×
UNCOV
125
      enums << item.item_data['enumeration_g']
×
UNCOV
126
      enums << item.item_data['enumeration_h']
×
UNCOV
127
      enums.compact_blank.join(', ')
×
128
    end
129

130
    def chronology
1✔
UNCOV
131
      chrons = []
×
UNCOV
132
      chrons << item.item_data['chronology_i']
×
UNCOV
133
      chrons << item.item_data['chronology_j']
×
UNCOV
134
      chrons << item.item_data['chronology_k']
×
UNCOV
135
      chrons << item.item_data['chronology_l']
×
UNCOV
136
      chrons << item.item_data['chronology_m']
×
UNCOV
137
      chrons.compact_blank.join(', ')
×
138
    end
139

140
    def holding_id
1✔
141
      item.holding_data['holding_id']
11✔
142
    end
143

144
    def item_id
1✔
145
      item.item_data['pid']
2✔
146
    end
147

148
    def barcode
1✔
UNCOV
149
      item.item_data['barcode']
×
150
    end
151

152
    def copy_number
1✔
153
      item.holding_data['copy_id']
11✔
154
    end
155

156
    def call_number
1✔
157
      item.holding_data['call_number']
2✔
158
    end
159

160
    def cdl?
1✔
161
      item.item_data.dig('work_order_type', 'value') == 'CDL'
13✔
162
    end
163

164
    def recap_customer_code
1✔
165
      return unless recap_item?
2✔
166
      return 'PG' if item.location[0].casecmp('x').zero?
2✔
167

168
      item.location.upcase
1✔
169
    end
170

171
    def recap_use_restriction
1✔
172
      return unless recap_item?
25✔
173

174
      case item.location
25✔
175
      when *in_library_recap_groups
176
        'In Library Use'
7✔
177
      when *supervised_recap_groups
178
        'Supervised Use'
18✔
179
      end
180
    end
181

182
    def group_designation
1✔
183
      return unless recap_item?
71✔
184
      return 'Committed' if item_cgd_committed?
71✔
185

186
      case item.location
68✔
187
      when 'pv', 'pa', 'gp', 'qk', 'pf'
188
        'Shared'
17✔
189
      when *(in_library_recap_groups_and_private + supervised_recap_groups + no_access_recap_groups)
51✔
190
        'Private'
51✔
191
      end
192
    end
193

194
    def recap_item?
1✔
195
      all_recap_groups.include?(holding_location)
98✔
196
    end
197

198
    def all_recap_groups
1✔
199
      default_recap_groups +
98✔
200
        in_library_recap_groups +
201
        supervised_recap_groups +
202
        no_access_recap_groups
203
    end
204

205
    def default_recap_groups
1✔
206
      %w[pa gp qk pf]
98✔
207
    end
208

209
    def in_library_recap_groups
1✔
210
      in_library_recap_groups_and_shared + in_library_recap_groups_and_private
123✔
211
    end
212

213
    def in_library_recap_groups_and_shared
1✔
214
      ['pv']
123✔
215
    end
216

217
    def in_library_recap_groups_and_private
1✔
218
      %w[pj pk pl pm pn pt]
174✔
219
    end
220

221
    def supervised_recap_groups
1✔
222
      %w[pb ph ps pw pz xc xg xm xn xp xr xw xx xgr xcr phr xrr xmr]
167✔
223
    end
224

225
    def no_access_recap_groups
1✔
226
      %w[jq pe pg ph pq qb ql qv qx]
149✔
227
    end
228

229
    # Returns a JSON representation used for the /items endpoint.
230
    def as_json
1✔
231
      item['item_data'].merge(
2✔
232
        'id' => item_id,
233
        'copy_number' => copy_number.to_i,
234
        'temp_location' => composite_temp_location,
235
        'perm_location' => composite_perm_location,
236
        'item_type' => type,
237
        'cdl' => cdl?
238
      )
239
    end
240

241
    def availability_summary
1✔
UNCOV
242
      status = calculate_status
×
243
      {
UNCOV
244
        barcode: item_data['barcode'],
×
245
        id: item_data['pid'],
246
        holding_id:,
247
        copy_number: holding_data['copy_id'],
248
        status: status[:code],        # Available
249
        status_label: status[:label], # Item in place
250
        status_source: status[:source], # e.g. work_order, process_type, base_status
251
        process_type: status[:process_type],
UNCOV
252
        on_reserve: on_reserve? ? 'Y' : 'N',
×
253
        item_type:, # e.g., Gen
254
        pickup_location_id: library, # firestone
255
        pickup_location_code: library, # firestone
256
        location: composite_location, # firestone$stacks
257
        label: holding_location_label(composite_location), # Firestore Library
258
        description: item_data['description'], # "v. 537, no. 7618 (2016 Sept. 1)" - new in Alma
259
        enum_display: enumeration, # in Alma there are many enumerations
260
        chron_display: chronology # in Alma there are many chronologies
261
      }.merge(temp_library_availability_summary)
262
    end
263

264
    def temp_library_availability_summary
1✔
UNCOV
265
      if in_temp_location?
×
UNCOV
266
        {
×
267
          in_temp_library: true,
268
          temp_library_code: temp_library,
269
          temp_library_label: holding_location_label(composite_temp_location),
270
          temp_location_code: composite_temp_location,
271
          temp_location_label: holding_location_label(composite_temp_location)
272
        }
273
      else
UNCOV
274
        { in_temp_library: false }
×
275
      end
276
    end
277

278
    def item_cgd_committed?
1✔
279
      item_committed_to_retain == 'true' && committed_retention_reasons.include?(item_retention_reason)
71✔
280
    end
281

282
    def committed_retention_reasons
1✔
283
      %w[ReCAPItalianImprints IPLCBrill ReCAPSACAP]
4✔
284
    end
285

286
    def item_retention_reason
1✔
287
      item_data.dig('retention_reason', 'value')
4✔
288
    end
289

290
    def item_committed_to_retain
1✔
291
      item_data.dig('committed_to_retain', 'value')
71✔
292
    end
293

294
    def item_type
1✔
UNCOV
295
      item_data.dig('policy', 'value')
×
296
    end
297

298
    def calculate_status
1✔
299
      return status_from_work_order_type if item_data.dig('work_order_type', 'value').present?
15✔
300
      return status_from_process_type if item_data.dig('process_type', 'value').present?
12✔
301

302
      status_from_base_status
8✔
303
    end
304

305
    def status_from_work_order_type
1✔
306
      value = item_data['work_order_type']['value']
3✔
307
      desc = item_data['work_order_type']['desc']
3✔
308

309
      # [Source for values](https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/)
310
      # [Work Order documentation](https://pul-confluence.atlassian.net/wiki/spaces/ALMA/pages/1770142/Work+Orders)
311
      code = if value.in?(%w[Bind Pres CDL AcqWorkOrder CollDev HMT])
3✔
312
               'Unavailable'
3✔
313
             else
314
               # "COURSE" or "PHYSICAL_TO_DIGITIZATION"
315
               'Available'
×
316
             end
317
      { code:, label: desc, source: 'work_order' }
3✔
318
    end
319

320
    def status_from_process_type
1✔
321
      # For now we return "Unavailable" for any item that has a process_type.
322
      # You can see a list of all the possible values here:
323
      #   https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/
324
      value = item_data.dig('process_type', 'value')
4✔
325
      desc = item_data.dig('process_type', 'desc')
4✔
326

327
      { code: 'Unavailable', label: desc, source: 'process_type', process_type: value }
4✔
328
    end
329

330
    def status_from_base_status
1✔
331
      value = item_data.dig('base_status', 'value')
8✔
332
      desc = item_data.dig('base_status', 'desc')
8✔
333

334
      # Source for values: https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/
335
      code = value == '1' ? 'Available' : 'Unavailable'
8✔
336
      { code:, label: desc, source: 'base_status' }
8✔
337
    end
338

339
    # Create the label by retrieving the value from the holding library label (external_name in Alma)
340
    # Add the library label in front if it exists
341
    def holding_location_label(code)
1✔
342
      label = HoldingLocation.find_by(code:)&.label
9✔
343
      [composite_library_label_display, label].select(&:present?).join(' - ')
9✔
344
    end
345
  end
346
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