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

pulibrary / bibdata / 373ad6ff-fad2-405a-ab26-3d30fb5ceecf

24 Dec 2024 08:24PM UTC coverage: 91.938% (+0.08%) from 91.859%
373ad6ff-fad2-405a-ab26-3d30fb5ceecf

Pull #2563

circleci

maxkadel
Put attaching xml files in their own batch
Pull Request #2563: I2321 Shift SCSB full index tasks into separate background jobs

152 of 156 new or added lines in 10 files covered. (97.44%)

65 existing lines in 17 files now uncovered.

3478 of 3783 relevant lines covered (91.94%)

366.14 hits per line

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

99.35
/app/adapters/alma_adapter/alma_item.rb
1
class AlmaAdapter
1✔
2
  class AlmaItem < SimpleDelegator
1✔
3
    attr_reader :item
1✔
4
    # @param item [Alma::BibItem]
5
    def initialize(item)
1✔
6
      @item = item
151✔
7
      super(item)
151✔
8
    end
9

10
    def self.reserve_location?(library_code, location_code)
1✔
11
      return false if library_code.nil? || location_code.nil?
48✔
12
      Rails.cache.fetch("library_#{library_code}_#{location_code}", expires_in: 30.minutes) do
48✔
13
        # We could get this information from our location table if we want to avoid the Alma API call.
14
        record = Alma::Location.find(library_code:, location_code:)
10✔
15
        record.response.dig("fulfillment_unit", "value") == "Reserves"
10✔
16
      end
17
    end
18

19
    def composite_location
1✔
20
      "#{library}$#{location}"
85✔
21
    end
22

23
    def composite_temp_location
1✔
24
      return unless in_temp_location?
37✔
25
      "#{temp_library}$#{temp_location}"
20✔
26
    end
27

28
    def composite_perm_location
1✔
29
      "#{holding_library}$#{holding_location}"
18✔
30
    end
31

32
    def composite_location_display
1✔
33
      if in_temp_location?
11✔
34
        composite_temp_location
8✔
35
      else
36
        composite_location
3✔
37
      end
38
    end
39

40
    def composite_location_label_display
1✔
41
      if in_temp_location?
11✔
42
        holding_location_label(composite_temp_location)
8✔
43
      else
44
        holding_location_label(composite_location)
3✔
45
      end
46
    end
47

48
    def composite_library_label_display
1✔
49
      if in_temp_location?
40✔
50
        holding_data.dig("temp_library", "desc")
11✔
51
      else
52
        item_data.dig("library", "desc")
29✔
53
      end
54
    end
55

56
    def on_reserve?
1✔
57
      AlmaItem.reserve_location?(library, location)
38✔
58
    end
59

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

68
    # 876 field used for enrichment in
69
    # AlmaAdapter::MarcRecord#enrich_with_item
70
    def enrichment_876
1✔
71
      MARC::DataField.new(
5✔
72
        '876', ' ', ' ',
73
        *subfields_for_876
74
      )
75
    end
76

77
    def subfields_for_876
1✔
78
      [
79
        MARC::Subfield.new('0', holding_id),
5✔
80
        MARC::Subfield.new('3', enum_cron),
81
        MARC::Subfield.new('a', item_id),
82
        MARC::Subfield.new('p', barcode),
83
        MARC::Subfield.new('t', copy_number)
84
      ] + recap_876_fields
85
    end
86

87
    def recap_876_fields
1✔
88
      return [] unless recap_item?
5✔
89
      [
90
        MARC::Subfield.new('h', recap_use_restriction),
5✔
91
        MARC::Subfield.new('x', group_designation),
92
        MARC::Subfield.new('z', recap_customer_code),
93
        MARC::Subfield.new('j', recap_status),
94
        MARC::Subfield.new('l', "RECAP"),
95
        MARC::Subfield.new('k', item.holding_library)
96
      ]
97
    end
98

99
    # Status isn't used for recap records, but 876j is a required field.
100
    def recap_status
1✔
101
      "Not Used"
6✔
102
    end
103

104
    def enum_cron
1✔
105
      return if enumeration.blank? && chronology.blank?
5✔
106
      return enumeration if chronology.blank?
1✔
107
      return chronology if enumeration.blank?
1✔
108
      "#{enumeration} (#{chronology})"
1✔
109
    end
110

111
    def enumeration
1✔
112
      enums = []
34✔
113
      enums << item.item_data["enumeration_a"]
34✔
114
      enums << item.item_data["enumeration_b"]
34✔
115
      enums << item.item_data["enumeration_c"]
34✔
116
      enums << item.item_data["enumeration_d"]
34✔
117
      enums << item.item_data["enumeration_e"]
34✔
118
      enums << item.item_data["enumeration_f"]
34✔
119
      enums << item.item_data["enumeration_g"]
34✔
120
      enums << item.item_data["enumeration_h"]
34✔
121
      enums.compact_blank.join(", ")
34✔
122
    end
123

124
    def chronology
1✔
125
      chrons = []
33✔
126
      chrons << item.item_data["chronology_i"]
33✔
127
      chrons << item.item_data["chronology_j"]
33✔
128
      chrons << item.item_data["chronology_k"]
33✔
129
      chrons << item.item_data["chronology_l"]
33✔
130
      chrons << item.item_data["chronology_m"]
33✔
131
      chrons.compact_blank.join(", ")
33✔
132
    end
133

134
    def holding_id
1✔
135
      item.holding_data["holding_id"]
61✔
136
    end
137

138
    def item_id
1✔
139
      item.item_data["pid"]
23✔
140
    end
141

142
    def barcode
1✔
143
      item.item_data["barcode"]
5✔
144
    end
145

146
    def copy_number
1✔
147
      item.holding_data["copy_id"]
34✔
148
    end
149

150
    def call_number
1✔
151
      item.holding_data["call_number"]
13✔
152
    end
153

154
    def cdl?
1✔
155
      item.item_data.dig("work_order_type", "value") == "CDL"
32✔
156
    end
157

158
    def recap_customer_code
1✔
159
      return unless recap_item?
7✔
160
      return "PG" if item.location[0].casecmp("x").zero?
7✔
161
      item.location.upcase
6✔
162
    end
163

164
    def recap_use_restriction
1✔
165
      return unless recap_item?
30✔
166
      case item.location
30✔
167
      when *in_library_recap_groups
168
        "In Library Use"
8✔
169
      when *supervised_recap_groups
170
        "Supervised Use"
18✔
171
      end
172
    end
173

174
    def group_designation
1✔
175
      return unless recap_item?
76✔
176
      return "Committed" if item_cgd_committed?
76✔
177
      case item.location
73✔
178
      when 'pv', 'pa', 'gp', 'qk', 'pf'
179
        "Shared"
20✔
180
      when *(in_library_recap_groups_and_private + supervised_recap_groups + no_access_recap_groups)
53✔
181
        "Private"
53✔
182
      end
183
    end
184

185
    def recap_item?
1✔
186
      all_recap_groups.include?(holding_location)
118✔
187
    end
188

189
    def all_recap_groups
1✔
190
      default_recap_groups +
118✔
191
        in_library_recap_groups +
192
        supervised_recap_groups +
193
        no_access_recap_groups
194
    end
195

196
    def default_recap_groups
1✔
197
      ["pa", "gp", "qk", "pf"]
118✔
198
    end
199

200
    def in_library_recap_groups
1✔
201
      in_library_recap_groups_and_shared + in_library_recap_groups_and_private
148✔
202
    end
203

204
    def in_library_recap_groups_and_shared
1✔
205
      ['pv']
148✔
206
    end
207

208
    def in_library_recap_groups_and_private
1✔
209
      ['pj', 'pk', 'pl', 'pm', 'pn', 'pt']
201✔
210
    end
211

212
    def supervised_recap_groups
1✔
213
      ["pb", "ph", "ps", "pw", "pz", "xc", "xg", "xm", "xn", "xp", "xr", "xw", "xx", "xgr", "xcr", "phr", "xrr", "xmr"]
193✔
214
    end
215

216
    def no_access_recap_groups
1✔
217
      ['jq', 'pe', 'pg', 'ph', 'pq', 'qb', 'ql', 'qv', 'qx']
171✔
218
    end
219

220
    # Returns a JSON representation used for the /items endpoint.
221
    def as_json
1✔
222
      item["item_data"].merge(
18✔
223
        "id" => item_id,
224
        "copy_number" => copy_number.to_i,
225
        "temp_location" => composite_temp_location,
226
        "perm_location" => composite_perm_location,
227
        "item_type" => type,
228
        "cdl" => cdl?
229
      )
230
    end
231

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

255
    def temp_library_availability_summary
1✔
256
      if in_temp_location?
27✔
257
        {
258
          in_temp_library: true,
1✔
259
          temp_library_code: temp_library,
260
          temp_library_label: holding_location_label(composite_temp_location),
261
          temp_location_code: composite_temp_location,
262
          temp_location_label: holding_location_label(composite_temp_location)
263
        }
264
      else
265
        { in_temp_library: false }
26✔
266
      end
267
    end
268

269
    def item_cgd_committed?
1✔
270
      item_committed_to_retain == "true" && committed_retention_reasons.include?(item_retention_reason)
76✔
271
    end
272

273
    def committed_retention_reasons
1✔
274
      %w[ReCAPItalianImprints IPLCBrill ReCAPSACAP]
4✔
275
    end
276

277
    def item_retention_reason
1✔
278
      item_data.dig("retention_reason", "value")
4✔
279
    end
280

281
    def item_committed_to_retain
1✔
282
      item_data.dig("committed_to_retain", "value")
76✔
283
    end
284

285
    def item_type
1✔
286
      item_data.dig("policy", "value")
27✔
287
    end
288

289
    def calculate_status
1✔
290
      return status_from_work_order_type if item_data.dig("work_order_type", "value").present?
44✔
291
      return status_from_process_type if item_data.dig("process_type", "value").present?
39✔
292
      status_from_base_status
30✔
293
    end
294

295
    def status_from_work_order_type
1✔
296
      value = item_data["work_order_type"]["value"]
5✔
297
      desc = item_data["work_order_type"]["desc"]
5✔
298

299
      # [Source for values](https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/)
300
      # [Work Order documentation](https://pul-confluence.atlassian.net/wiki/spaces/ALMA/pages/1770142/Work+Orders)
301
      code = if value.in?(["Bind", "Pres", "CDL", "AcqWorkOrder", "CollDev", "HMT"])
5✔
302
               "Unavailable"
5✔
303
             else
304
               # "COURSE" or "PHYSICAL_TO_DIGITIZATION"
UNCOV
305
               "Available"
×
306
             end
307
      { code:, label: desc, source: "work_order" }
5✔
308
    end
309

310
    def status_from_process_type
1✔
311
      # For now we return "Unavailable" for any item that has a process_type.
312
      # You can see a list of all the possible values here:
313
      #   https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/
314
      value = item_data.dig("process_type", "value")
9✔
315
      desc = item_data.dig("process_type", "desc")
9✔
316

317
      { code: "Unavailable", label: desc, source: "process_type", process_type: value }
9✔
318
    end
319

320
    def status_from_base_status
1✔
321
      value = item_data.dig("base_status", "value")
30✔
322
      desc = item_data.dig("base_status", "desc")
30✔
323

324
      # Source for values: https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/
325
      code = value == "1" ? "Available" : "Unavailable"
30✔
326
      { code:, label: desc, source: "base_status" }
30✔
327
    end
328

329
    # Create the label by retrieving the value from the holding library label (external_name in Alma)
330
    # Add the library label in front if it exists
331
    def holding_location_label(code)
1✔
332
      label = HoldingLocation.find_by(code:)&.label
40✔
333
      [composite_library_label_display, label].select(&:present?).join(" - ")
40✔
334
    end
335
  end
336
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