business-central 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +120 -0
  4. data/lib/business_central.rb +22 -0
  5. data/lib/business_central/client.rb +94 -0
  6. data/lib/business_central/exceptions.rb +55 -0
  7. data/lib/business_central/object/account.rb +11 -0
  8. data/lib/business_central/object/base.rb +105 -0
  9. data/lib/business_central/object/company.rb +11 -0
  10. data/lib/business_central/object/helper.rb +15 -0
  11. data/lib/business_central/object/item.rb +50 -0
  12. data/lib/business_central/object/purchase_invoice.rb +77 -0
  13. data/lib/business_central/object/purchase_invoice_line.rb +41 -0
  14. data/lib/business_central/object/request.rb +80 -0
  15. data/lib/business_central/object/response.rb +60 -0
  16. data/lib/business_central/object/validation.rb +54 -0
  17. data/lib/business_central/object/vendor.rb +45 -0
  18. data/lib/business_central/version.rb +3 -0
  19. data/lib/core_ext/string.rb +25 -0
  20. data/test/business_central/client_test.rb +77 -0
  21. data/test/business_central/object/account_test.rb +48 -0
  22. data/test/business_central/object/company_test.rb +47 -0
  23. data/test/business_central/object/item_test.rb +128 -0
  24. data/test/business_central/object/purchase_invoice_line_test.rb +129 -0
  25. data/test/business_central/object/purchase_invoice_test.rb +125 -0
  26. data/test/business_central/object/request_test.rb +82 -0
  27. data/test/business_central/object/response_test.rb +26 -0
  28. data/test/business_central/object/validation_test.rb +61 -0
  29. data/test/business_central/object/vendor_test.rb +125 -0
  30. data/test/business_central_test.rb +7 -0
  31. data/test/test_helper.rb +11 -0
  32. metadata +190 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aa79fbf2f11dba355ca4486b9bed998dc3a481aa2c52f66d311bf1525e3f8620
4
+ data.tar.gz: 13910cff1f4af8e26fe89921c1021e2799f074134ebb797163aff3aac863e510
5
+ SHA512:
6
+ metadata.gz: 67aa6439003bd9b3ec1599a97cfd2b235af9c68c140cac188609c0e3d496aac0a67447548c7acb9eef4182d7888a14cf5996bfbb48584db08ed9fb7ac97a8c4e
7
+ data.tar.gz: b5dfd537dd2c023f71196d8960b9bcc5b8ae88115f62bb53c1840531862312689eaba558b5903d0979b2c87070203c80a85294b69078efc014eb0caadf3d8dd2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,120 @@
1
+ # Business Central API Library
2
+
3
+ This library is designed to help ruby/rails based applications communicate with the publicly available API for dynamics 365 business central.
4
+
5
+ If you are unfamiliar with the business central API, you should first read the documentation located at https://docs.microsoft.com/en-us/dynamics-nav/api-reference/v1.0/.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'business-central'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install business-central
22
+
23
+ ## Basic Usage
24
+
25
+ This gem supports both authentication methods:
26
+
27
+ https://docs.microsoft.com/en-us/dynamics-nav/api-reference/v1.0/endpoints-apis-for-dynamics
28
+
29
+ ### Basic Authentication:
30
+
31
+ https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-develop-connect-apps#setting-up-basic-authentication
32
+
33
+ ```Ruby
34
+ require 'business_central'
35
+
36
+ client = BusinessCentral::Client.new(
37
+ username: '<username>',
38
+ password: '<password>',
39
+ url: '<url>'
40
+ )
41
+
42
+ # Find all vendors
43
+ vendors = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').find_all
44
+
45
+ # Find vendor by ID
46
+ vendor = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').find_by_id('3f445b08-2ffd-4f9d-81a0-b82f0d9714c4')
47
+
48
+ # Query vendor by display name
49
+ vendor = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').where("displayName eq 'First Up Consultants'")
50
+
51
+ # Create a new vendor
52
+ vendor = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').create({ display_name: 'hello testing new vendor' })
53
+
54
+ # Update an existing vendor by ID
55
+ vendor = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').update('3f445b08-2ffd-4f9d-81a0-b82f0d9714c4', { phone_number: '1112' })
56
+
57
+ # Delete a vendor
58
+ client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').destroy('f0730ada-b315-ea11-a813-000d3ad21e99')
59
+ ```
60
+
61
+ ### Oauth2 Authentication
62
+
63
+ https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-develop-connect-apps#AAD
64
+
65
+ ```Ruby
66
+ require 'business_central'
67
+
68
+ # Create client - used to connect to the API
69
+ client = BusinessCentral::Client.new(
70
+ tenant_id: '<tenant_id>',
71
+ application_id: '<application_id>',
72
+ secret_key: '<application_secret_key>',
73
+ url: '<url>'
74
+ )
75
+
76
+ # Controller endpoint 1
77
+ client.authorize({ state: '1234' }, oauth_authorize_callback: redirect_url )
78
+
79
+ # Redirect URL endpoint - safely store the token to be re-used later
80
+ token = client.request_token(params[:code], oauth_token_callback: redirect_url)
81
+
82
+ client.authorize_from_token(
83
+ token: token.token,
84
+ refresh_token: token.refresh_token,
85
+ expires_at: DateTime.current + 3600,
86
+ expires_in: 3600
87
+ )
88
+
89
+ # Find all vendors
90
+ vendors = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').find_all
91
+
92
+ # Find vendor by ID
93
+ vendor = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').find_by_id('3f445b08-2ffd-4f9d-81a0-b82f0d9714c4')
94
+
95
+ # Query vendor by display name
96
+ vendor = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').where("displayName eq 'First Up Consultants'")
97
+
98
+ # Create a new vendor
99
+ vendor = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').create({ display_name: 'hello testing' })
100
+
101
+ # Update an existing vendor by ID
102
+ vendor = client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').update('3f445b08-2ffd-4f9d-81a0-b82f0d9714c4', { phone_number: '1112' })
103
+
104
+ # Delete a vendor
105
+ client.vendor(company_id: '3a502065-2a08-4c3b-9468-fb83642d3d3a').destroy('f0730ada-b315-ea11-a813-000d3ad21e99')
106
+ ```
107
+
108
+ ## Development
109
+
110
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests.
111
+
112
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
113
+
114
+ ## Contributing
115
+
116
+ Bug reports and pull requests are welcome on GitHub at https://github.com/JDrizzy/business-central.
117
+
118
+ ## License
119
+
120
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,22 @@
1
+ require 'oauth2'
2
+ require 'oauth2/error'
3
+ require 'net/http'
4
+ require 'json'
5
+
6
+ require 'core_ext/string'
7
+
8
+ require 'business_central/object/helper'
9
+ require 'business_central/object/base'
10
+ require 'business_central/object/validation'
11
+ require 'business_central/object/response'
12
+ require 'business_central/object/request'
13
+
14
+ require 'business_central/object/account'
15
+ require 'business_central/object/company'
16
+ require 'business_central/object/vendor'
17
+ require 'business_central/object/item'
18
+ require 'business_central/object/purchase_invoice'
19
+ require 'business_central/object/purchase_invoice_line'
20
+
21
+ require 'business_central/exceptions'
22
+ require 'business_central/client'
@@ -0,0 +1,94 @@
1
+ module BusinessCentral
2
+ class Client
3
+ extend BusinessCentral::Object::Helper
4
+
5
+ DEFAULT_URL = 'https://api.businesscentral.dynamics.com/v1.0/api/beta'.freeze
6
+
7
+ attr_reader :tenant_id,
8
+ :username,
9
+ :password,
10
+ :application_id,
11
+ :secret_key,
12
+ :url,
13
+ :oauth2_client
14
+
15
+ alias_method :access_token, :oauth2_client
16
+
17
+ object :account
18
+ object :company
19
+ object :vendor
20
+ object :purchase_invoice
21
+ object :purchase_invoice_line
22
+ object :item
23
+
24
+ def initialize(options = {})
25
+ opts = options.dup
26
+ @tenant_id = opts.delete(:tenant_id)
27
+ @username = opts.delete(:username)
28
+ @password = opts.delete(:password)
29
+ @url = opts.delete(:url) || DEFAULT_URL
30
+ @application_id = opts.delete(:application_id)
31
+ @secret_key = opts.delete(:secret_key)
32
+ end
33
+
34
+ def authorize(params = {}, oauth_authorize_callback: '')
35
+ params[:redirect_uri] = oauth_authorize_callback
36
+ begin
37
+ oauth2_client.auth_code.authorize_url(params)
38
+ rescue Oauth2::Error => error
39
+ handle_error(error)
40
+ end
41
+ end
42
+
43
+ def request_token(code = '', oauth_token_callback: '')
44
+ begin
45
+ oauth2_client.auth_code.get_token(code, redirect_uri: oauth_token_callback)
46
+ rescue OAuth2::Error => error
47
+ handle_error(error)
48
+ end
49
+ end
50
+
51
+ def authorize_from_token(token: '', refresh_token: '', expires_at: nil, expires_in: nil)
52
+ @oauth2_client = OAuth2::AccessToken.new(
53
+ oauth2_client,
54
+ token,
55
+ refresh_token: refresh_token,
56
+ expires_at: expires_at,
57
+ expires_in: expires_in,
58
+ )
59
+ end
60
+
61
+ def refresh_token
62
+ @oauth2_client.refresh!
63
+ end
64
+
65
+ private
66
+
67
+ def oauth2_client
68
+ if @oauth2_client.nil?
69
+ @oauth2_client = OAuth2::Client.new(
70
+ @application_id,
71
+ @secret_key,
72
+ {
73
+ site: "https://login.windows.net/#{@tenant_id}",
74
+ authorize_url: 'oauth2/authorize?resource=https://api.businesscentral.dynamics.com',
75
+ token_url: 'oauth2/token?resource=https://api.businesscentral.dynamics.com'
76
+ }
77
+ )
78
+ end
79
+
80
+ return @oauth2_client
81
+ end
82
+
83
+ def handle_error(error)
84
+ if error.code.present?
85
+ case error.code
86
+ when 'invalid_client'
87
+ raise InvalidClientException.new
88
+ end
89
+ else
90
+ raise ApiException.new(error.message)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,55 @@
1
+ module BusinessCentral
2
+
3
+ class BusinessCentralError < StandardError; end
4
+
5
+ class InvalidClientException < BusinessCentralError
6
+ def message
7
+ 'Invalid application setup'
8
+ end
9
+ end
10
+
11
+ class ApiException < BusinessCentralError
12
+ def initialize(message)
13
+ @message = message
14
+ end
15
+
16
+ def message
17
+ @message
18
+ end
19
+ end
20
+
21
+ class CompanyNotFoundException < BusinessCentralError
22
+ def message
23
+ 'Company not found'
24
+ end
25
+ end
26
+
27
+ class UnauthorizedException < BusinessCentralError
28
+ def message
29
+ 'Unauthorized - The credentials provided are incorrect'
30
+ end
31
+ end
32
+
33
+ class InvalidObjectException < BusinessCentralError
34
+ def initialize(errors)
35
+ @errors = errors
36
+ end
37
+
38
+ def message
39
+ @errors.each do |error|
40
+ "#{error[:field]} - #{error[:message]}"
41
+ end
42
+ end
43
+ end
44
+
45
+ class NoSupportedMethod < BusinessCentralError
46
+ def initialize(method, allowed_methods)
47
+ @method = method
48
+ @allowed_methods = allowed_methods
49
+ end
50
+
51
+ def message
52
+ "#{method} method is currently not support. Allowed methods are: #{allowed_methods.join(', ')}"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ module BusinessCentral
2
+ module Object
3
+ class Account < Base
4
+ OBJECT = 'accounts'.freeze
5
+
6
+ OBJECT_METHODS = [
7
+ :get
8
+ ].freeze
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,105 @@
1
+ module BusinessCentral
2
+ module Object
3
+ class Base
4
+ attr_reader :client,
5
+ :company_id,
6
+ :parent_path,
7
+ :path,
8
+ :errors
9
+
10
+ def initialize(client, args = {})
11
+ @client = client
12
+ @company_id = args[:company_id] if !args.nil?
13
+ @parent_path = []
14
+ @path = ''
15
+ @errors = []
16
+ end
17
+
18
+ def find_all
19
+ if method_supported?(:get)
20
+ Request.get(@client, build_url(parent_path: @parent_path, child_path: object_name))
21
+ else
22
+ raise BusinessCentral::NoSupportedMethod.new(:get, object_methods)
23
+ end
24
+ end
25
+
26
+ def find_by_id(id)
27
+ if method_supported?(:get)
28
+ Request.get(@client, build_url(parent_path: @parent_path, child_path: object_name, child_id: id))
29
+ else
30
+ raise BusinessCentral::NoSupportedMethod.new(:get, object_methods)
31
+ end
32
+ end
33
+
34
+ def where(query = '')
35
+ if method_supported?(:get)
36
+ Request.get(@client, build_url(parent_path: @parent_path, child_path: object_name, filter: query))
37
+ else
38
+ raise BusinessCentral::NoSupportedMethod.new(:get, object_methods)
39
+ end
40
+ end
41
+
42
+ def create(params = {})
43
+ if method_supported?(:post)
44
+ if Validation.new(object_validation, params).valid?
45
+ Request.post(@client, build_url(parent_path: @parent_path, child_path: object_name), params)
46
+ end
47
+ else
48
+ raise BusinessCentral::NoSupportedMethod.new(:post, object_methods)
49
+ end
50
+ end
51
+
52
+ def update(id, params = {})
53
+ if method_supported?(:patch)
54
+ object = find_by_id(id)
55
+ if Validation.new(object_validation, params).valid?
56
+ Request.patch(@client, build_url(parent_path: @parent_path, child_path: object_name, child_id: id), object[:etag], params)
57
+ end
58
+ else
59
+ raise BusinessCentral::NoSupportedMethod.new(:patch, object_methods)
60
+ end
61
+ end
62
+
63
+ def destroy(id)
64
+ if method_supported?(:delete)
65
+ object = find_by_id(id)
66
+ Request.delete(@client, build_url(parent_path: @parent_path, child_path: object_name, child_id: id), object[:etag])
67
+ else
68
+ raise BusinessCentral::NoSupportedMethod.new(:delete, object_methods)
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def object_name
75
+ self.class.const_get(:OBJECT)
76
+ end
77
+
78
+ def object_validation
79
+ self.class.const_get(:OBJECT_VALIDATION)
80
+ end
81
+
82
+ def object_methods
83
+ self.class.const_get(:OBJECT_METHODS)
84
+ end
85
+
86
+ def method_supported?(method)
87
+ return true if object_methods.include?(method)
88
+ return false
89
+ end
90
+
91
+ def build_url(parent_path: [], child_path: '', child_id: '', filter: '')
92
+ url_builder(parent_path, child_path, child_id, filter)
93
+ end
94
+
95
+ def url_builder(parent_path = [], child_path = '', child_id = '', filter = '')
96
+ url = @client.url
97
+ url += parent_path.map { |parent| "/#{parent[:path]}(#{parent[:id]})" }.join('') if !parent_path.empty?
98
+ url += "/#{child_path}" if !child_path.blank?
99
+ url += "(#{child_id})" if !child_id.blank?
100
+ url += "?$filter=#{filter}" if !filter.blank?
101
+ return url
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,11 @@
1
+ module BusinessCentral
2
+ module Object
3
+ class Company < Base
4
+ OBJECT = 'companies'.freeze
5
+
6
+ OBJECT_METHODS = [
7
+ :get
8
+ ].freeze
9
+ end
10
+ end
11
+ end