erp_integration 0.2.0 → 0.3.3

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 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