erp_integration 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b73b17b72afec2d03e59213c35db31b1260f304907a9686af94a6bc0123b19c2
4
- data.tar.gz: e2f42a3b2c313c6aa91efdcd5663b3c070a04221416799f0b4ee8753fc29df43
3
+ metadata.gz: 5657cdcc644e079869eeb3189a451e97b558cb0c64f93b123280301acd0886b1
4
+ data.tar.gz: 5a279cf9b1c38ebba6939861e6f15bd5d4ad66acc03364068d5c5ffe46dfa3be
5
5
  SHA512:
6
- metadata.gz: e951c654c3293e5e169b212c5b0dc5ecc1b742750155520edb68302b2452e7f5637caa2e36ec75e37363e76626efe667186bd45e58e38cfe0c08c92709cd9f9a
7
- data.tar.gz: ff1d1e69793589517db55c30ad56be8e7a484d77ef5cba846d00805aad46e3123d90343b38854a6b2323eb7607835fe9ab4a6d491d769d6d78a66b2657e1c4c0
6
+ metadata.gz: 2a3203a5b4277d6656f955aa015b01ab9c180cf628510eceeff4f6b6cab878a349a403c495342a0d050cd5c23c25fb0bda5bbe945b658e64fe53261c7fd78e38
7
+ data.tar.gz: f9014829733dd877118093fc7375517ba73982a87c1ad84f1497c183e514900372297c127551de9591801e1404f65ef21439bf0ef8aa3778fb3c27fb650eca6c
data/.reek.yml CHANGED
@@ -49,7 +49,7 @@ detectors:
49
49
  # Acceptable code smell as we don't know the full extend of our configuration
50
50
  # interface for the gem yet.
51
51
  - ErpIntegration::Configuration
52
- - ErpIntegration::Resource#initialize
52
+ - ErpIntegration::Resource
53
53
  MissingSafeMethod:
54
54
  enabled: true
55
55
  exclude: []
data/README.md CHANGED
@@ -32,6 +32,29 @@ ErpIntegration.configure do |config|
32
32
  end
33
33
  ```
34
34
 
35
+ ### Querying third-party vendors
36
+
37
+ After configuring the gem, one can easily query all the available ERP resources from the connected third-parties.
38
+
39
+ ```ruby
40
+ $ ErpIntegration::Order.where(reference: 'MT1000SKX')
41
+ => #<ErpIntegration::Fulfil::Collection @items=[<ErpIntegration::Order @id=100 />] />
42
+ ```
43
+
44
+ By default, only the `id` will be added to the found ERP resources. However, one can use the `select` method to include more fields.
45
+
46
+ ```ruby
47
+ $ ErpIntegration::Order.select(:id, :reference).where(reference: 'MT1000SKX')
48
+ => #<ErpIntegration::Fulfil::Collection @items=[<ErpIntegration::Order @id=100 @reference=MT1000SKX />] />
49
+ ```
50
+
51
+ There are also other type of `where` queries available:
52
+ - `where_like` for case sensitive queries.
53
+ - `where_ilike` for case insensitive queries.
54
+ - `where_not` for non-equality queries.
55
+ - `where_in` for inclusion queries.
56
+ - `where_not_in` for exclusion queries.
57
+
35
58
  ## Development
36
59
 
37
60
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/bin/prerelease CHANGED
@@ -9,7 +9,7 @@ bundle
9
9
  rake
10
10
 
11
11
  # Update the gem version.
12
- printf "module ErpIntegration\n VERSION = \"$VERSION\".freeze\nend\n" > ./lib/erp_integration/version.rb
12
+ printf "# frozen_string_literal: true\n\nmodule ErpIntegration\n VERSION = '$VERSION'\nend\n" > ./lib/erp_integration/version.rb
13
13
  git add lib/erp_integration/version.rb
14
14
  git checkout -b "release_v.$VERSION"
15
15
  git commit -m "[RELEASE] Bump version for v$VERSION"
@@ -17,4 +17,4 @@ git push origin "release_v.$VERSION"
17
17
 
18
18
  # Tag the new gem version.
19
19
  git tag v$VERSION
20
- git push --tags
20
+ git push --tags
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.add_dependency 'activesupport', '>= 0.12'
33
33
 
34
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'
35
+ spec.add_dependency 'faraday', '>= 0.17.3', '< 1.9.0'
36
36
  spec.add_dependency 'faraday_middleware', '~> 0.14.0'
37
37
 
38
38
  # Development dependencies
@@ -27,6 +27,16 @@ module ErpIntegration
27
27
  # @return [Symbol] The configured adapter for the orders
28
28
  attr_writer :order_adapter
29
29
 
30
+ # Allows configuring an adapter for the `PurchaseOrder` resource. When none is
31
+ # configured, it will default to Fulfil.
32
+ # @return [Symbol] The configured adapter for the purchase orders.
33
+ attr_writer :purchase_order_adapter
34
+
35
+ # Allows configuring an adapter for the `PurchaseOrderLine` resource. When
36
+ # none is configured, it will default to Fulfil.
37
+ # @return [Symbol] The configured adapter for the purchase order lines.
38
+ attr_writer :purchase_order_line_adapter
39
+
30
40
  def initialize(**options)
31
41
  options.each_pair do |key, value|
32
42
  public_send("#{key}=", value) if respond_to?("#{key}=")
@@ -36,6 +46,14 @@ module ErpIntegration
36
46
  def order_adapter
37
47
  @order_adapter || :fulfil
38
48
  end
49
+
50
+ def purchase_order_adapter
51
+ @purchase_order_adapter || :fulfil
52
+ end
53
+
54
+ def purchase_order_line_adapter
55
+ @purchase_order_line_adapter || :fulfil
56
+ end
39
57
  end
40
58
 
41
59
  # Returns ERP Integration's configuration.
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'collection'
4
+ require_relative 'query_methods'
5
+
6
+ module ErpIntegration
7
+ module Fulfil
8
+ class ApiResource
9
+ include QueryMethods
10
+
11
+ cattr_accessor :resource_klass, instance_accessor: false
12
+
13
+ delegate :client, :model_name, :resource_klass, to: 'self.class'
14
+ delegate_missing_to :all
15
+
16
+ def initialize(resource_klass)
17
+ self.class.resource_klass = resource_klass
18
+ end
19
+
20
+ # The `where` and `includes` methods lazyly build a search/read query
21
+ # for Fulfil. By calling `all`, the prepared search/read query will actually
22
+ # be executed and the results will be fetched.
23
+ # @return [ErpIntegration::Fulfil::Collection] An enumerable collection object with all API results.
24
+ def all
25
+ return @results if defined?(@results)
26
+
27
+ @results = Collection.new(
28
+ client.put(
29
+ "model/#{model_name}/search_read",
30
+ Query.new(selected_fields, where_clauses)
31
+ ),
32
+ resource_klass: resource_klass
33
+ )
34
+ end
35
+
36
+ # The `client` exposes the `ErpIntegration::Fulfil::Client` to the class.
37
+ # @return [ErpIntegration::Fulfil::Client] The HTTP client for Fulfil.
38
+ def self.client
39
+ Client.new(
40
+ api_key: config.fulfil_api_key,
41
+ merchant_id: config.fulfil_merchant_id
42
+ )
43
+ end
44
+
45
+ # The `config` exposes the gem's configuration to the `ApiResource`.
46
+ # @return [ErpIntegration::Configuration] The gem's configuration object.
47
+ def self.config
48
+ ErpIntegration.config
49
+ end
50
+
51
+ # Fulfil doesn't use logical naming conventions. However, we do need
52
+ # the name of a model to build the resource URI.
53
+ #
54
+ # By manually setting the model name, we allow the `Fulfil::Connection`
55
+ # module to connect to the correct API endpoint.
56
+ #
57
+ # @param name [String] The logical path name in Fulfil.
58
+ # @return [String] The model name
59
+ def self.model_name=(name)
60
+ instance_variable_set(:@model_name, name)
61
+ end
62
+
63
+ def self.model_name
64
+ instance_variable_get(:@model_name)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faraday_middleware'
4
-
5
3
  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
4
+ module Fulfil
5
+ class Client
10
6
  attr_reader :api_key, :merchant_id
11
7
  attr_writer :connection, :faraday_adapter
12
8
 
@@ -49,6 +45,10 @@ module ErpIntegration
49
45
 
50
46
  %i[delete get patch put post].each do |action_name|
51
47
  define_method(action_name) do |path, options = {}|
48
+ if api_key.nil? || merchant_id.nil?
49
+ raise ErpIntegration::Error, 'The Fulfil API key and/or Merchant ID are missing.'
50
+ end
51
+
52
52
  connection.public_send(action_name, "api/#{version}/#{path}", options).body
53
53
  end
54
54
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ module Fulfil
5
+ # The `Collection` class provides an enumerable interface for the `ApiResource`
6
+ # to consume. It turns all the given items into the passed resource class.
7
+ #
8
+ # @example
9
+ # $ Collection.new([...], resource_klass: ErpIntegration::Order)
10
+ # # => <Collection @items=[<ErpIntegration::Order ... />, <ErpIntegration::Order ... />]
11
+ class Collection
12
+ include Enumerable
13
+ attr_reader :items
14
+
15
+ def initialize(items, resource_klass:)
16
+ @items = items.map { |item| resource_klass.new(item) }
17
+ end
18
+
19
+ # The `each` method turns the `Collection` instance into an enumerable object.
20
+ # For more information, see https://ruby-doc.org/core-3.0.2/Enumerable.html
21
+ def each(&block)
22
+ @items.each(&block)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ module Fulfil
5
+ # The `Query` class transforms where clauses and included fields into a
6
+ # queryable object that Fulfil understands.
7
+ #
8
+ # @example
9
+ # $ Query.new([:id, 'product.id'], [['id', '=', '100']]).to_json
10
+ # # => {"fields":["id","product.id"],"filters":[["id","=","100"]]}
11
+ #
12
+ # The instance of the `Query` class is meant to be passed to `Faraday` or
13
+ # a third-party vendor client (e.g. `Fulfil::Client`) that uses `Faraday`.
14
+ #
15
+ # `Faraday` will call the `.to_json` method before sending the data to Fulfil.
16
+ # So, there is no need to explicitly call `.to_json` on the `Query` instance.
17
+ #
18
+ # @example
19
+ # Faraday.post("/api-endpoint", Query.new([:id, 'product.id'], [WhereClause.new(key: :id, value: 100)]))
20
+ class Query
21
+ attr_reader :fields, :filters
22
+ DEFAULT_FIELDS = %w[id].freeze
23
+
24
+ # @param fields [Array<String|Symbol>] A list of fields for Fulfil.
25
+ # @param filters [Array<WhereClause>] A list of where clauses for Fulfil.
26
+ def initialize(fields, filters)
27
+ @fields = (fields || DEFAULT_FIELDS).map(&:to_s).uniq
28
+ @filters = (filters || []).map(&:to_filter).uniq
29
+ end
30
+
31
+ def to_json(*_object)
32
+ {
33
+ fields: @fields,
34
+ filters: @filters
35
+ }.to_json
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'query'
4
+ require_relative 'where_clause'
5
+
6
+ module ErpIntegration
7
+ module Fulfil
8
+ module QueryMethods
9
+ attr_accessor :selected_fields, :where_clauses
10
+
11
+ # Fulfil only returns an ID by default when one would query their API. However,
12
+ # the ID is seldom the only value one will need. The `select` method allows
13
+ # selecting multiple fields at once.
14
+ #
15
+ # @example
16
+ # $ ErpIntegration::Order.select(:id, :name, 'product.name')
17
+ # # => <ErpIntegration::Fulfil::Resources::Order @selected_fields=[:id, :name, "product.name"] />
18
+ #
19
+ # When one calls the `all` method, it will fetch all the resources from Fulfil
20
+ # and it will return all the given fields (if available).
21
+ #
22
+ # @example
23
+ # $ ErpIntegration::Order.select(:id, :name, 'product.name').all
24
+ # # => <ErpIntegration::Fulfil::Collection @items=[...] />
25
+ #
26
+ # Both a list of Strings, Symbols or a combination of these can be passed
27
+ # to the `select` method.
28
+ def select(*fields)
29
+ clone.select!(*fields)
30
+ end
31
+
32
+ def select!(*fields)
33
+ self.selected_fields = (selected_fields || []).concat(fields)
34
+ self
35
+ end
36
+
37
+ # Fulfil has a very powerful query syntax. However, that query syntax is rather
38
+ # complicated and it's really specific to Fulfil.
39
+ #
40
+ # The `where` method introduces a well-known interface (e.g. ActiveRecord::Relation)
41
+ # to query resources in Fulfil.
42
+ #
43
+ # @example
44
+ # $ ErpIntegration::Order.where(id: 100).all
45
+ # # => <ErpIntegration::Fulfil::Collection @items=[<ErpIntegration::Order @id=100 />] />
46
+ #
47
+ # If one adds the `comparison_operator` key to the arguments, it will use
48
+ # that comparison operator to build the query to Fulfil.
49
+ #
50
+ # All the other `where_` methods use this technique to provide logic for all
51
+ # the different comparison operators.
52
+ def where(*args)
53
+ clone.where!(*args)
54
+ end
55
+
56
+ def where!(args)
57
+ comparison_operator = args.delete(:comparison_operator)
58
+
59
+ args.each_pair do |key, value|
60
+ self.where_clauses =
61
+ (where_clauses || []) << WhereClause.new(
62
+ key: key, value: value, comparison_operator: comparison_operator
63
+ )
64
+ end
65
+
66
+ self
67
+ end
68
+
69
+ def where_ilike(args)
70
+ where(args.merge(comparison_operator: 'ilike'))
71
+ end
72
+
73
+ def where_in(args)
74
+ where(args.merge(comparison_operator: 'in'))
75
+ end
76
+
77
+ def where_like(args)
78
+ where(args.merge(comparison_operator: 'like'))
79
+ end
80
+
81
+ def where_not(args)
82
+ where(args.merge(comparison_operator: '!='))
83
+ end
84
+
85
+ def where_not_in(args)
86
+ where(args.merge(comparison_operator: 'not in'))
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../api_resource'
4
+
5
+ module ErpIntegration
6
+ module Fulfil
7
+ module Resources
8
+ class Order < ApiResource
9
+ self.model_name = 'sale.sale'
10
+
11
+ # Allows cancelling the entire sales order in Fulfil.
12
+ # @param id [Integer|String] The ID of the to be cancelled order.
13
+ # @return [boolean] Whether the sales order was cancelled successfully or not.
14
+ def cancel(id)
15
+ client.put("model/sale.sale/#{id}/cancel")
16
+ true
17
+
18
+ # Fulfil will return an 400 (a.k.a. "Bad Request") status code when a sales order couldn't
19
+ # be cancelled. If a sales order isn't cancellable by design, no exception should be raised.
20
+ #
21
+ # See the Fulfil's documentation for more information:
22
+ # https://developers.fulfil.io/rest_api/model/sale.sale/#cancel-a-sales-order
23
+ rescue ErpIntegration::HttpError::BadRequest
24
+ false
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../api_resource'
4
+
5
+ module ErpIntegration
6
+ module Fulfil
7
+ module Resources
8
+ class PurchaseOrder < ApiResource
9
+ self.model_name = 'purchase.purchase'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../api_resource'
4
+
5
+ module ErpIntegration
6
+ module Fulfil
7
+ module Resources
8
+ class PurchaseOrderLine < ApiResource
9
+ self.model_name = 'purchase.line'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ module Fulfil
5
+ # The `WhereClause` model encapsulates the logic for the filter options for Fulfil.
6
+ # It transforms the attributes passed to any of the where lookup methods into
7
+ # a format that Fulfil can actually understand.
8
+ #
9
+ # For more information, see their documentation.
10
+ # https://developers.fulfil.io/guides/searching-filtering/#field-filter-expressions
11
+ #
12
+ # @example
13
+ # WhereClause.new({ key: :id, value: 100, comparison_operator: 'ilike' })
14
+ # # => <WhereClause @key="id" value="100" comparison_operator="ilike" />
15
+ #
16
+ # @example
17
+ # $ WhereClause.new({ key: :id, value: 100 })
18
+ # # => <WhereClause @key="id" value="100" comparison_operator="=" />
19
+ class WhereClause
20
+ COMPARISON_OPERATORS = [
21
+ '=', '!=', '<', '<=', '=>', '>', 'like', 'ilike', 'in', 'not in'
22
+ ].freeze
23
+
24
+ DEFAULT_COMPARISON_OPERATOR = '='
25
+
26
+ def initialize(key:, value:, comparison_operator: DEFAULT_COMPARISON_OPERATOR)
27
+ @comparison_operator = verify_comparison_operator(comparison_operator)
28
+ @key = key.to_s
29
+ @value = value.to_s
30
+ end
31
+
32
+ # Transforms the `WhereClause` into a filter object for Fulfil.
33
+ # @return [Array<String>] The formatted filter for Fulfil.
34
+ def to_filter
35
+ [@key, @comparison_operator, @value]
36
+ end
37
+
38
+ # The `===` allows comparing different WhereClause objects. Under the hood,
39
+ # this is used by `.uniq` to determine whether objects are equal to each other.
40
+ #
41
+ # Note: `.uniq` also depends on `.hash` on the `WhereClause` instance. See
42
+ # the `.hash` method to see how the two objects are compared.
43
+ #
44
+ # @example
45
+ # $ WhereClause.new(id: 100) == WhereClause.new(id: 100)
46
+ # # => true
47
+ #
48
+ # $ WhereClause.new(id: 100) == WhereClause.new(id: 101)
49
+ # # => false
50
+ #
51
+ # $ WhereClause.new(id: 100) == WhereClause.new(name: "PT100")
52
+ # # => false
53
+ #
54
+ # @param other [WhereClause] Another `WhereClause` instance.
55
+ # @return [Boolean] Whether or not the `WhereClause`s are equal.
56
+ def ==(other)
57
+ to_filter == other.to_filter
58
+ end
59
+ alias eql? ==
60
+
61
+ # The `.hash` allows comparing two `WhereClause` instances for uniqueness.
62
+ # See https://rubyapi.org/2.3/o/object#method-i-hash
63
+ # @return [Fixnum] A Fixnum that identifies the `WhereClause`.
64
+ def hash
65
+ to_filter.hash
66
+ end
67
+
68
+ private
69
+
70
+ def verify_comparison_operator(comparison_operator)
71
+ return comparison_operator if COMPARISON_OPERATORS.include?(comparison_operator)
72
+
73
+ DEFAULT_COMPARISON_OPERATOR
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,16 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ErpIntegration
4
- # The `Order` class exposes available ERP Order operations
5
- # assigning them to the proper adapter
4
+ # The `ErpIntegration::Order` exposes an uniformed API for interaction with
5
+ # third-party ERP vendors.
6
6
  class Order < Resource
7
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
8
  end
16
9
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ # The `ErpIntegration::PurchaseOrder` exposes an uniformed API for interaction with
5
+ # third-party ERP vendors.
6
+ class PurchaseOrder < Resource
7
+ attr_accessor :id, :lines, :number, :party, :purchase_date, :warehouse,
8
+ :approval_status, :attachments, :company, :create_date, :create_uid,
9
+ :currency, :customer, :delivery_address, :description, :invoice_address,
10
+ :invoice_method, :invoice_state, :invoices, :metadata, :payment_term,
11
+ :purchase_person, :reference, :shipment_method, :shipments, :state,
12
+ :tax_amount, :total_amount, :total_quantity, :untaxed_amount, :write_date,
13
+ :write_uid
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ # The `ErpIntegration::PurchaseOrderLine` exposes an uniformed API for
5
+ # interaction with third-party ERP vendors.
6
+ class PurchaseOrderLine < Resource
7
+ attr_accessor :id, :amount, :delivery_date, :product, :quantity, :unit_price,
8
+ :attachments, :create_date, :create_uid, :description, :invoice_lines,
9
+ :metadata, :note, :purchase, :quantity_canceled, :quantity_invoiced,
10
+ :quantity_received, :sequence, :taxes, :type, :unit, :write_date, :write_uid
11
+ end
12
+ end
@@ -46,8 +46,8 @@ module ErpIntegration
46
46
  def adapter
47
47
  return @adapter if defined?(@adapter)
48
48
 
49
- require_relative File.join(File.dirname(__FILE__), '..', "#{adapter_path}.rb")
50
- @adapter = adapter_klass.constantize
49
+ require_relative File.join(File.dirname(__FILE__), "#{adapter_path}.rb")
50
+ @adapter = adapter_klass.constantize.new(self)
51
51
  end
52
52
 
53
53
  # Dynamically exposes the adapter class to the resource.
@@ -56,16 +56,37 @@ module ErpIntegration
56
56
  "ErpIntegration::#{adapter_path.classify}"
57
57
  end
58
58
 
59
+ # Provides a relative path to the adapter for the resource.
60
+ # @return [String] the path to the adapter
61
+ def adapter_path
62
+ "#{adapter_type}/resources/#{resource_name}"
63
+ end
64
+
59
65
  # Retrieves the adapter type for the resource from the global configuration.
60
66
  # @return [String] the adapter type for the resource
61
67
  def adapter_type
62
68
  ErpIntegration.config.send("#{resource_name}_adapter").to_s
63
69
  end
64
70
 
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}"
71
+ # Due to `method_missing` we're able to delegate all the work automatically
72
+ # to the adapter. However, if the method doesn't exist on the adapter, the
73
+ # `NoMethodError` is still being raised.
74
+ # @param method_name [Symbol] The name of the missing method.
75
+ # @param args [Array] The list of arguments for the missing method.
76
+ # @param block [Proc] Any block given to the missing method.
77
+ def method_missing(method_name, *args, &block)
78
+ if adapter.respond_to?(method_name)
79
+ adapter.send(method_name, *args, &block)
80
+ else
81
+ super
82
+ end
83
+ end
84
+
85
+ # The `respond_to_missing?` complements the `method_missing` method.
86
+ # @param method_name [Symbol] The name of the missing method.
87
+ # @param include_private [Boolean] Whether private methods should be checked too (defaults to false).
88
+ def respond_to_missing?(method_name, include_private = false)
89
+ adapter.respond_to?(method_name) || super
69
90
  end
70
91
 
71
92
  # Derives the name of the resource from the class name.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ErpIntegration
2
- VERSION = "0.2.0".freeze
4
+ VERSION = '0.3.0'
3
5
  end
@@ -2,7 +2,10 @@
2
2
 
3
3
  require 'active_support'
4
4
  require 'active_support/core_ext/string/inflections'
5
+ require 'active_support/core_ext/module/delegation' # Allows using `delegate`
6
+ require 'active_support/core_ext/module/attribute_accessors' # Allows using `delegate_missing_to`
5
7
  require 'faraday'
8
+ require 'faraday_middleware'
6
9
  require 'json'
7
10
 
8
11
  require_relative 'erp_integration/version'
@@ -12,13 +15,14 @@ require_relative 'erp_integration/configuration'
12
15
  # Middleware
13
16
  require_relative 'erp_integration/middleware/error_handling'
14
17
 
15
- # Resources
16
- require_relative 'erp_integration/resource'
17
- require_relative 'erp_integration/order'
18
-
19
18
  # HTTP clients
20
- require_relative 'erp_integration/clients/fulfil_client'
19
+ require_relative 'erp_integration/fulfil/client'
21
20
 
22
21
  # The `ErpIntegration` integrates Mejuri with third-party ERP vendors.
23
22
  module ErpIntegration
23
+ # Resources
24
+ autoload :Resource, 'erp_integration/resource'
25
+ autoload :Order, 'erp_integration/order'
26
+ autoload :PurchaseOrder, 'erp_integration/purchase_order'
27
+ autoload :PurchaseOrderLine, 'erp_integration/purchase_order_line'
24
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erp_integration
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Vermaas
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-23 00:00:00.000000000 Z
11
+ date: 2021-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -33,7 +33,7 @@ dependencies:
33
33
  version: 0.17.3
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: 1.8.0
36
+ version: 1.9.0
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: 0.17.3
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: 1.8.0
46
+ version: 1.9.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: faraday_middleware
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -218,7 +218,7 @@ dependencies:
218
218
  - - '='
219
219
  - !ruby/object:Gem::Version
220
220
  version: 1.19.2
221
- description:
221
+ description:
222
222
  email:
223
223
  - stefan@knowndecimal.com
224
224
  executables: []
@@ -253,12 +253,21 @@ files:
253
253
  - bin/setup
254
254
  - erp_integration.gemspec
255
255
  - lib/erp_integration.rb
256
- - lib/erp_integration/clients/fulfil_client.rb
257
256
  - lib/erp_integration/configuration.rb
258
257
  - lib/erp_integration/errors.rb
258
+ - lib/erp_integration/fulfil/api_resource.rb
259
+ - lib/erp_integration/fulfil/client.rb
260
+ - lib/erp_integration/fulfil/collection.rb
261
+ - lib/erp_integration/fulfil/query.rb
262
+ - lib/erp_integration/fulfil/query_methods.rb
263
+ - lib/erp_integration/fulfil/resources/order.rb
264
+ - lib/erp_integration/fulfil/resources/purchase_order.rb
265
+ - lib/erp_integration/fulfil/resources/purchase_order_line.rb
266
+ - lib/erp_integration/fulfil/where_clause.rb
259
267
  - lib/erp_integration/middleware/error_handling.rb
260
268
  - lib/erp_integration/order.rb
261
- - lib/erp_integration/orders/fulfil_order.rb
269
+ - lib/erp_integration/purchase_order.rb
270
+ - lib/erp_integration/purchase_order_line.rb
262
271
  - lib/erp_integration/resource.rb
263
272
  - lib/erp_integration/version.rb
264
273
  homepage: https://www.github.com/mejuri-inc/erp-integration
@@ -269,7 +278,7 @@ metadata:
269
278
  homepage_uri: https://www.github.com/mejuri-inc/erp-integration
270
279
  source_code_uri: https://www.github.com/mejuri-inc/erp-integration
271
280
  changelog_uri: https://www.github.com/mejuri-inc/erp-integration/blob/main/CHANGELOG.md
272
- post_install_message:
281
+ post_install_message:
273
282
  rdoc_options: []
274
283
  require_paths:
275
284
  - lib
@@ -285,7 +294,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
285
294
  version: '0'
286
295
  requirements: []
287
296
  rubygems_version: 3.2.22
288
- signing_key:
297
+ signing_key:
289
298
  specification_version: 4
290
299
  summary: Connects Mejuri with third-party ERP vendors
291
300
  test_files: []
@@ -1,36 +0,0 @@
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