fulfil_api 0.0.2 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c65226841f897596ccf870013c0854ddeca4b83590ee58cd7274870c037d9883
4
- data.tar.gz: fa73ad92e2d664493d0bb96dc3a537b51f3a61d6fb4ad4c567966bdd0873aad5
3
+ metadata.gz: 2a26b32a0feab8bd7e697b27db519ac3945fefcd00159aa714742c40ac5abffe
4
+ data.tar.gz: e8e5c494d57360d1cacbbea096c6c4cac3d70d504d04f4478a5f5508db658a63
5
5
  SHA512:
6
- metadata.gz: e3d865c496528323eb773ced6f7fff4249ef170c5d584d577933aae3173f24207e01a27f8b27b7183fb6896484c3d25a9a7f7a68fccf4846f954c09b0700bd28
7
- data.tar.gz: cfc11e259b7d9723415841d59c73f44f2f365e503835b2ee4cb2b826532f98a2dcce4e9e6570b323f25d80e72e75dddaf1b51da7ac429168913051f25f11463a
6
+ metadata.gz: 1d4b7f5ca1a4bc4e8449cc3b235fd9f3b467e7977d76feaa52fa11dac18a6a34ba1ab6b4a8280183e798e45f4ddfda00e0e2d27534ca467110a3cdec203adc49
7
+ data.tar.gz: 63ce6d7793de383098af74f7ca3c81ef8730351d7483390f8256f2dcd5cecc5cb3385ff46e9046fe7ed51f62e66f183b3b4abbf7b11f92a8322abe1ef44eeec9
data/README.md CHANGED
@@ -68,28 +68,28 @@ The gem uses an `ActiveRecord` like query interface to query the Fulfil API.
68
68
 
69
69
  ```ruby
70
70
  # Find one specific resource
71
- sales_order = FulfilApi::Resource.set(name: "sale.sale").find_by(["id", "=", 100])
71
+ sales_order = FulfilApi::Resource.set(model_name: "sale.sale").find_by(["id", "=", 100])
72
72
  p sales_order["id"] # => 100
73
73
 
74
74
  # Find a list of resources
75
- sales_orders = FulfilApi::Resource.set(name: "sale.sale").where(["channel", "=", 4])
75
+ sales_orders = FulfilApi::Resource.set(model_name: "sale.sale").where(["channel", "=", 4])
76
76
  p sales_orders.size # => 500 (standard number of resources returned by Fulfil)
77
77
  p sales_orders.first["id"] # => 10 (an example of an ID returned by Fulfil)
78
78
 
79
79
  # Find a limited list of resources
80
- sales_orders = FulfilApi::Resource.set(name: "sale.sale").where(["channel", "=", 4]).limit(50)
80
+ sales_orders = FulfilApi::Resource.set(model_name: "sale.sale").where(["channel", "=", 4]).limit(50)
81
81
  p sales_orders.size # => 50
82
82
 
83
83
  # Include more resource details than the ID only
84
- sales_orders = FulfilApi::Resource.set(name: "sale.sale").select("reference").where(["channel", "=", 4])
84
+ sales_orders = FulfilApi::Resource.set(model_name: "sale.sale").select("reference").where(["channel", "=", 4])
85
85
  p sales_orders.first["reference"] # => SO1234
86
86
 
87
87
  # Fetch nested data from a relation
88
- line_items = FulfilApi::Resource.set(name: "sale.line").select("sale.reference")
88
+ line_items = FulfilApi::Resource.set(model_name: "sale.line").select("sale.reference")
89
89
  p line_items.first["sale"]["reference"] # => SO1234
90
90
 
91
91
  # Query nested data from a relation
92
- line_items = FulfilApi::Resource.set(name: "sale.line").where(["sale.reference", "=", "SO1234"])
92
+ line_items = FulfilApi::Resource.set(model_name: "sale.line").where(["sale.reference", "=", "SO1234"])
93
93
  p line_items.first["id"] # => 10
94
94
  ```
95
95
 
@@ -100,14 +100,14 @@ p line_items.first["id"] # => 10
100
100
  Any data returned through the `FulfilApi` gem returns a list or a single `FulfilApi::Resource`. The data of the API resource is accessible through a `Hash`-like method.
101
101
 
102
102
  ```ruby
103
- sales_order = FulfilApi::Resource.set(name: "sale.sale").find_by(["id", "=", 100])
103
+ sales_order = FulfilApi::Resource.set(model_name: "sale.sale").find_by(["id", "=", 100])
104
104
  p sales_order["id"] # => 100
105
105
  ```
106
106
 
107
107
  When you're requesting relational data for an API resource, you can access it in a similar manner.
108
108
 
109
109
  ```ruby
110
- sales_order = FulfilApi::Resource.set(name: "sale.sale").select("channel.name").find_by(["id", "=", 100])
110
+ sales_order = FulfilApi::Resource.set(model_name: "sale.sale").select("channel.name").find_by(["id", "=", 100])
111
111
  p sales_order["channel"]["name"] # => Shopify
112
112
  ```
113
113
 
@@ -115,14 +115,14 @@ p sales_order["channel"]["name"] # => Shopify
115
115
 
116
116
  ```ruby
117
117
  # You can't do this
118
- FulfilApi::Resource.set(name: "sale.sale").select("lines.reference").find_by(["id", "=", 100])
118
+ FulfilApi::Resource.set(model_name: "sale.sale").select("lines.reference").find_by(["id", "=", 100])
119
119
 
120
120
  # You can do this (BUT it's not recommended)
121
- sales_order = FulfilApi::Resource.set(name: "sale.sale").select("lines").find_by(["id", "=", 100])
122
- line_items = FulfilApi::Resource.set(name: "sale.line").where(["id", "in", sales_order["lines"]])
121
+ sales_order = FulfilApi::Resource.set(model_name: "sale.sale").select("lines").find_by(["id", "=", 100])
122
+ line_items = FulfilApi::Resource.set(model_name: "sale.line").where(["id", "in", sales_order["lines"]])
123
123
 
124
124
  # You can do this (recommended)
125
- line_items = FulfilApi::Resource.set(name: "sale.line").find_by(["sale.id", "=", 100])
125
+ line_items = FulfilApi::Resource.set(model_name: "sale.line").find_by(["sale.id", "=", 100])
126
126
  ```
127
127
 
128
128
  ## Development
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FulfilApi
4
+ # The {FulfilApi::CustomerShipment} represents a single StockShipmentOut resource returned
5
+ # by the API endpoints of Fulfil.
6
+ class CustomerShipment < Resource
7
+ MODEL_NAME = "stock.shipment.out"
8
+
9
+ class << self
10
+ # Sets the fulfillment status of the customer shipment on hold
11
+ #
12
+ # @param id_or_ids [String, Integer, Array[String], Array[Integer]] The ID(s) of the customer shipment(s) to hold.
13
+ # @param note [String] A note to define the reason for holding. (Optional)
14
+ # @param hold_reason [String] An hold reason ID. (Optional)
15
+ # @return [Boolean] Returns true if hold successfully.
16
+ # @raise [FulfilApi::Error] If an error occurs during holding the customer shipment.
17
+ #
18
+ # @example Hold a customer shipment
19
+ # FulfilApi::CustomerShipment.hold(123, note: "Double booking", hold_reason: hold_reason_id)
20
+ #
21
+ # @example Hold multipe customer shipments
22
+ # FulfilApi::CustomerShipment.hold([123, 456], note: "Double booking", hold_reason: hold_reason_id)
23
+ def hold!(id_or_ids, note: nil, hold_reason: nil)
24
+ FulfilApi.client.put("/model/#{MODEL_NAME}/hold",
25
+ body: [[*id_or_ids].flatten, { note: note, hold_reason: hold_reason }.compact_blank])
26
+
27
+ true
28
+ end
29
+
30
+ # Unholds the fulfillment status of the customer shipment
31
+ #
32
+ # @param id_or_ids [String, Integer, Array[String], Array[Integer]]
33
+ # The ID(s) of the customer shipment(s) to unhold.
34
+ # @param note [String] A note to define the reason for unholding.
35
+ # @return [Boolean] Returns true if hold successfully.
36
+ # @raise [FulfilApi::Error] If an error occurs during holding the customer shipment.
37
+ #
38
+ # @example Unhold a customer shipment
39
+ # FulfilApi::CustomerShipment.unhold(123, note: "All clear")
40
+ #
41
+ # @example Unhold a customer shipment
42
+ # FulfilApi::CustomerShipment.unhold([123, 456], note: "All clear")
43
+ def unhold!(id_or_ids, note: nil)
44
+ FulfilApi.client.put("/model/#{MODEL_NAME}/unhold", body: [[*id_or_ids].flatten, { note: note }.compact_blank])
45
+
46
+ true
47
+ end
48
+ end
49
+
50
+ # Sets the current customer shipment on hold, rescuing any errors that occur and handling them based on error type.
51
+ #
52
+ # @param note [String] A note to define the reason for holding. (Optional)
53
+ # @param hold_reason [String] An hold reason ID. (Optional)
54
+ # @return [Boolean] Returns true if hold successfully, otherwise false.
55
+ #
56
+ # @example Holds a customer_shipment
57
+ # customer_shipment.hold(note: "Double booking", hold_reason: hold_reason_id)
58
+ def hold(note: nil, hold_reason: nil)
59
+ self.class.hold!(id, note: note, hold_reason: hold_reason)
60
+ rescue FulfilApi::Error => e
61
+ handle_error(e)
62
+ false
63
+ end
64
+
65
+ # Unholds the current customer shipment, rescuing any errors that occur and handling them based on error type.
66
+ #
67
+ # @param note [String] A note to define the reason for unholding.
68
+ # @return [Boolean] Returns true if unhold successfully, otherwise false.
69
+ #
70
+ # @example Unholds a customer_shipment
71
+ # customer_shipment.unhold(note: "Double booking")
72
+ def unhold(note: nil)
73
+ self.class.unhold!(id, note: note)
74
+ rescue FulfilApi::Error => e
75
+ handle_error(e)
76
+ false
77
+ end
78
+
79
+ private
80
+
81
+ def handle_error(err)
82
+ errors.add(code: err.details[:response_status], type: :system, message: err.details[:response_body])
83
+ end
84
+ end
85
+ end
@@ -25,7 +25,7 @@ module FulfilApi
25
25
 
26
26
  # @param value [Any]
27
27
  def initialize(value)
28
- @type = extended?(value) ? value.fetch("__class__") : nil
28
+ @type = extended?(value) ? value.fetch("__class__").downcase : nil
29
29
  @value = value
30
30
  end
31
31
 
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FulfilApi
4
+ class Resource
5
+ # The Errors class provides a structure to track and manage errors related to a API resource.
6
+ class Errors
7
+ include Enumerable
8
+
9
+ delegate_missing_to :@errors
10
+
11
+ # @param resource_klass [FulfilApi::Resource] The resource class that this Errors instance is associated with.
12
+ def initialize(resource_klass)
13
+ @errors = []
14
+ @resource_klass = resource_klass
15
+ end
16
+
17
+ # Adds a new error to the collection, unless the same error already exists.
18
+ #
19
+ # @param code [String, Symbol] The error code.
20
+ # @param message [String] A description of the error.
21
+ # @param type [String, Symbol] The type of the error (e.g. user, authorization).
22
+ # @return [Array<Hash>] The updated list of errors.
23
+ #
24
+ # @example Adding an error
25
+ # errors.add(code: "invalid_field", message: "Field is required", type: "validation")
26
+ def add(code:, message:, type:)
27
+ @errors << { code: code.to_s, type: type.to_sym, message: message } unless added?(code: code, type: type)
28
+ @errors
29
+ end
30
+
31
+ # Checks if an error with the specified code and type has already been added.
32
+ #
33
+ # @param code [String, Symbol] The error code to check.
34
+ # @param type [String, Symbol] The error type to check.
35
+ # @return [Boolean] True if the error has already been added, false otherwise.
36
+ #
37
+ # @example Checking if an error exists
38
+ # errors.added?(code: "invalid_field", type: "validation")
39
+ def added?(code:, type:)
40
+ @errors.any? do |error|
41
+ error[:code] == code.to_s && error[:type] == type.to_sym
42
+ end
43
+ end
44
+
45
+ # Clears all errors from the collection.
46
+ #
47
+ # @return [Array] The cleared list of errors
48
+ #
49
+ # @example Clearing all errors
50
+ # errors.clear
51
+ def clear
52
+ @errors = []
53
+ @errors
54
+ end
55
+
56
+ # Returns an array of the full error messages (just the message field).
57
+ #
58
+ # @return [Array<String>] The list of error messages.
59
+ #
60
+ # @example Retrieving full error messages
61
+ # errors.full_messages
62
+ def full_messages
63
+ @errors.pluck(:message)
64
+ end
65
+
66
+ # Returns the collection of error messages as an array of hashes.
67
+ #
68
+ # @return [Array<Hash>] The array of error hashes.
69
+ #
70
+ # @example Retrieving all error messages
71
+ # errors.messages
72
+ def messages
73
+ @errors
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FulfilApi
4
+ class Resource
5
+ # The Persistable module provides methods for saving and updating resources
6
+ # in the Fulfil API. It defines both instance and class methods for persisting
7
+ # changes to resources.
8
+ #
9
+ # This module handles common actions like saving and updating a resource,
10
+ # including error handling for different types of API errors.
11
+ module Persistable
12
+ extend ActiveSupport::Concern
13
+
14
+ class_methods do
15
+ # Updates a resource by its ID and model name.
16
+ #
17
+ # @param id [String, Integer] The ID of the resource to update.
18
+ # @param model_name [String] The name of the model to which the resource belongs.
19
+ # @param attributes [Hash] The attributes to update on the resource.
20
+ # @return [FulfilApi::Resource] The updated resource.
21
+ #
22
+ # @example Updating a resource
23
+ # FulfilApi::Resource.update(id: 123, model_name: "sale.sale", reference: "MK123")
24
+ def update(id:, model_name:, **attributes)
25
+ resource = new(id: id, model_name: model_name)
26
+ resource.update(attributes)
27
+ end
28
+
29
+ # Updates a resource by its ID and model name, raising an error if the update fails.
30
+ #
31
+ # @param id [String, Integer] The ID of the resource to update.
32
+ # @param model_name [String] The name of the model to which the resource belongs.
33
+ # @param attributes [Hash] The attributes to update on the resource.
34
+ # @return [FulfilApi::Resource] The updated resource.
35
+ # @raise [FulfilApi::Error] If the update fails.
36
+ #
37
+ # @example Updating a resource with error raising
38
+ # FulfilApi::Resource.update!(id: 123, model_name: "sale.sale", reference: "MK123")
39
+ def update!(id:, model_name:, **attributes)
40
+ resource = new(id: id, model_name: model_name)
41
+ resource.update!(attributes)
42
+ end
43
+ end
44
+
45
+ # Saves the current resource, rescuing any errors that occur and handling them based on error type.
46
+ #
47
+ # @return [FulfilApi::Resource, nil] Returns the resource if saved successfully, otherwise nil.
48
+ # @raise [FulfilApi::Error] If an error occurs during saving.
49
+ #
50
+ # @example Saving a resource
51
+ # resource.save
52
+ def save
53
+ save!
54
+ rescue FulfilApi::Error => e
55
+ case (error = JSON.parse(e.details[:response_body]).deep_symbolize_keys!)
56
+ in { type: "UserError" }
57
+ errors.add(code: error[:code], type: :user, message: error[:message])
58
+ in { code: Integer, name: String, description: String }
59
+ errors.add(code: error[:code], type: :authorization, message: error[:description])
60
+ end
61
+
62
+ self
63
+ end
64
+
65
+ # Saves the current resource, raising an error if it cannot be saved.
66
+ #
67
+ # @return [FulfilApi::Resource] The saved resource.
68
+ # @raise [FulfilApi::Error] If an error occurs during saving.
69
+ #
70
+ # @example Saving a resource with error raising
71
+ # resource.save!
72
+ def save!
73
+ errors.clear
74
+
75
+ if id.present?
76
+ FulfilApi.client.put("/model/#{model_name}/#{id}", body: to_h)
77
+ else # rubocop:disable Style/EmptyElse
78
+ # TODO: Implement the {#create} and {#create!} methods to save a new resource
79
+ end
80
+
81
+ self
82
+ end
83
+
84
+ # Updates the resource with the given attributes and saves it.
85
+ #
86
+ # @param attributes [Hash] The attributes to assign to the resource.
87
+ # @return [FulfilApi::Resource] The updated resource.
88
+ #
89
+ # @example Updating a resource
90
+ # resource.update(reference: "MK123")
91
+ def update(attributes)
92
+ assign_attributes(attributes)
93
+ save
94
+ end
95
+
96
+ # Updates the resource with the given attributes and saves it, raising an error if saving fails.
97
+ #
98
+ # @param attributes [Hash] The attributes to assign to the resource.
99
+ # @return [FulfilApi::Resource] The updated resource.
100
+ # @raise [FulfilApi::Error] If an error occurs during the update.
101
+ #
102
+ # @example Updating a resource with error raising
103
+ # resource.update!(reference: "MK123")
104
+ def update!(attributes)
105
+ assign_attributes(attributes)
106
+ save!
107
+ end
108
+ end
109
+ end
110
+ end
@@ -14,22 +14,25 @@ module FulfilApi
14
14
  # Loads resources from Fulfil's API based on the current filters, fields, and limits
15
15
  # if they haven't been loaded yet.
16
16
  #
17
- # Requires that {#name} is set; raises an exception if it's not.
17
+ # Requires that {#model_name} is set; raises an exception if it's not.
18
18
  #
19
19
  # @return [true, false] True if the resources were loaded successfully.
20
- def load
20
+ def load # rubocop:disable Metrics/MethodLength
21
21
  return true if loaded?
22
22
 
23
- if name.nil?
23
+ if model_name.nil?
24
24
  raise FulfilApi::Resource::Relation::ModelNameMissing, "The model name is missing. Use #set to define it."
25
25
  end
26
26
 
27
27
  response = FulfilApi.client.put(
28
- "/model/#{name}/search_read",
28
+ "/model/#{model_name}/search_read",
29
29
  body: { filters: conditions, fields: fields, limit: request_limit }.compact_blank
30
30
  )
31
31
 
32
- @resources = response.map { |resource| @resource_klass.new(resource) }
32
+ @resources = response.map do |attributes|
33
+ @resource_klass.new(attributes.merge(model_name: model_name))
34
+ end
35
+
33
36
  @loaded = true
34
37
  end
35
38
 
@@ -19,11 +19,11 @@ module FulfilApi
19
19
  #
20
20
  # @todo In the future, derive the {#name} from the @resource_klass automatically.
21
21
  #
22
- # @param name [String] The name of the resource model in Fulfil.
22
+ # @param model_name [String] The name of the resource model in Fulfil.
23
23
  # @return [FulfilApi::Resource::Relation] A new {Relation} instance with the model name set.
24
- def set(name:)
24
+ def set(model_name:)
25
25
  clone.tap do |relation|
26
- relation.name = name
26
+ relation.model_name = model_name
27
27
  end
28
28
  end
29
29
  end
@@ -41,10 +41,10 @@ module FulfilApi
41
41
  # depending on the API's limitations.
42
42
  #
43
43
  # @example Requesting nested data fields
44
- # FulfilApi::Resource.set(name: "sale.line").select("sale.reference").find_by(["id", "=", 10])
44
+ # FulfilApi::Resource.set(model_name: "sale.line").select("sale.reference").find_by(["id", "=", 10])
45
45
  #
46
46
  # @example Requesting additional fields
47
- # FulfilApi::Resource.set(name: "sale.sale").select(:reference).find_by(["id", "=", 10])
47
+ # FulfilApi::Resource.set(model_name: "sale.sale").select(:reference).find_by(["id", "=", 10])
48
48
  #
49
49
  # @param fields [Array<Symbol, String>] The fields to include in the response.
50
50
  # @return [FulfilApi::Resource::Relation] A new {Relation} instance with the selected fields.
@@ -59,7 +59,7 @@ module FulfilApi
59
59
  # as arrays according to the Fulfil API documentation.
60
60
  #
61
61
  # @example Simple querying with conditions
62
- # FulfilApi::Resource.set(name: "sale.line").where(["sale.reference", "=", "ORDER-123"])
62
+ # FulfilApi::Resource.set(model_name: "sale.line").where(["sale.reference", "=", "ORDER-123"])
63
63
  #
64
64
  # @todo Enhance the {#where} method to allow more natural and flexible queries.
65
65
  #
@@ -13,7 +13,7 @@ module FulfilApi
13
13
  include Naming
14
14
  include QueryMethods
15
15
 
16
- attr_accessor :conditions, :fields, :name, :request_limit
16
+ attr_accessor :conditions, :fields, :model_name, :request_limit
17
17
 
18
18
  delegate_missing_to :all
19
19
 
@@ -5,9 +5,17 @@ module FulfilApi
5
5
  # endpoints of Fulfil.
6
6
  class Resource
7
7
  include AttributeAssignable
8
+ include Persistable
9
+
10
+ class ModelNameMissing < Error; end
8
11
 
9
12
  def initialize(attributes = {})
13
+ attributes.deep_stringify_keys!
14
+
10
15
  @attributes = {}.with_indifferent_access
16
+ @model_name = attributes.delete("model_name").presence ||
17
+ raise(ModelNameMissing, "The model name is missing. Use the :model_name attribute to define it.")
18
+
11
19
  assign_attributes(attributes)
12
20
  end
13
21
 
@@ -23,7 +31,7 @@ module FulfilApi
23
31
  # forwarded to the {FulfilApi::Resource.relation}.
24
32
  #
25
33
  # @example forwarding of the .where class method
26
- # FulfilApi::Resource.set(name: "sale.sale").find_by(["id", "=", 100])
34
+ # FulfilApi::Resource.set(model_name: "sale.sale").find_by(["id", "=", 100])
27
35
  #
28
36
  # @return [FulfilApi::Resource::Relation]
29
37
  def relation
@@ -39,11 +47,30 @@ module FulfilApi
39
47
  @attributes[attribute_name]
40
48
  end
41
49
 
50
+ # Builds a structure for keeping track of any errors when trying to use the
51
+ # persistance methods for the API resource.
52
+ #
53
+ # @return [FulfilApi::Resource::Errors]
54
+ def errors
55
+ @errors ||= Errors.new(self)
56
+ end
57
+
58
+ # The {#id} is a shorthand to easily grab the ID of an API resource.
59
+ #
60
+ # @return [Integer, nil]
61
+ def id
62
+ @attributes["id"]
63
+ end
64
+
42
65
  # Returns all currently assigned attributes for a {FulfilApi::Resource}.
43
66
  #
44
67
  # @return [Hash]
45
68
  def to_h
46
69
  @attributes
47
70
  end
71
+
72
+ private
73
+
74
+ attr_reader :model_name
48
75
  end
49
76
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FulfilApi
4
- VERSION = "0.0.2"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/fulfil_api.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "zeitwerk"
4
4
 
5
5
  loader = Zeitwerk::Loader.for_gem
6
+ loader.ignore("#{__dir__}/fulfil_api/test_helper.rb")
6
7
  loader.setup
7
8
 
8
9
  require "active_support"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fulfil_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Vermaas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-05 00:00:00.000000000 Z
11
+ date: 2024-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -84,10 +84,13 @@ files:
84
84
  - lib/fulfil_api/access_token.rb
85
85
  - lib/fulfil_api/client.rb
86
86
  - lib/fulfil_api/configuration.rb
87
+ - lib/fulfil_api/customer_shipment.rb
87
88
  - lib/fulfil_api/error.rb
88
89
  - lib/fulfil_api/resource.rb
89
90
  - lib/fulfil_api/resource/attribute_assignable.rb
90
91
  - lib/fulfil_api/resource/attribute_type.rb
92
+ - lib/fulfil_api/resource/errors.rb
93
+ - lib/fulfil_api/resource/persistable.rb
91
94
  - lib/fulfil_api/resource/relation.rb
92
95
  - lib/fulfil_api/resource/relation/loadable.rb
93
96
  - lib/fulfil_api/resource/relation/naming.rb