business-central 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +120 -0
- data/lib/business_central.rb +22 -0
- data/lib/business_central/client.rb +94 -0
- data/lib/business_central/exceptions.rb +55 -0
- data/lib/business_central/object/account.rb +11 -0
- data/lib/business_central/object/base.rb +105 -0
- data/lib/business_central/object/company.rb +11 -0
- data/lib/business_central/object/helper.rb +15 -0
- data/lib/business_central/object/item.rb +50 -0
- data/lib/business_central/object/purchase_invoice.rb +77 -0
- data/lib/business_central/object/purchase_invoice_line.rb +41 -0
- data/lib/business_central/object/request.rb +80 -0
- data/lib/business_central/object/response.rb +60 -0
- data/lib/business_central/object/validation.rb +54 -0
- data/lib/business_central/object/vendor.rb +45 -0
- data/lib/business_central/version.rb +3 -0
- data/lib/core_ext/string.rb +25 -0
- data/test/business_central/client_test.rb +77 -0
- data/test/business_central/object/account_test.rb +48 -0
- data/test/business_central/object/company_test.rb +47 -0
- data/test/business_central/object/item_test.rb +128 -0
- data/test/business_central/object/purchase_invoice_line_test.rb +129 -0
- data/test/business_central/object/purchase_invoice_test.rb +125 -0
- data/test/business_central/object/request_test.rb +82 -0
- data/test/business_central/object/response_test.rb +26 -0
- data/test/business_central/object/validation_test.rb +61 -0
- data/test/business_central/object/vendor_test.rb +125 -0
- data/test/business_central_test.rb +7 -0
- data/test/test_helper.rb +11 -0
- metadata +190 -0
checksums.yaml
ADDED
@@ -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
|
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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,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
|