medidata 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []