erp_integration 0.2.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 +7 -0
- data/.flayignore +10 -0
- data/.github/dependabot.yml +7 -0
- data/.github/pull_request_template.md +7 -0
- data/.github/workflows/code_quality.yml +27 -0
- data/.github/workflows/main.yml +26 -0
- data/.github/workflows/pull_requests.yml +18 -0
- data/.gitignore +15 -0
- data/.reek.yml +136 -0
- data/.rspec +1 -0
- data/.rubocop.yml +88 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +20 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +81 -0
- data/Rakefile +14 -0
- data/bin/console +15 -0
- data/bin/flay +29 -0
- data/bin/prerelease +20 -0
- data/bin/rake +29 -0
- data/bin/reek +29 -0
- data/bin/release +7 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/setup +8 -0
- data/erp_integration.gemspec +54 -0
- data/lib/erp_integration/clients/fulfil_client.rb +73 -0
- data/lib/erp_integration/configuration.rb +65 -0
- data/lib/erp_integration/errors.rb +32 -0
- data/lib/erp_integration/middleware/error_handling.rb +31 -0
- data/lib/erp_integration/order.rb +16 -0
- data/lib/erp_integration/orders/fulfil_order.rb +36 -0
- data/lib/erp_integration/resource.rb +78 -0
- data/lib/erp_integration/version.rb +3 -0
- data/lib/erp_integration.rb +24 -0
- metadata +291 -0
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'pathname'
|
12
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path('bundle', __dir__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'bundler/setup'
|
28
|
+
|
29
|
+
load Gem.bin_path('rake', 'rake')
|
data/bin/reek
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'reek' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'pathname'
|
12
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path('bundle', __dir__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'bundler/setup'
|
28
|
+
|
29
|
+
load Gem.bin_path('reek', 'reek')
|
data/bin/release
ADDED
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'pathname'
|
12
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path('bundle', __dir__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'bundler/setup'
|
28
|
+
|
29
|
+
load Gem.bin_path('rspec-core', 'rspec')
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'pathname'
|
12
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path('bundle', __dir__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'bundler/setup'
|
28
|
+
|
29
|
+
load Gem.bin_path('rubocop', 'rubocop')
|
data/bin/setup
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/erp_integration/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'erp_integration'
|
7
|
+
spec.version = ErpIntegration::VERSION
|
8
|
+
spec.authors = ['Stefan Vermaas']
|
9
|
+
spec.email = ['stefan@knowndecimal.com']
|
10
|
+
|
11
|
+
spec.summary = 'Connects Mejuri with third-party ERP vendors'
|
12
|
+
spec.license = 'MIT'
|
13
|
+
spec.homepage = 'https://www.github.com/mejuri-inc/erp-integration'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.7')
|
15
|
+
|
16
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
17
|
+
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
+
spec.metadata['source_code_uri'] = 'https://www.github.com/mejuri-inc/erp-integration'
|
20
|
+
spec.metadata['changelog_uri'] = 'https://www.github.com/mejuri-inc/erp-integration/blob/main/CHANGELOG.md'
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
spec.bindir = 'exe'
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ['lib']
|
30
|
+
|
31
|
+
# ActiveSupport is used for extended support of String inflections.
|
32
|
+
spec.add_dependency 'activesupport', '>= 0.12'
|
33
|
+
|
34
|
+
# Faraday is a HTTP/REST API client library to interact with third-party API vendors
|
35
|
+
spec.add_dependency 'faraday', '>= 0.17.3', '< 1.8.0'
|
36
|
+
spec.add_dependency 'faraday_middleware', '~> 0.14.0'
|
37
|
+
|
38
|
+
# Development dependencies
|
39
|
+
spec.add_development_dependency 'byebug', '~> 11.0'
|
40
|
+
spec.add_development_dependency 'flay', '~> 2.12', '>= 2.12.1'
|
41
|
+
spec.add_development_dependency 'github_changelog_generator', '~> 1.15.2'
|
42
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
43
|
+
spec.add_development_dependency 'reek', '< 6.0'
|
44
|
+
spec.add_development_dependency 'rspec', '~> 3.10'
|
45
|
+
spec.add_development_dependency 'rubocop', '< 0.82.0'
|
46
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.5'
|
47
|
+
spec.add_development_dependency 'rubocop-rspec', '< 1.39.0'
|
48
|
+
spec.add_development_dependency 'webmock', '~> 3.14.0'
|
49
|
+
|
50
|
+
# The `parallel` gem is a dev dependency for Rubocop. However, the versions
|
51
|
+
# for parallel after 1.19.2 don't work with ruby 2.3.x. As ruby 2.3.x is
|
52
|
+
# required for Mejuri, we're manually locking it to stay on `1.19.2`.
|
53
|
+
spec.add_development_dependency 'parallel', '1.19.2'
|
54
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday_middleware'
|
4
|
+
|
5
|
+
module ErpIntegration
|
6
|
+
module Clients
|
7
|
+
# The `FulfilClient` is a simple HTTP client configured to be used for API
|
8
|
+
# requests to the Fulfil endpoints.
|
9
|
+
class FulfilClient
|
10
|
+
attr_reader :api_key, :merchant_id
|
11
|
+
attr_writer :connection, :faraday_adapter
|
12
|
+
|
13
|
+
def initialize(api_key:, merchant_id:)
|
14
|
+
@api_key = api_key
|
15
|
+
@merchant_id = merchant_id
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generates the url prefix for the Faraday connection client.
|
19
|
+
# @return [String] The base url for the Fulfil HTTP client
|
20
|
+
def base_url
|
21
|
+
"https://#{merchant_id}.fulfil.io/"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets the default adapter for the Faraday Connection.
|
25
|
+
# @return [Symbol] The default Faraday adapter
|
26
|
+
def faraday_adapter
|
27
|
+
@faraday_adapter ||= Faraday.default_adapter
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sets up the Faraday connection to talk to the Fulfil API.
|
31
|
+
# @return [Faraday::Connection] The configured Faraday connection
|
32
|
+
def connection
|
33
|
+
@connection ||= Faraday.new(url: base_url) do |faraday|
|
34
|
+
faraday.headers = default_headers
|
35
|
+
|
36
|
+
faraday.request :json # Encode request bodies as JSON
|
37
|
+
faraday.request :retry # Retry transient failures
|
38
|
+
|
39
|
+
faraday.response :follow_redirects
|
40
|
+
faraday.response :json # Decode response bodies as JSON
|
41
|
+
|
42
|
+
# Custom error handling for the error response
|
43
|
+
faraday.use ErpIntegration::Middleware::ErrorHandling
|
44
|
+
|
45
|
+
# Adapter definition should be last in order to make the json parsers be loaded correctly
|
46
|
+
faraday.adapter faraday_adapter
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
%i[delete get patch put post].each do |action_name|
|
51
|
+
define_method(action_name) do |path, options = {}|
|
52
|
+
connection.public_send(action_name, "api/#{version}/#{path}", options).body
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets the default version for the Fulfil API endpoints.
|
57
|
+
# @return [String] The Fulfil API version to be used
|
58
|
+
def version
|
59
|
+
@version ||= 'v2'
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def default_headers
|
65
|
+
{
|
66
|
+
'Accept': 'application/json',
|
67
|
+
'Content-Type': 'application/json',
|
68
|
+
'X-API-KEY': api_key
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
# Use the `Configuration` class to configure the ERP Integration gem. Use
|
5
|
+
# an initializer in your project configure the ERP Integration gem.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# ```ruby
|
9
|
+
# # config/initializers/erp_integration.rb
|
10
|
+
# ErpIntegration.configure do |config|
|
11
|
+
# config.fulfil_api_key = "..."
|
12
|
+
# end
|
13
|
+
# ```
|
14
|
+
class Configuration
|
15
|
+
# The `fulfil_api_key` is used by the `FulfilClient` to authorize the
|
16
|
+
# requests to the Fulfil API endpoints.
|
17
|
+
# @return [String] The API key for Fulfil.
|
18
|
+
attr_accessor :fulfil_api_key
|
19
|
+
|
20
|
+
# The `fulfil_merchant_id` is used by the `FulfilClient` to connect to
|
21
|
+
# the right Fulfil API endpoints.
|
22
|
+
# @return [String] The merchant ID for Fulfil.
|
23
|
+
attr_accessor :fulfil_merchant_id
|
24
|
+
|
25
|
+
# Allows configuring an adapter for the `Order` resource. When none is
|
26
|
+
# configured, it will default to Fulfil.
|
27
|
+
# @return [Symbol] The configured adapter for the orders
|
28
|
+
attr_writer :order_adapter
|
29
|
+
|
30
|
+
def initialize(**options)
|
31
|
+
options.each_pair do |key, value|
|
32
|
+
public_send("#{key}=", value) if respond_to?("#{key}=")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def order_adapter
|
37
|
+
@order_adapter || :fulfil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns ERP Integration's configuration.
|
42
|
+
# @return [ErpIntegration::Configuration] ERP Integration's configuration
|
43
|
+
def self.config
|
44
|
+
@config ||= Configuration.new
|
45
|
+
end
|
46
|
+
|
47
|
+
# Allows setting a new configuration for the ERP Integration gem.
|
48
|
+
# @return [ErpIntegration::Configuration] ERP Integration's new configuration
|
49
|
+
def self.config=(configuration)
|
50
|
+
raise BadConfiguration unless configuration.is_a?(Configuration)
|
51
|
+
|
52
|
+
@config = configuration
|
53
|
+
end
|
54
|
+
|
55
|
+
# Allows modifying ERP Integration's configuration.
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# ErpIntegration.configure do |config|
|
59
|
+
# config.some_api_key = "..."
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
def self.configure
|
63
|
+
yield(config)
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
# The `ErpIntegration::Error` is the default error class for any exception raised
|
5
|
+
# on purpose by this gem.
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
# The `ErpIntegration::BadConfiguration` is raised whenever the newly provided
|
9
|
+
# configuration is not a valid configuration object.
|
10
|
+
class BadConfiguration < Error
|
11
|
+
def initialize
|
12
|
+
super(
|
13
|
+
'[ERP Integration] The provided configuration object is not a ' \
|
14
|
+
'ErpIntegration::Configuration instance. Please provide a ' \
|
15
|
+
'ErpIntegration::Configuration instance when overriding the configuration directly.'
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class HttpError < Faraday::Error
|
21
|
+
class BadRequest < HttpError; end
|
22
|
+
class AuthorizationRequired < HttpError; end
|
23
|
+
class PaymentRequired < HttpError; end
|
24
|
+
class Forbidden < HttpError; end
|
25
|
+
class NotFound < HttpError; end
|
26
|
+
class MethodNotAllowed < HttpError; end
|
27
|
+
class NotAccepted < HttpError; end
|
28
|
+
class UnprocessableEntity < HttpError; end
|
29
|
+
class TooManyRequests < HttpError; end
|
30
|
+
class InternalServerError < HttpError; end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
module Middleware
|
5
|
+
# The `ErrorHandling` middleware allows to raise our own exceptions that
|
6
|
+
# are easier to catch for the host application (e.g. `mejuri-web`).
|
7
|
+
class ErrorHandling < Faraday::Response::Middleware
|
8
|
+
HTTP_ERROR_CODES = {
|
9
|
+
400 => ErpIntegration::HttpError::BadRequest,
|
10
|
+
401 => ErpIntegration::HttpError::AuthorizationRequired,
|
11
|
+
402 => ErpIntegration::HttpError::PaymentRequired,
|
12
|
+
403 => ErpIntegration::HttpError::Forbidden,
|
13
|
+
404 => ErpIntegration::HttpError::NotFound,
|
14
|
+
405 => ErpIntegration::HttpError::MethodNotAllowed,
|
15
|
+
406 => ErpIntegration::HttpError::NotAccepted,
|
16
|
+
422 => ErpIntegration::HttpError::UnprocessableEntity,
|
17
|
+
429 => ErpIntegration::HttpError::TooManyRequests,
|
18
|
+
500 => ErpIntegration::HttpError::InternalServerError
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
def on_complete(response)
|
22
|
+
key = response[:status].to_i
|
23
|
+
raise HTTP_ERROR_CODES[key], response_values(response) if HTTP_ERROR_CODES.key?(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def response_values(response)
|
27
|
+
{ status: response.status, headers: response.response_headers, body: response.body }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
# The `Order` class exposes available ERP Order operations
|
5
|
+
# assigning them to the proper adapter
|
6
|
+
class Order < Resource
|
7
|
+
attr_accessor :id, :number
|
8
|
+
|
9
|
+
# Allows cancelling the entire sales order.
|
10
|
+
# @param id [Integer|String] The ID of the to be cancelled order.
|
11
|
+
# @return [boolean] Whether the sales order was cancelled successfully or not.
|
12
|
+
def self.cancel(id)
|
13
|
+
adapter.new.cancel(id)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
module Orders
|
5
|
+
# The `FulfilOrder` handles all interaction with sales orders in Fulfil.
|
6
|
+
class FulfilOrder
|
7
|
+
extend Forwardable
|
8
|
+
def_delegator 'ErpIntegration', :config
|
9
|
+
|
10
|
+
# Allows cancelling the entire sales order in Fulfil.
|
11
|
+
# @param id [Integer|String] The ID of the to be cancelled order.
|
12
|
+
# @return [boolean] Whether the sales order was cancelled successfully or not.
|
13
|
+
def cancel(id)
|
14
|
+
client.put("model/sale.sale/#{id}/cancel")
|
15
|
+
true
|
16
|
+
|
17
|
+
# Fulfil will return an 400 (a.k.a. "Bad Request") status code when a sales order couldn't
|
18
|
+
# be cancelled. If a sales order isn't cancellable by design, no exception should be raised.
|
19
|
+
#
|
20
|
+
# See the Fulfil's documentation for more information:
|
21
|
+
# https://developers.fulfil.io/rest_api/model/sale.sale/#cancel-a-sales-order
|
22
|
+
rescue ErpIntegration::HttpError::BadRequest
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def client
|
29
|
+
@client ||= Clients::FulfilClient.new(
|
30
|
+
api_key: config.fulfil_api_key,
|
31
|
+
merchant_id: config.fulfil_merchant_id
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ErpIntegration
|
4
|
+
# The `ErpIntegration::Resource` is a generic, re-usable model for third-party sources.
|
5
|
+
#
|
6
|
+
# For the `ErpIntegration::Resource`, we're using the adapter pattern.
|
7
|
+
# Meaning; the `ErpIntegration::Resource` is a facade that delegates the
|
8
|
+
# actual work to an adapter.
|
9
|
+
#
|
10
|
+
# Every class that inherits from the `ErpIntegration::Resource`, will be able
|
11
|
+
# to configure a designated adapter. This allows configuring an adapter
|
12
|
+
# per resource to maximize the flexibility.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# ErpIntegration.configure do |config|
|
16
|
+
# config.order_adapter = :fulfil
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# $ ErpIntegration::Order.adapter
|
20
|
+
# => #<ErpIntegration::Orders::FulfilOrder />
|
21
|
+
#
|
22
|
+
# To add a new resource, follow these steps:
|
23
|
+
# 1. Add a new `attr_writer` in the `ErpIntegration::Configuration` class.
|
24
|
+
# 2. Add a new method to the `ErpIntegration::Configuration` that sets up the
|
25
|
+
# default adapter.
|
26
|
+
# 3. Create a new generic resource model in the `lib/erp_integration` folder.
|
27
|
+
# 4. Create a new pluralized folder name in the `lib/erp_integration` folder
|
28
|
+
# (e.g. `orders` for the `Order` resource).
|
29
|
+
# 5. Create a new adapter class prefixed with the adapter's name
|
30
|
+
# (e.g. `FulfilOrder` for the `Order` resource in the `lib/erp_integration/orders` folder).
|
31
|
+
class Resource
|
32
|
+
attr_accessor :raw_api_response
|
33
|
+
|
34
|
+
def initialize(attributes = {})
|
35
|
+
@raw_api_response = attributes
|
36
|
+
|
37
|
+
attributes.each_pair do |name, value|
|
38
|
+
public_send(:"#{name}=", value) if respond_to?(:"#{name}=")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
# Dynamically defines and loads the adapter for the class inheriting from
|
44
|
+
# the `ErpIntegration::Resource`.
|
45
|
+
# @return [Class] The adapter class for the resource.
|
46
|
+
def adapter
|
47
|
+
return @adapter if defined?(@adapter)
|
48
|
+
|
49
|
+
require_relative File.join(File.dirname(__FILE__), '..', "#{adapter_path}.rb")
|
50
|
+
@adapter = adapter_klass.constantize
|
51
|
+
end
|
52
|
+
|
53
|
+
# Dynamically exposes the adapter class to the resource.
|
54
|
+
# @return [String] the adapter class as a string
|
55
|
+
def adapter_klass
|
56
|
+
"ErpIntegration::#{adapter_path.classify}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Retrieves the adapter type for the resource from the global configuration.
|
60
|
+
# @return [String] the adapter type for the resource
|
61
|
+
def adapter_type
|
62
|
+
ErpIntegration.config.send("#{resource_name}_adapter").to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
# Provides a relative path to the adapter for the resource.
|
66
|
+
# @return [String] the path to the adapter
|
67
|
+
def adapter_path
|
68
|
+
"#{resource_name.pluralize}/#{adapter_type}_#{resource_name}"
|
69
|
+
end
|
70
|
+
|
71
|
+
# Derives the name of the resource from the class name.
|
72
|
+
# @return [String] the resource name
|
73
|
+
def resource_name
|
74
|
+
name.split('::').last.underscore
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
5
|
+
require 'faraday'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
require_relative 'erp_integration/version'
|
9
|
+
require_relative 'erp_integration/errors'
|
10
|
+
require_relative 'erp_integration/configuration'
|
11
|
+
|
12
|
+
# Middleware
|
13
|
+
require_relative 'erp_integration/middleware/error_handling'
|
14
|
+
|
15
|
+
# Resources
|
16
|
+
require_relative 'erp_integration/resource'
|
17
|
+
require_relative 'erp_integration/order'
|
18
|
+
|
19
|
+
# HTTP clients
|
20
|
+
require_relative 'erp_integration/clients/fulfil_client'
|
21
|
+
|
22
|
+
# The `ErpIntegration` integrates Mejuri with third-party ERP vendors.
|
23
|
+
module ErpIntegration
|
24
|
+
end
|