usps-imis-api 0.2.1 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3bb98ea050e0d9e6b21b9c67b3e1cc7f74020781556015cc4c03e6b748a8cf3d
4
- data.tar.gz: 8e309c38ebb754244a278de5b4968a6bd917c78d43271a042b0ab5e81846ea10
3
+ metadata.gz: 2fd4e1ff81a49f7bf1c0e18fd6f95563d0e59514a68c17ae69f6e18e3e40106d
4
+ data.tar.gz: 037cfc9634a8db74cd0fb8fbeca66d1d9c98e9eb8e73b677a6933b6caf8ed0dc
5
5
  SHA512:
6
- metadata.gz: b8ac2e1b390b4f1c6922eb2bd48202aff6015af2ebd9a577572e61d4c24d4219c89ee85bc0c76438fd0c97a020c10b60c021fe95a7819bc260a282d7c56a83b8
7
- data.tar.gz: 35aa8c10cc792f75ef6df018264e1171077598a74076193ea083dfd7edb754ed76173531acff8904df00d70adad9506dc844bce4b94d68537e1eca821d7c784d
6
+ metadata.gz: 1cfa43aa4502d9020faebaa25c06d92214ed5484f1ed0f871026be970a3ed37920b50cb00798120e7345f574c628b9426d47e8de39a64a16d33282e5919e3e49
7
+ data.tar.gz: 741f1a0444f81f4fb8d8803858d427a9c21508e779431ed48e849412b5ae8c43d871becb8ce83923c5a3545421124258bc5630aa613d603f2ca0cef9ff87c147
data/.rubocop.yml CHANGED
@@ -10,6 +10,7 @@ AllCops:
10
10
  - vendor/**/*
11
11
  - tmp/**/*
12
12
  NewCops: enable
13
+ SuggestExtensions: false
13
14
 
14
15
  Layout/FirstHashElementIndentation:
15
16
  EnforcedStyle: consistent
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'dotenv', '>= 3.1.4'
7
+ gem 'rake', '>= 13.2.1'
7
8
  gem 'rspec', '>= 3.13.0'
8
9
  gem 'rubocop', '>= 1.66.1'
9
10
  gem 'rubocop-rspec', '>= 3.1.0'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- usps-imis-api (0.2.1)
4
+ usps-imis-api (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -18,6 +18,7 @@ GEM
18
18
  racc
19
19
  racc (1.8.1)
20
20
  rainbow (3.1.1)
21
+ rake (13.2.1)
21
22
  regexp_parser (2.9.2)
22
23
  rspec (3.13.0)
23
24
  rspec-core (~> 3.13.0)
@@ -61,6 +62,7 @@ PLATFORMS
61
62
 
62
63
  DEPENDENCIES
63
64
  dotenv (>= 3.1.4)
65
+ rake (>= 13.2.1)
64
66
  rspec (>= 3.13.0)
65
67
  rubocop (>= 1.66.1)
66
68
  rubocop-rspec (>= 3.1.0)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/Readme.md CHANGED
@@ -13,7 +13,7 @@ gem install usps-imis-api
13
13
  or add this line to your Gemfile:
14
14
 
15
15
  ```ruby
16
- gem 'usps-imis-api', '>= 0.2.0'
16
+ gem 'usps-imis-api', '>= 0.3.0'
17
17
  ```
18
18
 
19
19
  ## Setup
@@ -22,9 +22,9 @@ Include the library, and set the configuration:
22
22
 
23
23
  ```ruby
24
24
  require 'dotenv/load' # Optionally load environment variables from `.env` file
25
- require 'usps-imis-api'
25
+ require 'usps/imis'
26
26
 
27
- Imis.configure do |config|
27
+ Usps::Imis.configure do |config|
28
28
  config.environment = :development # Rails.env
29
29
  config.imis_id_query_name = ENV['IMIS_ID_QUERY_NAME']
30
30
 
@@ -33,10 +33,12 @@ Imis.configure do |config|
33
33
  end
34
34
  ```
35
35
 
36
+ When using `bin/console`, this configuration will be run by default.
37
+
36
38
  Instantiate the API object:
37
39
 
38
40
  ```ruby
39
- api = Imis::Api.new
41
+ api = Usps::Imis::Api.new
40
42
  ```
41
43
 
42
44
  ### Authentication
@@ -148,7 +150,7 @@ For supported panels (usually, business objects with composite identity keys), y
148
150
  with them in the same general way:
149
151
 
150
152
  ```ruby
151
- vsc = Imis::Panel::Vsc.new
153
+ vsc = Usps::Imis::Panel::Vsc.new
152
154
 
153
155
  vsc.api.imis_id = 6374
154
156
 
@@ -162,6 +164,12 @@ vsc.update(certificate: 'E136924', year: 2024, count: 43, ordinal: ordinal)
162
164
  vsc.destroy(ordinal)
163
165
  ```
164
166
 
167
+ Panels are also accessible directly from the API object:
168
+
169
+ ```ruby
170
+ api.panels.vsc.get(1417)
171
+ ```
172
+
165
173
  ### DSL Mode
166
174
 
167
175
  Instead of manually setting the current iMIS ID, then running individual queries, you can instead
@@ -204,6 +212,12 @@ bundle exec rubocop
204
212
 
205
213
  Testing and linting are automatically run on every push.
206
214
 
215
+ ## Development
216
+
217
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
218
+
219
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
220
+
207
221
  ## PHP
208
222
 
209
223
  This same API is
data/bin/console ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'usps/imis'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # Configure from the environment automatically
11
+ require 'dotenv/load'
12
+ Usps::Imis.configure do |config|
13
+ config.environment = :development
14
+ config.imis_id_query_name = ENV['IMIS_ID_QUERY_NAME']
15
+
16
+ config.username = ENV['IMIS_USERNAME']
17
+ config.password = ENV['IMIS_PASSWORD']
18
+ end
19
+
20
+ require "irb"
21
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ class Api
6
+ AUTHENTICATION_PATH = 'Token'
7
+ API_PATH = 'api'
8
+ QUERY_PATH = 'api/Query'
9
+ PANELS = Struct.new(:vsc)
10
+
11
+ attr_reader :token, :token_expiration, :imis_id
12
+
13
+ def initialize(skip_authentication: false)
14
+ authenticate unless skip_authentication
15
+ end
16
+
17
+ # Manually set the current ID, if you already have it for a given member
18
+ #
19
+ def imis_id=(id)
20
+ @imis_id = id.to_s
21
+ end
22
+
23
+ # Convert a member's certificate number into an iMIS ID number
24
+ #
25
+ def imis_id_for(certificate)
26
+ result = query(Imis.configuration.imis_id_query_name, { certificate: certificate })
27
+ @imis_id = result['Items']['$values'][0]['ID']
28
+ end
29
+
30
+ # Run requests as DSL, with specific iMIS ID only maintained for this scope
31
+ #
32
+ # This should be used with methods that do not change the value of `imis_id`
33
+ #
34
+ def with(id, &block)
35
+ old_id = imis_id
36
+ self.imis_id = id
37
+ instance_eval(&block)
38
+ ensure
39
+ self.imis_id = old_id
40
+ end
41
+
42
+ # Get a business object for the current member
43
+ #
44
+ def get(business_object_name, url_id: nil)
45
+ uri = uri_for(business_object_name, url_id: url_id)
46
+ request = Net::HTTP::Get.new(uri)
47
+ result = submit(uri, authorize(request))
48
+ JSON.parse(result.body)
49
+ end
50
+
51
+ # Update only specific fields on a business object for the current member
52
+ #
53
+ # fields - hash of shape: { field_name => new_value }
54
+ #
55
+ def put_fields(business_object_name, fields, url_id: nil)
56
+ updated = filter_fields(business_object_name, fields)
57
+ put(business_object_name, updated, url_id: url_id)
58
+ end
59
+
60
+ # Update a business object for the current member
61
+ #
62
+ def put(business_object_name, body, url_id: nil)
63
+ uri = uri_for(business_object_name, url_id: url_id)
64
+ request = Net::HTTP::Put.new(uri)
65
+ request.body = JSON.dump(body)
66
+ result = submit(uri, authorize(request))
67
+ JSON.parse(result.body)
68
+ end
69
+
70
+ # Create a business object for the current member
71
+ #
72
+ def post(business_object_name, body, url_id: nil)
73
+ uri = uri_for(business_object_name, url_id: url_id)
74
+ request = Net::HTTP::Post.new(uri)
75
+ request.body = JSON.dump(body)
76
+ result = submit(uri, authorize(request))
77
+ JSON.parse(result.body)
78
+ end
79
+
80
+ # Remove a business object for the current member
81
+ #
82
+ # Returns empty string on success
83
+ #
84
+ def delete(business_object_name, url_id: nil)
85
+ uri = uri_for(business_object_name, url_id: url_id)
86
+ request = Net::HTTP::Delete.new(uri)
87
+ result = submit(uri, authorize(request))
88
+ result.body
89
+ end
90
+
91
+ # Run an IQA Query
92
+ #
93
+ # query_name - the full path of the query in IQA, e.g. `$/_ABC/Fiander/iMIS_ID`
94
+ # query_params - hash of shape: { param_name => param_value }
95
+ #
96
+ def query(query_name, query_params = {})
97
+ query_params[:QueryName] = query_name
98
+ path = "#{QUERY_PATH}?#{query_params.to_query}"
99
+ uri = URI(File.join(imis_hostname, path))
100
+ request = Net::HTTP::Get.new(uri)
101
+ result = submit(uri, authorize(request))
102
+ JSON.parse(result.body)
103
+ end
104
+
105
+ def mapper
106
+ @mapper ||= Mapper.new(self)
107
+ end
108
+
109
+ def panels
110
+ @panels ||= PANELS.new(
111
+ Panel::Vsc.new(self)
112
+ )
113
+ end
114
+
115
+ def update(data)
116
+ mapper.update(data)
117
+ end
118
+
119
+ private
120
+
121
+ def client(uri)
122
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
123
+ http.use_ssl = true
124
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
125
+ end
126
+ end
127
+
128
+ def imis_hostname
129
+ Imis.configuration.hostname
130
+ end
131
+
132
+ # Authorize a request prior to submitting
133
+ #
134
+ # If the current token is missing/expired, request a new one
135
+ #
136
+ def authorize(request)
137
+ authenticate if token_expiration < Time.now
138
+ request.tap { |r| r.add_field('Authorization', "Bearer #{token}") }
139
+ end
140
+
141
+ # Construct a business object API endpoint address
142
+ #
143
+ def uri_for(business_object_name, url_id: nil)
144
+ url_id ||= imis_id
145
+ url_id = CGI.escape(url_id)
146
+ URI(File.join(imis_hostname, "#{API_PATH}/#{business_object_name}/#{url_id}"))
147
+ end
148
+
149
+ def submit(uri, request)
150
+ client(uri).request(request).tap do |result|
151
+ raise Error::Api.from(result) unless result.is_a?(Net::HTTPSuccess)
152
+ end
153
+ end
154
+
155
+ # Authenticate to the iMIS API, and store the access token and expiration time
156
+ #
157
+ def authenticate
158
+ uri = URI(File.join(imis_hostname, AUTHENTICATION_PATH))
159
+ req = Net::HTTP::Post.new(uri)
160
+ authentication_data = {
161
+ grant_type: 'password',
162
+ username: Imis.configuration.username,
163
+ password: Imis.configuration.password
164
+ }
165
+ req.body = URI.encode_www_form(authentication_data)
166
+ result = submit(uri, req)
167
+ json = JSON.parse(result.body)
168
+
169
+ @token = json['access_token']
170
+ @token_expiration = Time.parse(json['.expires'])
171
+ end
172
+
173
+ # Manually assemble the matching data structure, with fields in the correct order
174
+ #
175
+ def filter_fields(business_object_name, fields)
176
+ existing = get(business_object_name)
177
+
178
+ JSON.parse(JSON.dump(existing)).tap do |updated|
179
+ # The first property is always the iMIS ID again
180
+ updated['Properties']['$values'] = [existing['Properties']['$values'][0]]
181
+
182
+ # Iterate through all existing fields
183
+ existing['Properties']['$values'].each do |value|
184
+ next unless fields.keys.include?(value['Name'])
185
+
186
+ # Strings are not wrapped in the type definition structure
187
+ new_value = fields[value['Name']]
188
+ if new_value.is_a?(String)
189
+ value['Value'] = new_value
190
+ else
191
+ value['Value']['$value'] = new_value
192
+ end
193
+
194
+ # Add the completed field with the updated value
195
+ updated['Properties']['$values'] << value
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ class Config
6
+ IMIS_ROOT_URL_PROD = 'https://portal.americasboatingclub.org'
7
+ IMIS_ROOT_URL_DEV = 'https://abcdev.imiscloud.com'
8
+
9
+ attr_accessor :environment, :imis_id_query_name, :username, :password
10
+
11
+ def initialize
12
+ yield self if block_given?
13
+ end
14
+
15
+ def hostname
16
+ case environment.to_sym
17
+ when :production
18
+ IMIS_ROOT_URL_PROD
19
+ when :development
20
+ IMIS_ROOT_URL_DEV
21
+ else
22
+ raise "Unexpected API environment: #{environment}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Error
6
+ class Api < StandardError
7
+ attr_reader :response
8
+ attr_accessor :metadata
9
+
10
+ def self.from(response)
11
+ new('The iMIS API returned an error.', response)
12
+ end
13
+
14
+ def initialize(message, response, metadata = {})
15
+ super(message)
16
+ @metadata = metadata
17
+ @response = response
18
+ end
19
+
20
+ def bugsnag_meta_data
21
+ { api: { status: status, body: body }.merge!(metadata) }
22
+ end
23
+
24
+ private
25
+
26
+ def status
27
+ @status ||=
28
+ case response.code
29
+ when '400'
30
+ :bad_request
31
+ when '401'
32
+ :unauthorized # RequestVerificationToken invalid
33
+ when '404'
34
+ :not_found
35
+ when '422'
36
+ :unprocessable_entity # validation error
37
+ when /^50\d$/
38
+ :internal_server_error # error within iMIS
39
+ end
40
+ end
41
+
42
+ def response_body
43
+ @response_body ||= JSON.parse(response.body)
44
+ rescue StandardError
45
+ @response_body ||= response.body
46
+ end
47
+
48
+ def body
49
+ return response_body unless response_body.is_a?(Hash)
50
+
51
+ case response_body['error']
52
+ when 'invalid_grant'
53
+ response_body['error_description']
54
+ else
55
+ # Unknown error type: just use the raw response
56
+ response.body
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Error
6
+ class Mapper < StandardError; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ class Mapper
6
+ FIELD_MAPPING = {
7
+ vessel_examiner: %w[ABC_ASC_Individual_Demog Vol_Vessel_Examiner],
8
+ mm: %w[ABC_ASC_Individual_Demog TotMMS],
9
+ mm_updated: %w[ABC_ASC_Individual_Demog MMS_Updated]
10
+ }.freeze
11
+
12
+ attr_reader :api
13
+
14
+ def initialize(api = nil)
15
+ @api = api || Api.new
16
+ end
17
+
18
+ # Update a member's data on multiple affected business objects by arbitrary field names
19
+ # Does not require knowing which business object / iMIS-specific field name to use
20
+ #
21
+ # Only available for previously-mapped fields
22
+ #
23
+ # `data` is a hash of shape { field_key => value }
24
+ #
25
+ def update(data)
26
+ updates = data.each_with_object({}) do |(field_key, value), hash|
27
+ map_update(field_key) do |business_object_name, field|
28
+ hash[business_object_name] ||= {}
29
+ hash[business_object_name][field] = value
30
+ end
31
+ end
32
+
33
+ updates.map do |business_object_name, field_updates|
34
+ api.put_fields(business_object_name, field_updates)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def map_update(field_name)
41
+ if FIELD_MAPPING.key?(field_name.to_sym)
42
+ business_object_name, field = FIELD_MAPPING[field_name.to_sym]
43
+ yield(business_object_name, field)
44
+ else
45
+ missing_mapping(field_name)
46
+ end
47
+ end
48
+
49
+ def missing_mapping(field_name)
50
+ unless ENV['TESTING']
51
+ warn(
52
+ "Mapper does not know how to handle field \"#{field_name}\".\n\n" \
53
+ 'You can use api.put_fields(business_object_name, { field_name => value }) ' \
54
+ "if you know the business object and iMIS-specific field name.\n\n"
55
+ )
56
+ end
57
+
58
+ raise(
59
+ Imis::Error::Mapper,
60
+ "Unrecognized field: \"#{field_name}\". " \
61
+ 'Please report what data you are attempting to work with to ITCom leadership.'
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Panel
6
+ class Vsc
7
+ BUSINESS_OBJECT = 'ABC_ASC_Vessel_Safety_Checks'
8
+
9
+ attr_reader :api
10
+
11
+ def initialize(api = nil)
12
+ @api = api || Api.new
13
+ end
14
+
15
+ def get(ordinal)
16
+ api.get(BUSINESS_OBJECT, url_id: "~#{api.imis_id}|#{ordinal}")
17
+ end
18
+
19
+ def create(data)
20
+ api.post(BUSINESS_OBJECT, payload(data), url_id: '')
21
+ end
22
+
23
+ def update(data)
24
+ api.put(BUSINESS_OBJECT, payload(data), url_id: "~#{api.imis_id}|#{data[:ordinal]}")
25
+ end
26
+
27
+ def destroy(ordinal)
28
+ api.delete(BUSINESS_OBJECT, url_id: "~#{api.imis_id}|#{ordinal}")
29
+ end
30
+
31
+ private
32
+
33
+ # rubocop:disable Metrics/MethodLength
34
+ def payload(data)
35
+ identity_type =
36
+ 'System.Collections.ObjectModel.Collection`1[[System.String, mscorlib]], mscorlib'
37
+
38
+ {
39
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericEntityData, Asi.Contracts',
40
+ 'EntityTypeName' => 'ABC_ASC_Vessel_Safety_Checks',
41
+ 'PrimaryParentEntityTypeName' => 'Party',
42
+ 'Identity' => {
43
+ '$type' => 'Asi.Soa.Core.DataContracts.IdentityData, Asi.Contracts',
44
+ 'EntityTypeName' => 'ABC_ASC_Vessel_Safety_Checks',
45
+ 'IdentityElements' => {
46
+ '$type' => identity_type,
47
+ '$values' => [api.imis_id]
48
+ }
49
+ },
50
+ 'PrimaryParentIdentity' => {
51
+ '$type' => 'Asi.Soa.Core.DataContracts.IdentityData, Asi.Contracts',
52
+ 'EntityTypeName' => 'Party',
53
+ 'IdentityElements' => {
54
+ '$type' => identity_type,
55
+ '$values' => [api.imis_id]
56
+ }
57
+ },
58
+ 'Properties' => {
59
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyDataCollection, Asi.Contracts',
60
+ '$values' => [
61
+ {
62
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
63
+ 'Name' => 'ID',
64
+ 'Value' => api.imis_id
65
+ },
66
+ (
67
+ if data[:ordinal]
68
+ {
69
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
70
+ 'Name' => 'Ordinal',
71
+ 'Value' => {
72
+ '$type' => 'System.Int32',
73
+ '$value' => data[:ordinal]
74
+ }
75
+ }
76
+ end
77
+ ),
78
+ {
79
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
80
+ 'Name' => 'Source_System',
81
+ 'Value' => 'Manual ITCom Entry'
82
+ },
83
+ {
84
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
85
+ 'Name' => 'ABC_ECertificate',
86
+ 'Value' => data[:certificate]
87
+ },
88
+ {
89
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
90
+ 'Name' => 'Activity_Type',
91
+ 'Value' => 'VSC'
92
+ },
93
+ {
94
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
95
+ 'Name' => 'Description',
96
+ 'Value' => 'Vessel Safety Checks'
97
+ },
98
+ {
99
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
100
+ 'Name' => 'Effective_Date',
101
+ 'Value' => "#{data[:year]}-12-01T00:00:00"
102
+ },
103
+ {
104
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
105
+ 'Name' => 'Quantity',
106
+ 'Value' => {
107
+ '$type' => 'System.Int32',
108
+ '$value' => data[:count]
109
+ }
110
+ },
111
+ {
112
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
113
+ 'Name' => 'Thru_Date',
114
+ 'Value' => "#{data[:year]}-12-31T00:00:00"
115
+ },
116
+ {
117
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
118
+ 'Name' => 'Transaction_Date',
119
+ 'Value' => Time.now.strftime('%Y-%m-%dT%H:%M:%S')
120
+ }
121
+ ].compact
122
+ }
123
+ }
124
+ end
125
+ # rubocop:enable Metrics/MethodLength
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ VERSION = '0.3.0'
6
+ end
7
+ end