alma 0.2.4 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +54 -0
- data/.circleci/setup-rubygems.sh +3 -0
- data/.github/dependabot.yml +7 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +134 -0
- data/.ruby-version +1 -1
- data/Gemfile +5 -1
- data/Guardfile +75 -0
- data/README.md +146 -57
- data/Rakefile +3 -1
- data/alma.gemspec +21 -12
- data/lib/alma.rb +34 -54
- data/lib/alma/alma_record.rb +3 -3
- data/lib/alma/api_defaults.rb +39 -0
- data/lib/alma/availability_response.rb +69 -31
- data/lib/alma/bib.rb +54 -29
- data/lib/alma/bib_holding.rb +25 -0
- data/lib/alma/bib_item.rb +164 -0
- data/lib/alma/bib_item_set.rb +93 -0
- data/lib/alma/bib_set.rb +5 -10
- data/lib/alma/config.rb +10 -4
- data/lib/alma/course.rb +47 -0
- data/lib/alma/course_set.rb +17 -0
- 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 +16 -4
- data/lib/alma/fine.rb +16 -0
- data/lib/alma/fine_set.rb +41 -8
- data/lib/alma/item_request_options.rb +23 -0
- data/lib/alma/library.rb +29 -0
- data/lib/alma/library_set.rb +21 -0
- data/lib/alma/loan.rb +31 -2
- data/lib/alma/loan_set.rb +62 -4
- data/lib/alma/location.rb +29 -0
- data/lib/alma/location_set.rb +21 -0
- data/lib/alma/renewal_response.rb +25 -14
- data/lib/alma/request.rb +167 -0
- data/lib/alma/request_options.rb +66 -0
- data/lib/alma/request_set.rb +69 -5
- data/lib/alma/response.rb +45 -0
- data/lib/alma/result_set.rb +27 -35
- data/lib/alma/user.rb +142 -86
- data/lib/alma/user_request.rb +19 -0
- data/lib/alma/user_set.rb +5 -6
- data/lib/alma/version.rb +3 -1
- data/log/.gitignore +4 -0
- metadata +149 -10
- data/.travis.yml +0 -5
- data/lib/alma/api.rb +0 -33
data/lib/alma/loan.rb
CHANGED
@@ -1,9 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Alma
|
2
4
|
class Loan < AlmaRecord
|
5
|
+
extend Alma::ApiDefaults
|
6
|
+
|
7
|
+
|
8
|
+
def renewable?
|
9
|
+
!!renewable
|
10
|
+
end
|
11
|
+
|
12
|
+
def renewable
|
13
|
+
response.fetch("renewable", false)
|
14
|
+
end
|
15
|
+
|
16
|
+
def overdue?
|
17
|
+
loan_status == "Overdue"
|
18
|
+
end
|
3
19
|
|
4
20
|
def renew
|
5
|
-
Alma::User.renew_loan({user_id: user_id, loan_id: loan_id})
|
21
|
+
Alma::User.renew_loan({ user_id: user_id, loan_id: loan_id })
|
6
22
|
end
|
7
23
|
|
24
|
+
def self.where_user(user_id, args = {})
|
25
|
+
# Always expand renewable unless you really don't want to
|
26
|
+
args[:expand] ||= "renewable"
|
27
|
+
# Default to upper limit
|
28
|
+
args[:limit] ||= 100
|
29
|
+
response = HTTParty.get(
|
30
|
+
"#{users_base_path}/#{user_id}/loans",
|
31
|
+
query: args,
|
32
|
+
headers: headers,
|
33
|
+
timeout: timeout
|
34
|
+
)
|
35
|
+
Alma::LoanSet.new(response, args)
|
36
|
+
end
|
8
37
|
end
|
9
|
-
end
|
38
|
+
end
|
data/lib/alma/loan_set.rb
CHANGED
@@ -1,18 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Alma
|
2
4
|
class LoanSet < ResultSet
|
5
|
+
class ResponseError < Alma::StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
alias :total_records :total_record_count
|
9
|
+
|
10
|
+
|
11
|
+
attr_reader :results, :raw_response
|
12
|
+
def_delegators :results, :empty?
|
13
|
+
|
14
|
+
def initialize(raw_response, search_args = {})
|
15
|
+
@raw_response = raw_response
|
16
|
+
@response = raw_response.parsed_response
|
17
|
+
@search_args = search_args
|
18
|
+
validate(raw_response)
|
19
|
+
@results = @response.fetch(key, [])
|
20
|
+
.map { |item| single_record_class.new(item) }
|
21
|
+
# args passed to the search that returned this set
|
22
|
+
# such as limit, expand, order_by, etc
|
23
|
+
end
|
3
24
|
|
25
|
+
def loggable
|
26
|
+
{ search_args: @search_args,
|
27
|
+
uri: @raw_response&.request&.uri.to_s
|
28
|
+
}.select { |k, v| !(v.nil? || v.empty?) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate(response)
|
32
|
+
if response.code != 200
|
33
|
+
error = "Could not find loans info."
|
34
|
+
log = loggable.merge(response.parsed_response)
|
35
|
+
raise ResponseError.new(error, log)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def all
|
40
|
+
Enumerator.new do |yielder|
|
41
|
+
offset = 0
|
42
|
+
loop do
|
43
|
+
extra_args = @search_args.merge({ limit: 100, offset: offset })
|
44
|
+
r = (offset == 0) ? self : single_record_class.where_user(user_id, extra_args)
|
45
|
+
unless r.empty?
|
46
|
+
r.map { |item| yielder << item }
|
47
|
+
offset += 100
|
48
|
+
else
|
49
|
+
raise StopIteration
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def each(&block)
|
56
|
+
@results.each(&block)
|
57
|
+
end
|
4
58
|
|
5
|
-
def
|
6
|
-
|
59
|
+
def success?
|
60
|
+
raw_response.response.code.to_s == "200"
|
7
61
|
end
|
8
62
|
|
9
|
-
def
|
10
|
-
|
63
|
+
def key
|
64
|
+
"item_loan"
|
11
65
|
end
|
12
66
|
|
13
67
|
def single_record_class
|
14
68
|
Alma::Loan
|
15
69
|
end
|
16
70
|
|
71
|
+
private
|
72
|
+
def user_id
|
73
|
+
@user_id ||= results.first.user_id
|
74
|
+
end
|
17
75
|
end
|
18
76
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alma
|
4
|
+
class Location < AlmaRecord
|
5
|
+
extend Alma::ApiDefaults
|
6
|
+
|
7
|
+
def self.all(library_code:, args: {})
|
8
|
+
response = HTTParty.get("#{configuration_base_path}/libraries/#{library_code}/locations", query: args, headers: headers, timeout: timeout)
|
9
|
+
if response.code == 200
|
10
|
+
LocationSet.new(response)
|
11
|
+
else
|
12
|
+
raise StandardError, get_body_from(response)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find(library_code:, location_code:, args: {})
|
17
|
+
response = HTTParty.get("#{configuration_base_path}/libraries/#{library_code}/locations/#{location_code}", query: args, headers: headers, timeout: timeout)
|
18
|
+
if response.code == 200
|
19
|
+
AlmaRecord.new(response)
|
20
|
+
else
|
21
|
+
raise StandardError, get_body_from(response)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.get_body_from(response)
|
26
|
+
JSON.parse(response.body)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alma
|
4
|
+
class LocationSet < ResultSet
|
5
|
+
def_delegators :results, :[], :empty?
|
6
|
+
|
7
|
+
def each(&block)
|
8
|
+
results.each(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def results
|
12
|
+
@results ||= @response.fetch(key, [])
|
13
|
+
.map { |item| single_record_class.new(item) }
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
def key
|
18
|
+
"location"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,42 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Alma
|
2
4
|
class RenewalResponse
|
3
|
-
|
4
|
-
include Alma::Error
|
5
|
-
|
6
5
|
def initialize(response)
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
6
|
+
@raw_response = response
|
7
|
+
@response = response.parsed_response
|
8
|
+
@success = response.has_key?("loan_id")
|
9
|
+
end
|
10
|
+
|
11
|
+
def loggable
|
12
|
+
{ uri: @raw_response&.request&.uri.to_s
|
13
|
+
}.select { |k, v| !(v.nil? || v.empty?) }
|
10
14
|
end
|
11
15
|
|
12
16
|
def renewed?
|
13
|
-
@
|
17
|
+
@success
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_error?
|
21
|
+
!renewed?
|
14
22
|
end
|
15
23
|
|
16
24
|
def due_date
|
17
|
-
@
|
25
|
+
@response.fetch("due_date", "")
|
18
26
|
end
|
19
27
|
|
20
28
|
|
21
29
|
def due_date_pretty
|
22
|
-
Time.parse(due_date).strftime(
|
30
|
+
Time.parse(due_date).strftime("%m-%e-%y %H:%M")
|
23
31
|
end
|
24
32
|
|
25
33
|
def item_title
|
26
|
-
if
|
27
|
-
@
|
34
|
+
if renewed?
|
35
|
+
@response["title"]
|
28
36
|
else
|
29
|
-
|
37
|
+
"This Item"
|
30
38
|
end
|
31
39
|
end
|
32
40
|
|
33
41
|
def message
|
34
|
-
if
|
42
|
+
if renewed?
|
35
43
|
"#{item_title} is now due #{due_date}"
|
36
44
|
else
|
37
45
|
"#{item_title} could not be renewed."
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
49
|
+
def error_message
|
50
|
+
@response unless renewed?
|
51
|
+
end
|
41
52
|
end
|
42
|
-
end
|
53
|
+
end
|
data/lib/alma/request.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alma
|
4
|
+
class BibRequest
|
5
|
+
class ItemAlreadyExists < Alma::StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
extend Alma::ApiDefaults
|
9
|
+
|
10
|
+
REQUEST_TYPES = %w[HOLD DIGITIZATION BOOKING]
|
11
|
+
|
12
|
+
def self.submit(args)
|
13
|
+
request = new(args)
|
14
|
+
response = HTTParty.post(
|
15
|
+
"#{bibs_base_path}/#{request.mms_id}/requests",
|
16
|
+
query: { user_id: request.user_id },
|
17
|
+
headers: headers,
|
18
|
+
body: request.body.to_json
|
19
|
+
)
|
20
|
+
Alma::Response.new(response)
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :mms_id, :user_id, :body, :request_type
|
24
|
+
def initialize(args)
|
25
|
+
@mms_id = args.delete(:mms_id) { raise ArgumentError.new(":mms_id option must be specified to create request") }
|
26
|
+
@user_id = args.delete(:user_id) { raise ArgumentError.new(":user_id option must be specified to create request") }
|
27
|
+
@request_type = args.fetch(:request_type, "NOT_SPECIFIED")
|
28
|
+
validate!(args)
|
29
|
+
normalize!(args)
|
30
|
+
@body = args
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def normalize!(args)
|
35
|
+
request_type_normalization!(args)
|
36
|
+
additional_normalization!(args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def request_type_normalization!(args)
|
40
|
+
method = "#{@request_type.downcase}_normalization".to_sym
|
41
|
+
send(method, args) if respond_to? method
|
42
|
+
end
|
43
|
+
|
44
|
+
# Intended to be overridden by subclasses, allowing extra normalization logic to be provided
|
45
|
+
def additional_normalization!(args)
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate!(args)
|
49
|
+
unless REQUEST_TYPES.include?(request_type)
|
50
|
+
raise ArgumentError.new(":request_type option must be specified and one of #{REQUEST_TYPES.join(", ")} to submit a request")
|
51
|
+
end
|
52
|
+
request_type_validation!(args)
|
53
|
+
additional_validation!(args)
|
54
|
+
end
|
55
|
+
|
56
|
+
def request_type_validation!(args)
|
57
|
+
method = "#{@request_type.downcase}_validation".to_sym
|
58
|
+
send(method, args) if respond_to? method
|
59
|
+
end
|
60
|
+
|
61
|
+
# Intended to be overridden by subclasses, allowing extra validation logic to be provided
|
62
|
+
def additional_validation!(args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def digitization_normalization(args)
|
66
|
+
if args[:target_destination].is_a? String
|
67
|
+
args[:target_destination] = { value: args[:target_destination] }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def digitization_validation(args)
|
72
|
+
args.fetch(:target_destination) do
|
73
|
+
raise ArgumentError.new(
|
74
|
+
":target_destination option must be specified when request_type is DIGITIZATION"
|
75
|
+
)
|
76
|
+
end
|
77
|
+
pd = args.fetch(:partial_digitization) do
|
78
|
+
raise ArgumentError.new(
|
79
|
+
":partial_digitization option must be specified when request_type is DIGITIZATION"
|
80
|
+
)
|
81
|
+
end
|
82
|
+
if pd == true
|
83
|
+
args.fetch(:comment) do
|
84
|
+
raise ArgumentError.new(
|
85
|
+
":comment option must be specified when :request_type is DIGITIZATION and :partial_digitization is true"
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def booking_normalization(args)
|
92
|
+
if args[:material_type].is_a? String
|
93
|
+
args[:material_type] = { value: args[:material_type] }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def booking_validation(args)
|
98
|
+
args.fetch(:booking_start_date) do
|
99
|
+
raise ArgumentError.new(
|
100
|
+
":booking_start_date option must be specified when request_type is BOOKING"
|
101
|
+
)
|
102
|
+
end
|
103
|
+
args.fetch(:booking_end_date) do
|
104
|
+
raise ArgumentError.new(
|
105
|
+
":booking_end_date option must be specified when request_type is BOOKING"
|
106
|
+
)
|
107
|
+
end
|
108
|
+
args.fetch(:pickup_location_type) do
|
109
|
+
raise ArgumentError.new(
|
110
|
+
":pickup_location_type option must be specified when request_type is BOOKING"
|
111
|
+
)
|
112
|
+
end
|
113
|
+
args.fetch(:pickup_location_library) do
|
114
|
+
raise ArgumentError.new(
|
115
|
+
":pickup_location_library option must be specified when request_type is BOOKING"
|
116
|
+
)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def hold_normalization(args)
|
121
|
+
# if args[:material_type].is_a? String
|
122
|
+
# args[:material_type] = { value: args[:material_type] }
|
123
|
+
# end
|
124
|
+
end
|
125
|
+
|
126
|
+
def hold_validation(args)
|
127
|
+
args.fetch(:pickup_location_type) do
|
128
|
+
raise ArgumentError.new(
|
129
|
+
":pickup_location_type option must be specified when request_type is HOLD"
|
130
|
+
)
|
131
|
+
end
|
132
|
+
args.fetch(:pickup_location_library) do
|
133
|
+
raise ArgumentError.new(
|
134
|
+
":pickup_location_library option must be specified when request_type is HOLD"
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class ItemRequest < BibRequest
|
141
|
+
def self.submit(args)
|
142
|
+
request = new(args)
|
143
|
+
response = HTTParty.post(
|
144
|
+
"#{bibs_base_path}/#{request.mms_id}/holdings/#{request.holding_id}/items/#{request.item_pid}/requests",
|
145
|
+
query: { user_id: request.user_id },
|
146
|
+
headers: headers,
|
147
|
+
body: request.body.to_json
|
148
|
+
)
|
149
|
+
Alma::Response.new(response)
|
150
|
+
end
|
151
|
+
|
152
|
+
attr_reader :holding_id, :item_pid
|
153
|
+
def initialize(args)
|
154
|
+
super(args)
|
155
|
+
@holding_id = args.delete(:holding_id) { raise ArgumentError.new(":holding_id option must be specified to create request") }
|
156
|
+
@item_pid = args.delete(:item_pid) { raise ArgumentError.new(":item_pid option must be specified to create request") }
|
157
|
+
end
|
158
|
+
|
159
|
+
def additional_validation!(args)
|
160
|
+
args.fetch(:description) do
|
161
|
+
raise ArgumentError.new(
|
162
|
+
":description option must be specified when request_type is DIGITIZATION"
|
163
|
+
)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alma
|
4
|
+
class RequestOptions
|
5
|
+
class ResponseError < Alma::StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
extend Forwardable
|
9
|
+
extend Alma::ApiDefaults
|
10
|
+
|
11
|
+
attr_accessor :request_options, :raw_response
|
12
|
+
def_delegators :raw_response, :response, :request
|
13
|
+
|
14
|
+
REQUEST_OPTIONS_PERMITTED_ARGS = [:user_id]
|
15
|
+
|
16
|
+
def initialize(response)
|
17
|
+
@raw_response = response
|
18
|
+
validate(response)
|
19
|
+
@request_options = response.parsed_response["request_option"]
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def self.get(mms_id, options = {})
|
24
|
+
url = "#{bibs_base_path}/#{mms_id}/request-options"
|
25
|
+
options.select! { |k, _| REQUEST_OPTIONS_PERMITTED_ARGS.include? k }
|
26
|
+
response = HTTParty.get(url, headers: headers, query: options, timeout: timeout)
|
27
|
+
new(response)
|
28
|
+
end
|
29
|
+
|
30
|
+
def loggable
|
31
|
+
{ uri: @raw_response&.request&.uri.to_s
|
32
|
+
}.select { |k, v| !(v.nil? || v.empty?) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate(response)
|
36
|
+
if response.code != 200
|
37
|
+
raise ResponseError.new("Could not get request options.", loggable.merge(response.parsed_response))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def hold_allowed?
|
42
|
+
!request_options.nil? &&
|
43
|
+
!request_options.select { |option| option["type"]["value"] == "HOLD" }.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def digitization_allowed?
|
47
|
+
!request_options.nil? &&
|
48
|
+
!request_options.select { |option| option["type"]["value"] == "DIGITIZATION" }.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
def booking_allowed?
|
52
|
+
!request_options.nil? &&
|
53
|
+
!request_options.select { |option| option["type"]["value"] == "BOOKING" }.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def resource_sharing_broker_allowed?
|
57
|
+
!request_options.nil? &&
|
58
|
+
!request_options.select { |option| option["type"]["value"] == "RS_BROKER" }.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def ez_borrow_link
|
62
|
+
broker = request_options.select { |option| option["type"]["value"] == "RS_BROKER" }
|
63
|
+
broker.collect { |opt| opt["request_url"] }.first
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|