medidata 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 12a82b143cd97a12a83f592971730feca9678a498aceef53671a919f6f3a6413
4
+ data.tar.gz: f4dadff58258bed1a7f6cb9f16dcd0af6a2ee8aae08a698a6acdec048570024f
5
+ SHA512:
6
+ metadata.gz: b9af6ed6ceb4d866e0b806c6686bf40ff35155964c0f24871bdbfbfa5eab2eab65e55b11a5ebd6295fbd888afc7ffa49ed2ab5791307e8b55da6b28fa94156f0
7
+ data.tar.gz: 84bb1fc78121f31c1b237f939dcf0f817c84153968492cc8f31748e86463f65f80976539334ecbab9fb7d2208a255602ccf1036027bfb0a2a3d6108c741b8dc8
data/lib/medidata.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Medidata
2
+ require 'medidata/api'
3
+ end
@@ -0,0 +1,10 @@
1
+ module Medidata::API
2
+ require 'medidata/api/error'
3
+ require 'medidata/api/http'
4
+ require 'medidata/api/type'
5
+ require 'medidata/api/upload'
6
+ require 'medidata/api/download'
7
+ require 'medidata/api/participant'
8
+ require 'medidata/api/notification'
9
+ require 'medidata/api/client'
10
+ end
@@ -0,0 +1,198 @@
1
+ module Medidata::API
2
+ require 'json'
3
+ require "base64"
4
+
5
+ require_relative "error"
6
+ require_relative "http"
7
+
8
+ require_relative "upload"
9
+ require_relative "participant"
10
+ require_relative "notification"
11
+
12
+ class Client
13
+ # * *Args* :
14
+ # - +host+ -> REST API client
15
+ # - +lang+ -> Preferred language for error messages
16
+ def initialize(host:, path_prefix: '', lang: 'de')
17
+ @host = host
18
+ @lang = lang
19
+ rest = REST.new(
20
+ host: host,
21
+ url_path: [path_prefix],
22
+ request_headers: {
23
+ "Accept": "application/json",
24
+ "Accept-Language": lang,
25
+ "DenteoSecret": "yes_it's_us"
26
+ },
27
+ )
28
+
29
+ @rest = rest
30
+ end
31
+
32
+ # Set credentials for this Client's instance
33
+ #
34
+ # * *Args* :
35
+ # - +id+ -> Medidata client ID
36
+ # - +username+ -> Username
37
+ # - +password+ -> password
38
+ def auth(id, username, password)
39
+ @rest.update_headers({
40
+ "X-CLIENT-ID": id,
41
+ "Authorization": "Basic " + Base64.encode64(username + ":" + password).strip
42
+ })
43
+ end
44
+
45
+ # * *Args* :
46
+ # - +limit+ -> The total number of participants allowed
47
+ # - +offset+ -> Pagination offset
48
+ # - +query+ -> ParticipantsQuery object (optional)
49
+ def participants(limit: 100, offset: 0, query: nil)
50
+ qs = {
51
+ "limit": limit,
52
+ "offset": offset
53
+ }
54
+ if query
55
+ qs["lawtype"] = query.lawType if query.lawType
56
+ qs["name"] = query.name if query.name
57
+ qs["glnparticipant"] = query.glnParticipant if query.glnParticipant
58
+ end
59
+
60
+ params = {
61
+ "query_params": qs
62
+ }
63
+ res = @rest.ela.participants.get(params)
64
+ case res.status_code
65
+ when 200 then
66
+ body = res.parsed_body
67
+ raise "Invalid participants response body" unless body.kind_of?(Array)
68
+ body.map { |row| Medidata::API::Participant.new(row) }
69
+ when 400 then
70
+ raise BadRequestError.new("bad request")
71
+ when 401 then
72
+ raise UnauthenticatedError.new("authentication required to load participants")
73
+ when 403 then
74
+ raise PermissionError.new("wrong credentials to load participants")
75
+ else
76
+ raise "Error fetching Medidata participants <#{res.status_code}>"
77
+ end
78
+ end
79
+
80
+ # Upload a document to Medidata
81
+ #
82
+ # * *Args* :
83
+ # - +file+ -> File to upload (e.g. Invoice XML)
84
+ # - +info+ -> Medidata::API::UploadControlData (optional)
85
+ def upload(file, info = nil)
86
+ multipart = Medidata::API::MultipartPost.new
87
+ multipart.with_binary key: "elauploadstream", value: file
88
+ multipart.with_text key: "elauploadinfo", value: Medidata::API::UploadControlData.new(info).to_json if info
89
+
90
+ bounday = "__MEDI_REST_IN_PEACE__"
91
+ params = {
92
+ "request_headers": {
93
+ "Content-Type": "multipart/form-data; charset=utf-8; boundary=#{bounday}"
94
+ },
95
+ "request_body": multipart.build(bounday)
96
+ }
97
+ res = @rest.ela.uploads.post(params)
98
+ case res.status_code
99
+ when 201 then
100
+ # Upload created
101
+ Medidata::API::Upload.new(res.parsed_body)
102
+ when 400 then
103
+ raise BadRequestError.new("bad request")
104
+ when 401 then
105
+ raise UnauthenticatedError.new("authentication required to upload")
106
+ when 403 then
107
+ raise PermissionError.new("wrong credentials to upload")
108
+ when 409 then
109
+ # TODO: Extract transmission reference from response
110
+ raise ConflictError.new("this file has already been uploaded")
111
+ else
112
+ raise "Error uploading to Medidata <#{res.status_code}>"
113
+ end
114
+ end
115
+
116
+ # Check upload status
117
+ #
118
+ # * *Args* :
119
+ # - +tref+ -> Upload's transmission reference
120
+ def upload_status(tref)
121
+ res = @rest.ela.uploads._(tref).status.get
122
+ case res.status_code
123
+ when 200 then
124
+ Medidata::API::Upload.new(res.parsed_body)
125
+ when 400 then
126
+ raise BadRequestError.new("bad request")
127
+ when 401 then
128
+ raise UnauthenticatedError.new("authentication required to check upload status")
129
+ when 403 then
130
+ raise PermissionError.new("wrong credentials to check upload status")
131
+ when 404 then
132
+ raise MissingError.new("upload status not found")
133
+ else
134
+ raise "Error fetching Medidata upload status <#{res.status_code}>"
135
+ end
136
+ end
137
+
138
+ # Load pending notifications
139
+ #
140
+ # * *Args* :
141
+ # - +limit+ -> The total number of notifications allowed
142
+ # - +offset+ -> Pagination offset
143
+ def notifications_pending(limit: 100, offset: 0)
144
+ params = {
145
+ "query_params": {
146
+ "limit": limit,
147
+ "offset": offset
148
+ }
149
+ }
150
+ res = @rest.ela.notifications.get(params)
151
+ case res.status_code
152
+ when 200 then
153
+ body = res.parsed_body
154
+ raise "Invalid notifications response body" unless body.kind_of?(Array)
155
+ body.map { |row| Medidata::API::Notification.new(row) }
156
+ when 400 then
157
+ raise BadRequestError.new("bad request")
158
+ when 401 then
159
+ raise UnauthenticatedError.new("authentication required to load pending notifications")
160
+ when 403 then
161
+ raise PermissionError.new("wrong credentials to load pending notifications")
162
+ else
163
+ raise "Error fetching Medidata notifications <#{res.status_code}>"
164
+ end
165
+ end
166
+
167
+ # Confirm notification receipt
168
+ #
169
+ # All notifications received from Medidata are pending by default and
170
+ # clients have to manually mark them as "read". That should be done
171
+ # on a regular basis according to Medidata.
172
+ #
173
+ # * *Args* :
174
+ # - +id+ -> Notification unique identifier
175
+ def notifications_confirm_receipt(id)
176
+ params = {
177
+ "request_body": {
178
+ "notificationFetched": true
179
+ }
180
+ }
181
+ res = @rest.ela.notifications._(id).status.put(params)
182
+ case res.status_code
183
+ when 200, 204 then
184
+ true
185
+ when 400 then
186
+ raise BadRequestError.new("bad request")
187
+ when 401 then
188
+ raise UnauthenticatedError.new("authentication required to confirm notification receipt")
189
+ when 403 then
190
+ raise PermissionError.new("wrong credentials to confirm notification receipt")
191
+ when 404 then
192
+ raise MissingError.new("notification not found")
193
+ else
194
+ raise "Error updating notification status <#{res.status_code}>"
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,29 @@
1
+ module Medidata::API
2
+ require_relative "type"
3
+
4
+ # A Download is available when an invoice has been sent to the insurance company,
5
+ # but the content check does not go through there and an invoice response
6
+ # (General Invoice Response) is returned.
7
+ # These invoice responses then arrive on the MD client and have to be downloaded
8
+ # and confirmed by the customer's Denteo application using ela / downloads.
9
+ class Download < Dry::Struct
10
+ # Reference to the document. This is generated by the MediData client.
11
+ attribute :transmissionReference, Types::Strict::String
12
+ # Reference to the document
13
+ attribute :documentReference, Types::Strict::String
14
+ # Reference to the business case
15
+ attribute :correlationReference, Types::Strict::String
16
+ # Sender identification
17
+ attribute :senderGln, Types::Strict::String
18
+ # MIME document type
19
+ attribute :docType, Types::Strict::String
20
+ # Length of the document in bytes
21
+ attribute :fileSize, Types::Strict::Integer
22
+ # TEST or PRODUCTION
23
+ attribute :mode, Types::Strict::String
24
+ # Document status
25
+ attribute :status, Types::Strict::String
26
+ # Date document was delivered
27
+ attribute :created, Types::JSON::DateTime
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module Medidata::API
2
+ class MissingError < StandardError
3
+ end
4
+ class BadRequestError < StandardError
5
+ end
6
+ class PermissionError < StandardError
7
+ end
8
+ class UnauthenticatedError < StandardError
9
+ end
10
+ class ConflictError < StandardError
11
+ # TODO: Add resource
12
+ end
13
+ end
@@ -0,0 +1,363 @@
1
+ module Medidata::API
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require "uri"
6
+
7
+ # Holds the response from an API call.
8
+ class Response
9
+ # Provide useful functionality around API rate limiting.
10
+ class Ratelimit
11
+ attr_reader :limit, :remaining, :reset
12
+
13
+ # * *Args* :
14
+ # - +limit+ -> The total number of requests allowed within a rate limit window
15
+ # - +remaining+ -> The number of requests that have been processed within this current rate limit window
16
+ # - +reset+ -> The time (in seconds since Unix Epoch) when the rate limit will reset
17
+ def initialize(limit, remaining, reset)
18
+ @limit = limit.to_i
19
+ @remaining = remaining.to_i
20
+ @reset = Time.at reset.to_i
21
+ end
22
+
23
+ def exceeded?
24
+ remaining <= 0
25
+ end
26
+
27
+ # * *Returns* :
28
+ # - The number of requests that have been used out of this
29
+ # rate limit window
30
+ def used
31
+ limit - remaining
32
+ end
33
+
34
+ # Sleep until the reset time arrives. If given a block, it will
35
+ # be called after sleeping is finished.
36
+ #
37
+ # * *Returns* :
38
+ # - The amount of time (in seconds) that the rate limit slept
39
+ # for.
40
+ def wait!
41
+ now = Time.now.utc.to_i
42
+ duration = (reset.to_i - now) + 1
43
+
44
+ sleep duration if duration >= 0
45
+
46
+ yield if block_given?
47
+
48
+ duration
49
+ end
50
+ end
51
+
52
+ # * *Args* :
53
+ # - +response+ -> A NET::HTTP response object
54
+ #
55
+ attr_reader :status_code, :body, :headers
56
+
57
+ def initialize(response)
58
+ @status_code = response.code.to_i
59
+ @body = response.body
60
+ @headers = response.to_hash
61
+ end
62
+
63
+ # Returns the body as a hash
64
+ #
65
+ def parsed_body
66
+ @parsed_body ||= JSON.parse(@body, symbolize_names: true)
67
+ end
68
+
69
+ def ratelimit
70
+ return @ratelimit unless @ratelimit.nil?
71
+
72
+ limit = headers['X-RateLimit-Limit']
73
+ remaining = headers['X-RateLimit-Remaining']
74
+ reset = headers['X-RateLimit-Reset']
75
+
76
+ # Guard against possibility that one (or probably, all) of the
77
+ # needed headers were not returned.
78
+ @ratelimit = Ratelimit.new(limit, remaining, reset) if limit && remaining && reset
79
+
80
+ @ratelimit
81
+ end
82
+ end
83
+
84
+ # A simple REST client.
85
+ class REST
86
+ attr_reader :host, :request_headers, :url_path, :request, :http
87
+ # * *Args* :
88
+ # - +host+ -> Base URL for the api. (e.g. https://api.sendgrid.com)
89
+ # - +request_headers+ -> A hash of the headers you want applied on
90
+ # all calls
91
+ # (e.g. client._("/v3"))
92
+ # - +url_path+ -> A list of the url path segments
93
+ # - +proxy_options+ -> A hash of proxy settings.
94
+ # (e.g. { host: '127.0.0.1', port: 8080 })
95
+ #
96
+ def initialize(host: nil, request_headers: nil, url_path: nil, http_options: {}, proxy_options: {}) # rubocop:disable Metrics/ParameterLists
97
+ @host = host
98
+ @request_headers = request_headers || {}
99
+ @url_path = url_path || []
100
+ @methods = %w[delete get patch post put]
101
+ @query_params = nil
102
+ @request_body = nil
103
+ @http_options = http_options
104
+ @proxy_options = proxy_options
105
+ end
106
+
107
+ # Update the headers for the request
108
+ #
109
+ # * *Args* :
110
+ # - +request_headers+ -> Hash of request header key/values
111
+ #
112
+ def update_headers(request_headers)
113
+ @request_headers.merge!(request_headers)
114
+ end
115
+
116
+ # Build the final request headers
117
+ #
118
+ # * *Args* :
119
+ # - +request+ -> HTTP::NET request object
120
+ # * *Returns* :
121
+ # - HTTP::NET request object
122
+ #
123
+ def build_request_headers(request)
124
+ @request_headers.each do |key, value|
125
+ request[key] = value
126
+ end
127
+ request
128
+ end
129
+
130
+ # Add query parameters to the url
131
+ #
132
+ # * *Args* :
133
+ # - +url+ -> path to endpoint
134
+ # - +query_params+ -> hash of query parameters
135
+ # * *Returns* :
136
+ # - The url string with the query parameters appended
137
+ #
138
+ def build_query_params(url, query_params)
139
+ params = URI.encode_www_form(query_params)
140
+ url.concat("?#{params}")
141
+ end
142
+
143
+ # Set the query params, request headers and request body
144
+ #
145
+ # * *Args* :
146
+ # - +args+ -> array of args obtained from method_missing
147
+ #
148
+ def build_args(args)
149
+ args.each do |arg|
150
+ arg.each do |key, value|
151
+ case key.to_s
152
+ when 'query_params'
153
+ @query_params = value
154
+ when 'request_headers'
155
+ update_headers(value)
156
+ when 'request_body'
157
+ @request_body = value
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ # Build the final url
164
+ #
165
+ # * *Args* :
166
+ # - +query_params+ -> A hash of query parameters
167
+ # * *Returns* :
168
+ # - The final url string
169
+ #
170
+ def build_url(query_params: nil)
171
+ url = @url_path.join('/')
172
+ url = build_query_params(url, query_params) if query_params
173
+ URI.parse([@host, url].join('/'))
174
+ end
175
+
176
+ # Build the API request for HTTP::NET
177
+ #
178
+ # * *Args* :
179
+ # - +name+ -> method name, received from method_missing
180
+ # - +args+ -> args passed to the method
181
+ # * *Returns* :
182
+ # - A Response object from make_request
183
+ #
184
+ def build_request(name, args)
185
+ build_args(args) if args
186
+ # build the request & http object
187
+ build_http_request(name)
188
+ # set the content type & request body
189
+ build_request_body(name)
190
+ make_request(@http, @request)
191
+ end
192
+
193
+ # Make the API call and return the response. This is separated into
194
+ # it's own function, so we can mock it easily for testing.
195
+ #
196
+ # * *Args* :
197
+ # - +http+ -> NET:HTTP request object
198
+ # - +request+ -> NET::HTTP request object
199
+ # * *Returns* :
200
+ # - Response object
201
+ #
202
+ def make_request(http, request)
203
+ response = http.request(request)
204
+ Response.new(response)
205
+ end
206
+
207
+ # Build HTTP request object
208
+ #
209
+ # * *Returns* :
210
+ # - Request object
211
+ def build_http(host, port)
212
+ params = [host, port]
213
+ params += @proxy_options.values_at(:host, :port, :user, :pass) unless @proxy_options.empty?
214
+ http = add_ssl(Net::HTTP.new(*params))
215
+ http = add_http_options(http) unless @http_options.empty?
216
+ http
217
+ end
218
+
219
+ # Allow for https calls
220
+ #
221
+ # * *Args* :
222
+ # - +http+ -> HTTP::NET object
223
+ # * *Returns* :
224
+ # - HTTP::NET object
225
+ #
226
+ def add_ssl(http)
227
+ if host.start_with?('https')
228
+ http.use_ssl = true
229
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
230
+ end
231
+ http
232
+ end
233
+
234
+ # Add others http options to http object
235
+ #
236
+ # * *Args* :
237
+ # - +http+ -> HTTP::NET object
238
+ # * *Returns* :
239
+ # - HTTP::NET object
240
+ #
241
+ def add_http_options(http)
242
+ @http_options.each do |attribute, value|
243
+ http.send("#{attribute}=", value)
244
+ end
245
+ http
246
+ end
247
+
248
+ # Add variable values to the url.
249
+ # (e.g. /your/api/{variable_value}/call)
250
+ # Another example: if you have a ruby reserved word, such as true,
251
+ # in your url, you must use this method.
252
+ #
253
+ # * *Args* :
254
+ # - +name+ -> Name of the url segment
255
+ # * *Returns* :
256
+ # - REST object
257
+ #
258
+ def _(name = nil)
259
+ url_path = name ? @url_path + [name] : @url_path
260
+ REST.new(
261
+ host: @host,
262
+ request_headers: @request_headers,
263
+ url_path: url_path,
264
+ http_options: @http_options
265
+ )
266
+ end
267
+
268
+ # Dynamically add segments to the url, then call a method.
269
+ # (e.g. client.name.name.get())
270
+ #
271
+ # * *Args* :
272
+ # - The args are automatically passed in
273
+ # * *Returns* :
274
+ # - REST object or Response object
275
+ #
276
+ # rubocop:disable Style/MethodMissingSuper
277
+ # rubocop:disable Style/MissingRespondToMissing
278
+ def method_missing(name, *args, &_block)
279
+ # We have reached the end of the method chain, make the API call
280
+ return build_request(name, args) if @methods.include?(name.to_s)
281
+
282
+ # Add a segment to the URL
283
+ _(name)
284
+ end
285
+
286
+ private
287
+
288
+ def build_http_request(http_method)
289
+ uri = build_url(query_params: @query_params)
290
+ net_http = Kernel.const_get('Net::HTTP::' + http_method.to_s.capitalize)
291
+
292
+ @http = build_http(uri.host, uri.port)
293
+ @request = build_request_headers(net_http.new(uri.request_uri))
294
+ end
295
+
296
+ def build_request_body(http_method)
297
+ if @request_body && is_json_request? && [Hash, Array].include?(@request_body.class)
298
+ @request.body = @request_body.to_json
299
+ @request['Content-Type'] = 'application/json'
300
+ elsif !@request_body && http_method.to_s != 'get'
301
+ @request['Content-Type'] = ''
302
+ else
303
+ @request['Content-Type'] = @request_headers[:'Content-Type']
304
+ @request.body = @request_body
305
+ end
306
+ end
307
+
308
+ def is_json_request?
309
+ !@request_headers.key?(:'Content-Type') || @request_headers[:'Content-Type'] == 'application/json'
310
+ end
311
+ # rubocop:enable Style/MethodMissingSuper
312
+ # rubocop:enable Style/MissingRespondToMissing
313
+ end
314
+
315
+ class MultipartPost
316
+ EOL = "\r\n"
317
+
318
+ def initialize
319
+ @params = Array.new
320
+ end
321
+
322
+ def with_text(key:, value:)
323
+ @params << multipart_text(key, value)
324
+ end
325
+
326
+ def with_binary(key:, value:)
327
+ @params << multipart_binary(key, value)
328
+ end
329
+
330
+ def with_file(key:, value:, filename:, mime_type:)
331
+ @params << multipart_file(key, value, filename, mime_type)
332
+ end
333
+
334
+ def build(bounday)
335
+ body = @params.map{|p| "--#{bounday}#{EOL}" << p}.join ""
336
+ body << "#{EOL}--#{bounday}--#{EOL}"
337
+ end
338
+
339
+ private
340
+
341
+ def multipart_text(key, value)
342
+ "Content-Disposition: form-data; name=\"#{key}\"" <<
343
+ EOL <<
344
+ EOL <<
345
+ "#{value}" << EOL
346
+ end
347
+
348
+ def multipart_file(key, value, filename, mime_type)
349
+ "Content-Disposition: form-data; name=\"#{key}\"; filename=\"#{filename}\"#{EOL}" <<
350
+ "Content-Type: #{mime_type}#{EOL}" <<
351
+ EOL <<
352
+ "#{value}" << EOL
353
+ end
354
+
355
+ def multipart_binary(key, value)
356
+ "Content-Disposition: form-data; name=\"#{key}\"#{EOL}" <<
357
+ "Content-Transfer-Encoding: binary#{EOL}" <<
358
+ "Content-Type: application/octet-stream#{EOL}" <<
359
+ EOL <<
360
+ "#{value}" << EOL
361
+ end
362
+ end
363
+ end
@@ -0,0 +1,39 @@
1
+ module Medidata::API
2
+ require_relative "type"
3
+
4
+ # A Notification is generated when an upload results in the status «ERROR»,
5
+ # e.g. the schema check on the MD client detects an error.
6
+ #
7
+ # The status ERROR indicates that there was an error during the transmission
8
+ # and the recipient did not receive the file.
9
+ # In the associated notification of this ERROR, the customer receives more
10
+ # detailed information on the error code, cause and possible remedial measures.
11
+ class Notification < Dry::Struct
12
+ # ID is a unique identifier for notification
13
+ attribute :id, Types::Strict::Integer.optional
14
+ # Subject of the notification (in three languages)
15
+ attribute :subject, Types::Strict::Hash
16
+ # The content of the notification (in three languages)
17
+ attribute :message, Types::Strict::Hash
18
+ # The notification type.
19
+ attribute :type, Types::Strict::String.optional.default("".freeze)
20
+ # “true” if the notification has already been marked as red, otherwise “false”.
21
+ attribute :read, Types::Strict::Bool.default(false)
22
+ # The notification’s creation date ISO 8601 timestamp
23
+ attribute :created, Types::JSON::DateTime
24
+ # The template’s name
25
+ attribute :template, Types::Strict::String
26
+ # The notification’s mode
27
+ attribute :mode, Types::Strict::String
28
+ # The error code
29
+ attribute :errorCode, Types::Strict::String
30
+ # Possible causes (in three languages). Texts are entered by MediData
31
+ attribute :potentialReasons, Types::Strict::Hash
32
+ # Possible solutionss (in three languages). Texts are entered by MediData
33
+ attribute :possibleSolutions, Types::Strict::Hash
34
+ # Additional parameters for notification
35
+ attribute :notificationParameters, Types::Strict::Hash
36
+ # Additional technical information. (can be used for problem analysis.
37
+ attribute :technicalInformation, Types::Strict::String.optional.default(nil)
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ module Medidata::API
2
+ require_relative "type"
3
+
4
+ class Participant < Dry::Struct
5
+ # Recipient’s GLN number.
6
+ # Note: Always the same GLN as with the parameter “glnReceiver”.
7
+ attribute :glnParticipant, Types::Strict::String
8
+ # Recipient’s GLN number
9
+ attribute :glnReceiver, Types::Strict::String
10
+ # Name of the participant
11
+ attribute :name, Types::Strict::String
12
+ # Address (street name + house number)
13
+ attribute :street, Types::Strict::String
14
+ # Post code
15
+ attribute :zipCode, Types::Strict::String
16
+ # Name of city
17
+ attribute :town, Types::Strict::String
18
+ # List of laws supported by the participants
19
+ attribute :lawTypes, Types::Strict::Array
20
+ # Number registered with the Swiss Federal Office of Public Health (FOPH)
21
+ attribute :bagNumber, Types::Strict::Integer.optional
22
+ # Is the change from TG to TP accepted by the participant. The default is false.
23
+ attribute :tgTpChange, Types::Strict::Bool
24
+ # The sending of a document to this participant may result in additional
25
+ # costs (e.g. printing costs) if additionalCosts is set to true. The default is false.
26
+ attribute :additionalCosts, Types::Strict::Bool
27
+ # Maximum document size that the participant can or wants to receive.
28
+ attribute :maxReceive, Types::Strict::Integer
29
+ # List of the document MIME types not supported by the participant.
30
+ # No documents of this type may be sent to the recipient.
31
+ attribute :notSupported, Types::Strict::Array
32
+ # Specifies whether the participant can receive and process TG documents.
33
+ attribute :tgAllowed, Types::Strict::Bool
34
+ end
35
+
36
+ class ParticipantsQuery
37
+ attr_reader :lawType, :name, :glnParticipant
38
+
39
+ def initialize(lawType: "", name: "", glnParticipant: "")
40
+ @lawType = lawType
41
+ @name = name
42
+ @glnParticipant = glnParticipant
43
+ end
44
+ end
45
+
46
+ # TODO: Add LawType type
47
+ end
@@ -0,0 +1,8 @@
1
+ module Medidata::API
2
+ require 'dry-types'
3
+ require 'dry-struct'
4
+
5
+ module Types
6
+ include Dry.Types()
7
+ end
8
+ end
@@ -0,0 +1,88 @@
1
+ module Medidata::API
2
+ require_relative "type"
3
+
4
+ # Upload: is generally used for sending payloads in XML format, i.e. sending the invoice.
5
+ class Upload < Dry::Struct
6
+ # Reference to the document sent
7
+ attribute :transmissionReference, Types::Strict::String
8
+ # Date document was delivered
9
+ attribute :created, Types::JSON::DateTime.optional.default(nil)
10
+ # Date of last modification
11
+ attribute :modified, Types::JSON::DateTime.optional.default(nil)
12
+ # Current status of main delivery.
13
+ # The status is a summary of all subdeliveries.
14
+ attribute :status, Types::Strict::String.optional.default("".freeze)
15
+ end
16
+
17
+ class UploadControlData < Dry::Struct
18
+ # Transmission mode. The “Mode” control parameter must be identical to
19
+ # the “Mode” attribute in the XML document. Otherwise, an error will be generated.
20
+ attribute :mode, Types::Strict::String.optional.default(nil)
21
+ # Dispatch to organisation (no data warehouse).
22
+ # Overrides the “to” attribute (GLN recipient) in the XML document.
23
+ #
24
+ # In the case of “2000000000008” or “0000000000000”, the following applies
25
+ # • A TP invoice is not delivered to an organisation
26
+ # • A TG invoice is not delivered to an organisation, but to the TG warehouse.
27
+ #
28
+ # An error will be generated with all other differences as compared with
29
+ # the “to” attribute in XML.
30
+ attribute :toOrganization, Types::Strict::String.optional.default(nil)
31
+ # Dispatch to data warehouse (TrustCenter).
32
+ # Overrides the configured data warehouse in the MediData client.
33
+ # This must be a TrustCenter that the sender knows. See participant directory.
34
+ # No dispatch to a data warehouse will be overridden with
35
+ # value “2000000000008” or “0000000000000”.
36
+ attribute :toTrustCenter, Types::Strict::String.optional.default(nil)
37
+ # Dispatch to patients or to their legal representatives (guarantor, debtor).
38
+ #
39
+ # If the value is set to “true”, the dispatch will be triggered; if the
40
+ # value is set to “false”, dispatch will not take place.
41
+ # In the case of a Tiers Payant (TP) doc- ument, this value overrides the
42
+ # TP patient copy to the guarantors.
43
+ # In the case of a Tiers Garant (TG) doc- ument, this value overrides the TG
44
+ # patient invoice and the reimbursement receipt.
45
+ attribute :toPatient, Types::Strict::Bool.default(false)
46
+ # Defines the print language of the document (print layout).
47
+ # If this parameter is not set, it is read from the document or configuration.
48
+ attribute :printLanguage, Types::Strict::String.optional.default(nil)
49
+ # Type of postal dispatch.
50
+ # If this parameter is not set, it is read from the document or configuration.
51
+ attribute :postalDelivery, Types::Strict::String.optional.default(nil)
52
+ # Forces postal delivery to the patient.
53
+ # “true” forces printing with postal delivery
54
+ # (if multiple types of delivery are possible, such as electronic and post- al).
55
+ # “False” means “no forcing”.
56
+ # Application: e.g. a reminder should be sent to the guarantor/debtor by post.
57
+ attribute :forcePostalToPatient, Types::Strict::Bool.default(false)
58
+ # Contains a document designation unique to the participant.
59
+ # The reference data must consist solely of alphanumeric characters
60
+ # (incl. special characters) and be between min. 1 and max. 100 characters long.
61
+ attribute :documentReference, Types::Strict::String.optional.default(nil)
62
+ # Contains a business case number. This number is for all sent and received
63
+ # documents that belong to the same business case in technical terms.
64
+ #
65
+ # In this way, all documents belonging to the same business case can be
66
+ # clearly summarised. If this is missing, it is generated from the document
67
+ # content by the MediData client according to the description in the annex.
68
+ #
69
+ # The business number case must consist solely of alphanumeric characters
70
+ # (incl. special characters) and be between min.
71
+ # 1 and max. 100 characters long.
72
+ attribute :correlationReference, Types::Strict::String.optional.default(nil)
73
+
74
+ def to_json
75
+ s = {}
76
+ s[:mode] = mode if mode
77
+ s[:toOrganization] = toOrganization if toOrganization
78
+ s[:toTrustCenter] = toTrustCenter if toTrustCenter
79
+ s[:toPatient] = toPatient if toPatient
80
+ s[:printLanguage] = printLanguage if printLanguage
81
+ s[:postalDelivery] = postalDelivery if postalDelivery
82
+ s[:forcePostalToPatient] = forcePostalToPatient if forcePostalToPatient
83
+ s[:documentReference] = documentReference if documentReference
84
+ s[:correlationReference] = correlationReference if correlationReference
85
+ JSON.generate(s)
86
+ end
87
+ end
88
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: medidata
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Denteo AG
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-07-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-types
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-struct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.4.0
41
+ description: Ruby client interacting with Medidata REST API
42
+ email: simon@denteo.ch
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/medidata.rb
48
+ - lib/medidata/api.rb
49
+ - lib/medidata/api/client.rb
50
+ - lib/medidata/api/download.rb
51
+ - lib/medidata/api/error.rb
52
+ - lib/medidata/api/http.rb
53
+ - lib/medidata/api/notification.rb
54
+ - lib/medidata/api/participant.rb
55
+ - lib/medidata/api/type.rb
56
+ - lib/medidata/api/upload.rb
57
+ homepage: https://rubygems.org/gems/medidata
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.1.2
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: Ruby client interacting with Medidata REST API
80
+ test_files: []