erp_integration 0.2.0 → 0.3.3

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: 373cc92b70776b3ab2547879840468b0b5e2c9269aaf4991a1e3c283435609c4
4
+ data.tar.gz: cd372fe6aed0b7f88ddcf0007ad895386a75ca593a0bfff4ce978cad8de84cce
5
5
  SHA512:
6
- metadata.gz: e951c654c3293e5e169b212c5b0dc5ecc1b742750155520edb68302b2452e7f5637caa2e36ec75e37363e76626efe667186bd45e58e38cfe0c08c92709cd9f9a
7
- data.tar.gz: ff1d1e69793589517db55c30ad56be8e7a484d77ef5cba846d00805aad46e3123d90343b38854a6b2323eb7607835fe9ab4a6d491d769d6d78a66b2657e1c4c0
6
+ metadata.gz: e711394078bb8e517436cd9b10f4231248cbef3bf6bb744834270e305e6329e0672611761b091d98a53fcd5d00f7f781ef2309b76c4db64cbe2bbd8b2ad3a519
7
+ data.tar.gz: 5490ad8f93dfabc1e77d8ef326ab34ee29d62b416f061cf83780f49fcb7d42882aab46e29e16e36f730ba7555a1d1482b55a2f45255fb71b252b5758a9cdf58b
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 origin v$VERSION
@@ -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,26 @@ 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 `OrderLine` resource. When none is
31
+ # configured, it will default to Fulfil.
32
+ # @return [Symbol] The configured adapter for the order lines.
33
+ attr_writer :order_line_adapter
34
+
35
+ # Allows configuring an adapter for the `PurchaseOrder` resource. When none is
36
+ # configured, it will default to Fulfil.
37
+ # @return [Symbol] The configured adapter for the purchase orders.
38
+ attr_writer :purchase_order_adapter
39
+
40
+ # Allows configuring an adapter for the `PurchaseOrderLine` resource. When
41
+ # none is configured, it will default to Fulfil.
42
+ # @return [Symbol] The configured adapter for the purchase order lines.
43
+ attr_writer :purchase_order_line_adapter
44
+
45
+ # Allows configuring an adapter for the `PurchaseRequest` resource. When
46
+ # none is configured, it will default to Fulfil.
47
+ # @return [Symbol] The configured adapter for the purchase request.
48
+ attr_writer :purchase_request_adapter
49
+
30
50
  def initialize(**options)
31
51
  options.each_pair do |key, value|
32
52
  public_send("#{key}=", value) if respond_to?("#{key}=")
@@ -36,6 +56,22 @@ module ErpIntegration
36
56
  def order_adapter
37
57
  @order_adapter || :fulfil
38
58
  end
59
+
60
+ def order_line_adapter
61
+ @order_line_adapter || :fulfil
62
+ end
63
+
64
+ def purchase_order_adapter
65
+ @purchase_order_adapter || :fulfil
66
+ end
67
+
68
+ def purchase_order_line_adapter
69
+ @purchase_order_line_adapter || :fulfil
70
+ end
71
+
72
+ def purchase_request_adapter
73
+ @purchase_request_adapter || :fulfil
74
+ end
39
75
  end
40
76
 
41
77
  # Returns ERP Integration's configuration.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'query_methods'
4
+
5
+ module ErpIntegration
6
+ module Fulfil
7
+ class ApiResource
8
+ include Enumerable
9
+ include QueryMethods
10
+
11
+ cattr_accessor :resource_klass, instance_accessor: false
12
+ delegate :client, :model_name, :resource_klass, to: 'self.class'
13
+
14
+ def initialize(resource_klass)
15
+ self.class.resource_klass = resource_klass
16
+ end
17
+
18
+ # The `where` and `includes` methods lazyly build a search/read query
19
+ # for Fulfil. By calling `all`, the prepared search/read query will actually
20
+ # be executed and the results will be fetched.
21
+ # @return [Array] An enumerable collection object with all API results.
22
+ def all
23
+ return @results if defined?(@results)
24
+
25
+ @results =
26
+ client.put(
27
+ "model/#{model_name}/search_read",
28
+ Query.new(selected_fields, where_clauses)
29
+ ).map { |item| resource_klass.new(item) }
30
+ end
31
+
32
+ # The `each` method turns the `ApiResource` instance into an enumerable object.
33
+ # For more information, see https://ruby-doc.org/core-3.0.2/Enumerable.html
34
+ def each(&block)
35
+ all.each(&block)
36
+ end
37
+
38
+ # The `client` exposes the `ErpIntegration::Fulfil::Client` to the class.
39
+ # @return [ErpIntegration::Fulfil::Client] The HTTP client for Fulfil.
40
+ def self.client
41
+ Client.new(
42
+ api_key: config.fulfil_api_key,
43
+ merchant_id: config.fulfil_merchant_id
44
+ )
45
+ end
46
+
47
+ # The `config` exposes the gem's configuration to the `ApiResource`.
48
+ # @return [ErpIntegration::Configuration] The gem's configuration object.
49
+ def self.config
50
+ ErpIntegration.config
51
+ end
52
+
53
+ # Fulfil doesn't use logical naming conventions. However, we do need
54
+ # the name of a model to build the resource URI.
55
+ #
56
+ # By manually setting the model name, we allow the `Fulfil::Connection`
57
+ # module to connect to the correct API endpoint.
58
+ #
59
+ # @param name [String] The logical path name in Fulfil.
60
+ # @return [String] The model name
61
+ def self.model_name=(name)
62
+ instance_variable_set(:@model_name, name)
63
+ end
64
+
65
+ def self.model_name
66
+ instance_variable_get(:@model_name)
67
+ end
68
+ end
69
+ end
70
+ 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,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,35 @@
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
+ begin
16
+ client.put("model/sale.sale/#{id}/cancel")
17
+ rescue Faraday::Error::ParsingError
18
+ # Workaround: Fulfil api does not return a json when status code is 200 (a.k.a. "Ok")
19
+ # and faraday is having an error when trying to parse it. Let's skip the parse error
20
+ # and move on.
21
+ end
22
+ true
23
+
24
+ # Fulfil will return an 400 (a.k.a. "Bad Request") status code when a sales order couldn't
25
+ # be cancelled. If a sales order isn't cancellable by design, no exception should be raised.
26
+ #
27
+ # See the Fulfil's documentation for more information:
28
+ # https://developers.fulfil.io/rest_api/model/sale.sale/#cancel-a-sales-order
29
+ rescue ErpIntegration::HttpError::BadRequest
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
35
+ 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 OrderLine < ApiResource
9
+ self.model_name = 'sale.line'
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 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../api_resource'
4
+
5
+ module ErpIntegration
6
+ module Fulfil
7
+ module Resources
8
+ class PurchaseRequest < ApiResource
9
+ self.model_name = 'purchase.request'
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,23 @@
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
- attr_accessor :id, :number
7
+ attr_accessor :id, :channel, :number, :party, :sale_date, :shipment_address,
8
+ :amount_invoiced, :attachments, :carrier, :carrier_service,
9
+ :channel_identifier, :comment, :company, :confirmation_time,
10
+ :create_date, :currency, :description, :gateway_transaction,
11
+ :invoice_address, :invoice_method, :invoice_state, :invoices,
12
+ :lines, :metadata, :payment_term, :payment_total, :price_list,
13
+ :reference, :sales_person, :shipment_amount, :shipment_method,
14
+ :shipment_state, :shipments, :shipping_start_date, :state,
15
+ :tax_amount, :total_amount, :total_quantity, :total_shipment_cost,
16
+ :untaxed_amount, :warehouse, :weight, :weight_uom, :write_date,
17
+ :write_uid
8
18
 
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)
19
+ def cancel
20
+ self.class.adapter.cancel(id)
14
21
  end
15
22
  end
16
23
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ # The `ErpIntegration::OrderLine` exposes an uniformed API for interaction with
5
+ # third-party ERP vendors.
6
+ class OrderLine < Resource
7
+ attr_accessor :id, :product, :quantity, :unit, :unit_price, :amount, :attachments,
8
+ :channel_identifier, :create_date, :create_uid, :delivery_address,
9
+ :delivery_date, :delivery_mode, :description, :discount, :gift_message,
10
+ :gross_profit_cpny_cc, :is_gift_card, :list_price, :listing_sku,
11
+ :metadata, :note, :options, :purhcase_request, :quantity_canceled,
12
+ :quantity_reserved, :quantity_shipped, :return_reason, :sale,
13
+ :sequence, :shipping_date, :taxes, :type, :warehouse, :write_date,
14
+ :write_uid
15
+ end
16
+ 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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ErpIntegration
4
+ # The `ErpIntegration::PurchaseRequest` exposes an uniformed API for interaction with
5
+ # third-party ERP vendors.
6
+ class PurchaseRequest < Resource
7
+ attr_accessor :id, :party, :product, :quantity, :attachments, :company,
8
+ :create_date, :create_uid, :customer, :delivery_address,
9
+ :metadata, :purchase, :purchase_date, :purchase_line, :state,
10
+ :uom, :warehouse, :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.3'
3
5
  end
@@ -2,7 +2,9 @@
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`
5
6
  require 'faraday'
7
+ require 'faraday_middleware'
6
8
  require 'json'
7
9
 
8
10
  require_relative 'erp_integration/version'
@@ -12,13 +14,16 @@ require_relative 'erp_integration/configuration'
12
14
  # Middleware
13
15
  require_relative 'erp_integration/middleware/error_handling'
14
16
 
15
- # Resources
16
- require_relative 'erp_integration/resource'
17
- require_relative 'erp_integration/order'
18
-
19
17
  # HTTP clients
20
- require_relative 'erp_integration/clients/fulfil_client'
18
+ require_relative 'erp_integration/fulfil/client'
21
19
 
22
20
  # The `ErpIntegration` integrates Mejuri with third-party ERP vendors.
23
21
  module ErpIntegration
22
+ # Resources
23
+ autoload :Resource, 'erp_integration/resource'
24
+ autoload :Order, 'erp_integration/order'
25
+ autoload :OrderLine, 'erp_integration/order_line'
26
+ autoload :PurchaseOrder, 'erp_integration/purchase_order'
27
+ autoload :PurchaseOrderLine, 'erp_integration/purchase_order_line'
28
+ autoload :PurchaseRequest, 'erp_integration/purchase_request'
24
29
  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.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Vermaas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-23 00:00:00.000000000 Z
11
+ date: 2021-10-14 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
@@ -253,12 +253,24 @@ 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/query.rb
261
+ - lib/erp_integration/fulfil/query_methods.rb
262
+ - lib/erp_integration/fulfil/resources/order.rb
263
+ - lib/erp_integration/fulfil/resources/order_line.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/resources/purchase_request.rb
267
+ - lib/erp_integration/fulfil/where_clause.rb
259
268
  - lib/erp_integration/middleware/error_handling.rb
260
269
  - lib/erp_integration/order.rb
261
- - lib/erp_integration/orders/fulfil_order.rb
270
+ - lib/erp_integration/order_line.rb
271
+ - lib/erp_integration/purchase_order.rb
272
+ - lib/erp_integration/purchase_order_line.rb
273
+ - lib/erp_integration/purchase_request.rb
262
274
  - lib/erp_integration/resource.rb
263
275
  - lib/erp_integration/version.rb
264
276
  homepage: https://www.github.com/mejuri-inc/erp-integration
@@ -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