alma 0.2.8 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.ruby-version +1 -1
- data/.travis.yml +10 -2
- data/Guardfile +75 -0
- data/README.md +136 -26
- data/alma.gemspec +9 -4
- data/lib/alma.rb +8 -4
- data/lib/alma/api_defaults.rb +30 -0
- data/lib/alma/availability_response.rb +1 -1
- data/lib/alma/bib.rb +4 -22
- data/lib/alma/bib_item.rb +8 -31
- data/lib/alma/bib_item_set.rb +60 -11
- data/lib/alma/bib_set.rb +7 -21
- data/lib/alma/config.rb +7 -3
- data/lib/alma/electronic.rb +167 -0
- data/lib/alma/electronic/README.md +20 -0
- data/lib/alma/electronic/batch_utils.rb +224 -0
- data/lib/alma/electronic/business.rb +29 -0
- data/lib/alma/error.rb +12 -1
- data/lib/alma/fine.rb +15 -0
- data/lib/alma/fine_set.rb +34 -23
- data/lib/alma/item_request_options.rb +22 -0
- data/lib/alma/loan.rb +18 -0
- data/lib/alma/loan_set.rb +59 -17
- data/lib/alma/renewal_response.rb +7 -3
- data/lib/alma/request.rb +165 -0
- data/lib/alma/request_options.rb +31 -18
- data/lib/alma/request_set.rb +62 -17
- data/lib/alma/response.rb +45 -0
- data/lib/alma/result_set.rb +27 -35
- data/lib/alma/user.rb +65 -57
- data/lib/alma/user_request.rb +17 -0
- data/lib/alma/user_set.rb +5 -6
- data/lib/alma/version.rb +1 -1
- data/log/.gitignore +4 -0
- metadata +75 -8
- data/lib/alma/api.rb +0 -33
@@ -0,0 +1,20 @@
|
|
1
|
+
## Alma::Electronic
|
2
|
+
|
3
|
+
A wrapper for the Alma::Electronic API.
|
4
|
+
|
5
|
+
The main entry point is the get methods.
|
6
|
+
|
7
|
+
### To get a list of all the collections.
|
8
|
+
|
9
|
+
```
|
10
|
+
Alma::Electronic.get()
|
11
|
+
```
|
12
|
+
|
13
|
+
Will also accept these params:
|
14
|
+
|
15
|
+
| Parameter | Type | Required | Description |
|
16
|
+
| --------- | ----------| --------- | ---------------------------------------------------------------------------------------------------------------|
|
17
|
+
| q | xs:string | Optional. | Search query. Optional. Searching for words in interface_name, keywords, name or po_line_id (see Brief Search) |
|
18
|
+
| limit | xs:int | Optional. | Default: 10Limits the number of results. Optional. Valid values are 0-100. Default value: 10. |
|
19
|
+
| offset | xs:int | Optional. | Default: 0Offset of the results returned. Optional. Default value: 0, which methodseans that the first results will be returned. |
|
20
|
+
|
@@ -0,0 +1,224 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "alma/electronic"
|
5
|
+
|
6
|
+
# Contains batch processing utils for Alma Electronic APIs.
|
7
|
+
#
|
8
|
+
# This class and its methods are used to iterate over Alma Electronic IDs to
|
9
|
+
# process and fetch Alma electronic objects via the Alma Electronic APIs. The
|
10
|
+
# https calls are logged and can be used to rerun the batch process without
|
11
|
+
# making any further http calls or to just rerun parts of the full batch.
|
12
|
+
module Alma
|
13
|
+
class Electronic::BatchUtils
|
14
|
+
attr_reader :notes, :type
|
15
|
+
|
16
|
+
# @param [Hash] options The options to create a batch instance.
|
17
|
+
# @option [true, false] :chain true indicates a new instance of self returned.
|
18
|
+
# @option [Array<String>] :ids List of collection ids.
|
19
|
+
# @option ["collection", "service", "portfolio"] :type The Alma Electronic object type.
|
20
|
+
# @option [String] :tag A string used to tag the batch session.
|
21
|
+
# @option [Logger] :logger A logger used t
|
22
|
+
def initialize(options = {})
|
23
|
+
options ||= options {}
|
24
|
+
@chain = options.fetch(:chain, false)
|
25
|
+
@ids = options.fetch(:ids, [])
|
26
|
+
@type = options.fetch(:type, "collection")
|
27
|
+
@tag = options.fetch(:tag, Time.now.to_s)
|
28
|
+
|
29
|
+
@@logger = options[:logger] || Logger.new("log/electronic_batch_process.log")
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_collection_notes(ids = nil, options = {})
|
33
|
+
ids ||= @ids
|
34
|
+
get_notes(options.merge(ids: make_collection_ids(ids), type: "collection"))
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_service_notes(ids = nil, options = {})
|
38
|
+
ids ||= @ids
|
39
|
+
get_notes(options.merge(ids: get_service_ids(ids, options), type: "service"))
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_portfolio_notes(ids = nil, options = {})
|
43
|
+
ids ||= @ids
|
44
|
+
get_notes(options.merge(ids: get_portfolio_ids(ids, options, type: "portfolio")))
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_notes(options = {})
|
48
|
+
options ||= {}
|
49
|
+
chain = options.fetch(:chain, @chain)
|
50
|
+
ids = options[:ids] || (chain ? build_ids(options) : @ids)
|
51
|
+
|
52
|
+
type = options.fetch(:type, @type)
|
53
|
+
tag = options.fetch(:tag, @tag)
|
54
|
+
@notes = ids.inject({}) do |notes, params|
|
55
|
+
id = get_id(type, params)
|
56
|
+
start = Time.now
|
57
|
+
|
58
|
+
begin
|
59
|
+
item = Alma::Electronic.get(params)
|
60
|
+
rescue StandardError => e
|
61
|
+
item = { "error" => e.message }
|
62
|
+
end
|
63
|
+
|
64
|
+
data = ["error", "authentication_note", "public_note"].reduce({}) do |acc, field|
|
65
|
+
acc[field] = item[field] if item[field].present?
|
66
|
+
acc
|
67
|
+
end
|
68
|
+
|
69
|
+
unavailable = item.dig("service_temporarily_unavailable", "value")
|
70
|
+
if unavailable == "1" || unavailable == "true"
|
71
|
+
data.merge!(item.slice("service_temporarily_unavailable", "service_unavailable_date", "service_unavailable_reason"))
|
72
|
+
end
|
73
|
+
|
74
|
+
if data.present?
|
75
|
+
log(params.merge(data).merge(type: type, start: start, tag: tag))
|
76
|
+
|
77
|
+
notes[id] = data unless data["error"].present?
|
78
|
+
end
|
79
|
+
|
80
|
+
notes
|
81
|
+
end
|
82
|
+
|
83
|
+
self.class.new(options.merge(
|
84
|
+
chain: chain,
|
85
|
+
ids: ids,
|
86
|
+
type: type,
|
87
|
+
tag: tag,
|
88
|
+
notes: notes,
|
89
|
+
logger: @@logger,
|
90
|
+
))
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_service_ids(ids = @ids, options = {})
|
94
|
+
tag = options.fetch(:tag, @tag)
|
95
|
+
start = Time.now
|
96
|
+
|
97
|
+
make_collection_ids(ids)
|
98
|
+
.map { |id| id.merge(type: "services") }
|
99
|
+
.inject([]) do |service_ids, params|
|
100
|
+
params.merge!(tag: tag)
|
101
|
+
|
102
|
+
begin
|
103
|
+
item = Alma::Electronic.get(params)
|
104
|
+
|
105
|
+
if item["errorList"]
|
106
|
+
log params.merge(item["errorList"])
|
107
|
+
.merge(start: start)
|
108
|
+
else
|
109
|
+
item["electronic_service"].each { |service|
|
110
|
+
service_id = { service_id: service["id"].to_s }
|
111
|
+
service_ids << params.slice(:collection_id)
|
112
|
+
.merge(service_id)
|
113
|
+
|
114
|
+
log params.merge(service_id)
|
115
|
+
.merge(start: start)
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
rescue StandardError => e
|
120
|
+
log params.merge("error" => e.message)
|
121
|
+
.merge(start: start)
|
122
|
+
end
|
123
|
+
|
124
|
+
service_ids
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Builds the notes object using the logs.
|
129
|
+
def build_notes(options = {})
|
130
|
+
options ||= {}
|
131
|
+
type ||= options.fetch(:type, "collection")
|
132
|
+
|
133
|
+
get_logged_items(options)
|
134
|
+
.select { |item| item.slice("authentication_note", "public_note").values.any?(&:present?) }
|
135
|
+
.inject({}) do |nodes, item|
|
136
|
+
|
137
|
+
id = item["#{type}_id"]
|
138
|
+
nodes.merge(id => item.slice("authentication_note", "public_note"))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Builds list of ids from logs based on failed attempts.
|
143
|
+
# Useful for rebuilding part of collection.
|
144
|
+
def build_failed_ids(options = {})
|
145
|
+
successful_ids = build_successful_ids(options)
|
146
|
+
get_logged_items(options)
|
147
|
+
.select { |item| item.slice("authentication_note", "public_note").values.all?(&:nil?) }
|
148
|
+
.map { |item| item["collection_id"] }
|
149
|
+
.select { |id| !successful_ids.include? id }
|
150
|
+
.uniq
|
151
|
+
end
|
152
|
+
|
153
|
+
# Builds list of ids from logs based on successful attempts.
|
154
|
+
# Useful for verifying that failed ids have always failed.
|
155
|
+
def build_successful_ids(options = {})
|
156
|
+
get_logged_items(options)
|
157
|
+
.select { |item| item.slice("authentication_note", "public_note").values.present? }
|
158
|
+
.map { |item| item["collection_id"] }
|
159
|
+
.uniq
|
160
|
+
end
|
161
|
+
|
162
|
+
# Builds a list of all ids for a specific session.
|
163
|
+
# Useful for analytics purpose or rebuilds.
|
164
|
+
def build_ids(options = {})
|
165
|
+
build_failed_ids(options) + build_successful_ids(options)
|
166
|
+
end
|
167
|
+
|
168
|
+
def print_notes(options = {})
|
169
|
+
options ||= {}
|
170
|
+
chain = options.fetch(:chain, @chain)
|
171
|
+
notes = options[:notes] || chain ? build_notes(options) : @notes
|
172
|
+
type = options.fetch(:type, @type)
|
173
|
+
tag = options.fetch(:tag, @tag)
|
174
|
+
|
175
|
+
filename = options.fetch(:filename, "spec/fixtures/#{type}_notes.json")
|
176
|
+
|
177
|
+
File.open(filename, "w") do |file|
|
178
|
+
file.write(JSON.pretty_generate(notes))
|
179
|
+
end
|
180
|
+
|
181
|
+
self.class.new(options.merge(
|
182
|
+
chain: chain,
|
183
|
+
notes: notes,
|
184
|
+
type: type,
|
185
|
+
tag: tag,
|
186
|
+
logger: @@logger,
|
187
|
+
))
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
def log(params)
|
192
|
+
if defined?(LogUtils)
|
193
|
+
LogUtils.json_request_logger(@@logger, params)
|
194
|
+
else
|
195
|
+
@@logger.info(params)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def get_id(type, params = {})
|
200
|
+
id = "#{type}_id".to_sym
|
201
|
+
params[id]
|
202
|
+
end
|
203
|
+
|
204
|
+
def make_collection_ids(ids = @ids)
|
205
|
+
ids.map { |id|
|
206
|
+
id.class == Hash ? id : { collection_id: id.to_s }
|
207
|
+
}
|
208
|
+
end
|
209
|
+
|
210
|
+
# Returns JSON parsed list of logged items
|
211
|
+
def get_logged_items(options = {})
|
212
|
+
options ||= {}
|
213
|
+
type ||= options.fetch(:type, "collection")
|
214
|
+
tag ||= options.fetch(:tag, @tag)
|
215
|
+
filename = (@@logger.instance_variable_get :@logdev).filename
|
216
|
+
File.readlines(filename)
|
217
|
+
.map { |log| log.match(/{.*}/).to_s }
|
218
|
+
.select(&:present?)
|
219
|
+
.map { |json| JSON.parse(json) }
|
220
|
+
.select { |item| item["tag"] == tag }
|
221
|
+
.select { |item| item["type"] == type }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "alma/electronic"
|
5
|
+
|
6
|
+
module Alma
|
7
|
+
# Holds some custom business logic for our Alma Electronic API.
|
8
|
+
# This class is not intended for public use.
|
9
|
+
class Electronic::Business
|
10
|
+
# The Service ID is usually the Collection ID grouped by
|
11
|
+
# 2 digits with the first number incremented by 1 and the
|
12
|
+
# fifth number decremented by 1.
|
13
|
+
#
|
14
|
+
# @note However, this pattern does not hold for all cases.
|
15
|
+
#
|
16
|
+
# @param collection_id [String] The electronic collection id.
|
17
|
+
def service_id(collection_id)
|
18
|
+
collection_id.scan(/.{1,2}/).each_with_index.map { |char, index|
|
19
|
+
if index == 0
|
20
|
+
"%02d" % (char.to_i + 1)
|
21
|
+
elsif index == 4
|
22
|
+
"%02d" % (char.to_i - 1)
|
23
|
+
else
|
24
|
+
char
|
25
|
+
end
|
26
|
+
}.join
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/alma/error.rb
CHANGED
@@ -11,5 +11,16 @@ module Alma::Error
|
|
11
11
|
def error
|
12
12
|
@response.fetch('web_service_result', {})
|
13
13
|
end
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
+
module Alma
|
17
|
+
class StandardError < ::StandardError
|
18
|
+
def initialize(message, loggable = {})
|
19
|
+
if Alma.configuration.enable_loggable
|
20
|
+
message = { error: message }.merge(loggable).to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
super message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/alma/fine.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Alma
|
2
|
+
class Fine < AlmaRecord
|
3
|
+
extend Alma::ApiDefaults
|
4
|
+
|
5
|
+
def self.where_user(user_id, args={})
|
6
|
+
|
7
|
+
response = HTTParty.get("#{users_base_path}/#{user_id}/fees", query: args, headers: headers, timeout: timeout)
|
8
|
+
if response.code == 200
|
9
|
+
Alma::FineSet.new(response)
|
10
|
+
else
|
11
|
+
raise StandardError, get_body_from(response)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/alma/fine_set.rb
CHANGED
@@ -1,43 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Alma
|
2
|
-
class FineSet
|
3
|
-
|
4
|
-
|
5
|
-
#include Alma::Error
|
4
|
+
class FineSet < ResultSet
|
5
|
+
class ResponseError < Alma::StandardError
|
6
|
+
end
|
6
7
|
|
7
|
-
attr_reader :
|
8
|
-
def_delegators :
|
8
|
+
attr_reader :results, :raw_response
|
9
|
+
def_delegators :results, :empty?
|
9
10
|
|
10
|
-
def initialize(
|
11
|
-
@
|
11
|
+
def initialize(raw_response)
|
12
|
+
@raw_response = raw_response
|
13
|
+
@response = raw_response.parsed_response
|
14
|
+
validate(raw_response)
|
15
|
+
@results = @response.fetch(key, [])
|
16
|
+
.map { |item| single_record_class.new(item) }
|
12
17
|
end
|
13
18
|
|
14
|
-
def
|
15
|
-
|
19
|
+
def loggable
|
20
|
+
{ uri: @raw_response&.request&.uri.to_s
|
21
|
+
}.select { |k, v| !(v.nil? || v.empty?) }
|
16
22
|
end
|
17
23
|
|
18
|
-
def
|
19
|
-
|
24
|
+
def validate(response)
|
25
|
+
if response.code != 200
|
26
|
+
message = "Could not find fines."
|
27
|
+
log = loggable.merge(response.parsed_response)
|
28
|
+
raise ResponseError.new(message, log)
|
29
|
+
end
|
20
30
|
end
|
21
|
-
alias list each
|
22
31
|
|
23
|
-
def
|
24
|
-
|
32
|
+
def each(&block)
|
33
|
+
@results.each(&block)
|
25
34
|
end
|
26
35
|
|
27
|
-
def
|
28
|
-
|
36
|
+
def success?
|
37
|
+
raw_response.response.code.to_s == "200"
|
29
38
|
end
|
30
|
-
alias :total_sum :sum
|
31
39
|
|
32
|
-
def
|
33
|
-
|
40
|
+
def key
|
41
|
+
"fee"
|
34
42
|
end
|
35
43
|
|
36
|
-
def
|
37
|
-
fetch(
|
44
|
+
def sum
|
45
|
+
fetch("total_sum", 0)
|
38
46
|
end
|
39
|
-
alias :total_records :total_record_count
|
40
47
|
|
48
|
+
alias :total_sum :sum
|
41
49
|
|
50
|
+
def currency
|
51
|
+
fetch("currency", nil)
|
52
|
+
end
|
42
53
|
end
|
43
54
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Alma
|
2
|
+
class ItemRequestOptions < RequestOptions
|
3
|
+
|
4
|
+
class ResponseError < Alma::StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.get(mms_id, holding_id=nil, item_pid=nil, options={})
|
8
|
+
url = "#{bibs_base_path}/#{mms_id}/holdings/#{holding_id}/items/#{item_pid}/request-options"
|
9
|
+
options.select! {|k,_| REQUEST_OPTIONS_PERMITTED_ARGS.include? k }
|
10
|
+
response = HTTParty.get(url, headers: headers, query: options, timeout: timeout)
|
11
|
+
new(response)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate(response)
|
15
|
+
if response.code != 200
|
16
|
+
message = "Could not get item request options."
|
17
|
+
log = loggable.merge(response.parsed_response)
|
18
|
+
raise ResponseError.new(message, log)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/alma/loan.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
module Alma
|
2
2
|
class Loan < AlmaRecord
|
3
|
+
extend Alma::ApiDefaults
|
3
4
|
|
4
5
|
|
5
6
|
def renewable?
|
6
7
|
!!renewable
|
7
8
|
end
|
8
9
|
|
10
|
+
def renewable
|
11
|
+
response.fetch("renewable", false)
|
12
|
+
end
|
13
|
+
|
9
14
|
def overdue?
|
10
15
|
loan_status == "Overdue"
|
11
16
|
end
|
@@ -14,5 +19,18 @@ module Alma
|
|
14
19
|
Alma::User.renew_loan({user_id: user_id, loan_id: loan_id})
|
15
20
|
end
|
16
21
|
|
22
|
+
def self.where_user(user_id, args={})
|
23
|
+
# Always expand renewable unless you really don't want to
|
24
|
+
args[:expand] ||= "renewable"
|
25
|
+
# Default to upper limit
|
26
|
+
args[:limit] ||= 100
|
27
|
+
response = HTTParty.get(
|
28
|
+
"#{users_base_path}/#{user_id}/loans",
|
29
|
+
query: args,
|
30
|
+
headers: headers,
|
31
|
+
timeout: timeout
|
32
|
+
)
|
33
|
+
Alma::LoanSet.new(response, args)
|
34
|
+
end
|
17
35
|
end
|
18
36
|
end
|