ledger_sync 1.3.4 → 1.3.5

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +14 -1
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +3 -2
  5. data/Dockerfile +2 -2
  6. data/Gemfile.lock +11 -11
  7. data/LICENSE.txt +97 -21
  8. data/README.md +28 -11
  9. data/bin/quickbooks_online_oauth_server.rb +101 -0
  10. data/lib/ledger_sync.rb +8 -0
  11. data/lib/ledger_sync/adaptor_configuration.rb +4 -4
  12. data/lib/ledger_sync/adaptor_configuration_store.rb +4 -4
  13. data/lib/ledger_sync/adaptors/adaptor.rb +4 -6
  14. data/lib/ledger_sync/adaptors/dashboard_url_helper.rb +23 -0
  15. data/lib/ledger_sync/adaptors/ledger_serializer_attribute_set.rb +3 -5
  16. data/lib/ledger_sync/adaptors/mixins/infer_ledger_serializer_mixin.rb +16 -0
  17. data/lib/ledger_sync/adaptors/netsuite/account/searcher_ledger_deserializer.rb +30 -0
  18. data/lib/ledger_sync/adaptors/netsuite/adaptor.rb +11 -3
  19. data/lib/ledger_sync/adaptors/netsuite/customer/searcher.rb +12 -0
  20. data/lib/ledger_sync/adaptors/netsuite/customer/searcher_ledger_deserializer.rb +23 -0
  21. data/lib/ledger_sync/adaptors/netsuite/dashboard_url_helper.rb +24 -0
  22. data/lib/ledger_sync/adaptors/netsuite/department/searcher_ledger_deserializer.rb +21 -0
  23. data/lib/ledger_sync/adaptors/netsuite/ledger_serializer_type/active_type.rb +26 -0
  24. data/lib/ledger_sync/adaptors/netsuite/location/ledger_serializer.rb +18 -0
  25. data/lib/ledger_sync/adaptors/netsuite/location/operations/create.rb +21 -0
  26. data/lib/ledger_sync/adaptors/netsuite/location/operations/delete.rb +21 -0
  27. data/lib/ledger_sync/adaptors/netsuite/location/operations/find.rb +21 -0
  28. data/lib/ledger_sync/adaptors/netsuite/location/operations/update.rb +21 -0
  29. data/lib/ledger_sync/adaptors/netsuite/location/searcher.rb +12 -0
  30. data/lib/ledger_sync/adaptors/netsuite/record/http_method.rb +1 -1
  31. data/lib/ledger_sync/adaptors/netsuite/record/metadata.rb +11 -7
  32. data/lib/ledger_sync/adaptors/netsuite/searcher.rb +30 -8
  33. data/lib/ledger_sync/adaptors/netsuite/vendor/searcher.rb +12 -0
  34. data/lib/ledger_sync/adaptors/netsuite/vendor/searcher_ledger_deserializer.rb +23 -0
  35. data/lib/ledger_sync/adaptors/operation.rb +7 -3
  36. data/lib/ledger_sync/adaptors/quickbooks_online/adaptor.rb +7 -2
  37. data/lib/ledger_sync/adaptors/quickbooks_online/dashboard_url_helper.rb +1 -26
  38. data/lib/ledger_sync/adaptors/quickbooks_online/department/ledger_serializer.rb +6 -3
  39. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer.rb +1 -1
  40. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/department_reference_type.rb +26 -0
  41. data/lib/ledger_sync/adaptors/quickbooks_online/operation/full_update.rb +0 -1
  42. data/lib/ledger_sync/adaptors/quickbooks_online/util/adaptor_error_parser.rb +5 -5
  43. data/lib/ledger_sync/adaptors/quickbooks_online/util/error_matcher.rb +1 -1
  44. data/lib/ledger_sync/adaptors/quickbooks_online/util/error_parser.rb +1 -1
  45. data/lib/ledger_sync/adaptors/quickbooks_online/util/operation_error_parser.rb +5 -5
  46. data/lib/ledger_sync/adaptors/stripe/adaptor.rb +4 -12
  47. data/lib/ledger_sync/adaptors/stripe/dashboard_url_helper.rb +21 -0
  48. data/lib/ledger_sync/deserializer.rb +59 -0
  49. data/lib/ledger_sync/error/adaptor_errors.rb +2 -2
  50. data/lib/ledger_sync/error/resource_errors.rb +18 -19
  51. data/lib/ledger_sync/resource.rb +9 -8
  52. data/lib/ledger_sync/resource_attribute.rb +30 -7
  53. data/lib/ledger_sync/resource_attribute/mixin.rb +13 -23
  54. data/lib/ledger_sync/resource_attribute/reference/many.rb +11 -5
  55. data/lib/ledger_sync/resource_attribute/reference/one.rb +10 -4
  56. data/lib/ledger_sync/resource_attribute_set.rb +3 -7
  57. data/lib/ledger_sync/resources/location.rb +7 -0
  58. data/lib/ledger_sync/serialization/attribute.rb +47 -0
  59. data/lib/ledger_sync/serialization/attribute_set_mixin.rb +27 -0
  60. data/lib/ledger_sync/serialization/deserializer_attribute.rb +60 -0
  61. data/lib/ledger_sync/serialization/deserializer_attribute_set.rb +22 -0
  62. data/lib/ledger_sync/serialization/deserializer_delegator.rb +17 -0
  63. data/lib/ledger_sync/serialization/mixin.rb +72 -0
  64. data/lib/ledger_sync/serialization/serializer_attribute.rb +51 -0
  65. data/lib/ledger_sync/serialization/serializer_attribute_set.rb +22 -0
  66. data/lib/ledger_sync/serialization/serializer_delegator.rb +17 -0
  67. data/lib/ledger_sync/serialization/type/references_many_type.rb +19 -0
  68. data/lib/ledger_sync/serialization/type/references_one_type.rb +12 -0
  69. data/lib/ledger_sync/serialization/type/serializer_type.rb +23 -0
  70. data/lib/ledger_sync/serialization/type/value_type.rb +15 -0
  71. data/lib/ledger_sync/serializer.rb +46 -0
  72. data/lib/ledger_sync/type/date.rb +2 -0
  73. data/lib/ledger_sync/type/id.rb +2 -0
  74. data/lib/ledger_sync/type/reference_one.rb +8 -4
  75. data/lib/ledger_sync/type/value_mixin.rb +2 -2
  76. data/lib/ledger_sync/util/mixins/delegate_iterable_methods_mixin.rb +42 -0
  77. data/lib/ledger_sync/util/mixins/dupable_mixin.rb +13 -0
  78. data/lib/ledger_sync/version.rb +1 -1
  79. metadata +39 -3
@@ -16,7 +16,7 @@ module LedgerSync
16
16
  def initialize(args = {})
17
17
  super(
18
18
  args.merge(
19
- key: "#{method} #{path}".downcase
19
+ key: "#{args.fetch(:method)} #{args.fetch(:path)}".downcase
20
20
  )
21
21
  )
22
22
  end
@@ -21,11 +21,11 @@ module LedgerSync
21
21
  end
22
22
 
23
23
  def create
24
- @create ||= http_methods.find { |_e| "post /#{record}" }
24
+ @create ||= http_methods.find { |e| e.key == "post /#{record}" }
25
25
  end
26
26
 
27
27
  def delete
28
- @delete ||= http_methods.find { |_e| "delete /#{record}/{id}" }
28
+ @delete ||= http_methods.find { |e| e.key == "delete /#{record}/{id}" }
29
29
  end
30
30
 
31
31
  def find
@@ -51,6 +51,10 @@ module LedgerSync
51
51
  end
52
52
  end
53
53
 
54
+ def index
55
+ @index ||= http_methods.find { |e| e.key == "get /#{record}" }
56
+ end
57
+
54
58
  def metadata_response
55
59
  @metadata_response = begin
56
60
  adaptor.get(
@@ -79,20 +83,20 @@ module LedgerSync
79
83
  end
80
84
  end
81
85
 
82
- def index
83
- @index ||= http_methods.find { |_e| "get /#{record}" }
86
+ def patch
87
+ @patch ||= http_methods.find { |e| e.key == "patch /#{record}/{id}" }
84
88
  end
85
89
 
86
90
  def show
87
- @show ||= http_methods.find { |_e| "get /#{record}/{id}" }
91
+ @show ||= http_methods.find { |e| e.key == "get /#{record}/{id}" }
88
92
  end
89
93
 
90
94
  def update
91
- @update ||= http_methods.find { |_e| "patch /#{record}/{id}" }
95
+ @update ||= http_methods.find { |e| e.key == "patch /#{record}/{id}" }
92
96
  end
93
97
 
94
98
  def upsert
95
- @upsert ||= http_methods.find { |_e| "put /#{record}/{id}" }
99
+ @upsert ||= http_methods.find { |e| e.key == "put /#{record}/{id}" }
96
100
  end
97
101
  end
98
102
  end
@@ -6,25 +6,47 @@ module LedgerSync
6
6
  class Searcher < Adaptors::Searcher
7
7
  include Mixins::OffsetAndLimitPaginationSearcherMixin
8
8
 
9
+ def query_attributes
10
+ @query_attributes ||= searcher_ledger_deserializer_class.attributes.map(&:ledger_attribute)
11
+ end
12
+
13
+ def query_string
14
+ "SELECT #{query_attributes.join(', ')} FROM #{query_table}"
15
+ end
16
+
17
+ def query_table
18
+ @query_table ||= self.class.inferred_resource_class.resource_type
19
+ end
20
+
9
21
  def resources
10
22
  resource_class = self.class.inferred_resource_class
11
23
 
12
24
  @resources ||= begin
13
25
  @request = adaptor
14
- .get(
15
- path: "/#{adaptor.class.ledger_resource_type_for(resource_class: resource_class)}?limit=#{limit}&offset=#{offset}"
16
- )
17
-
18
- request.body
19
- .fetch('items')
20
- .map do |c|
21
- ledger_deserializer_class.new(
26
+ .post(
27
+ body: { q: query_string.to_s },
28
+ request_url: adaptor.api_base_url.gsub('/record/v1', '') + "/query/v1/suiteql?limit=#{limit}&offset=#{offset}"
29
+ )
30
+
31
+ case request.status
32
+ when 200
33
+ request.body
34
+ .fetch('items')
35
+ .map do |c|
36
+ searcher_ledger_deserializer_class.new(
22
37
  resource: resource_class.new
23
38
  ).deserialize(hash: c)
24
39
  end
40
+ when 404
41
+ []
42
+ end
25
43
  end
26
44
  end
27
45
 
46
+ def searcher_ledger_deserializer_class
47
+ @searcher_ledger_deserializer_class ||= self.class.inferred_searcher_ledger_deserializer_class
48
+ end
49
+
28
50
  private
29
51
 
30
52
  def default_offset
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ module Adaptors
5
+ module NetSuite
6
+ module Vendor
7
+ class Searcher < Searcher
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ module Adaptors
5
+ module NetSuite
6
+ module Vendor
7
+ class SearcherLedgerDeserializer < NetSuite::LedgerSerializer
8
+ attribute ledger_attribute: :id,
9
+ resource_attribute: :ledger_id
10
+
11
+ attribute ledger_attribute: :email,
12
+ resource_attribute: :email
13
+
14
+ attribute ledger_attribute: :companyname,
15
+ resource_attribute: :company_name
16
+
17
+ attribute ledger_attribute: :phone,
18
+ resource_attribute: :phone_number
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -5,8 +5,12 @@ module LedgerSync
5
5
  module Operation
6
6
  module Mixin
7
7
  module ClassMethods
8
- def adaptor_klass
9
- @adaptor_klass ||= Class.const_get("#{name.split('::')[0..2].join('::')}::Adaptor")
8
+ def adaptor_class
9
+ @adaptor_class ||= Class.const_get("#{name.split('::')[0..2].join('::')}::Adaptor")
10
+ end
11
+
12
+ def operation_method
13
+ @operation_method ||= name.split('::').last.snakecase.to_sym
10
14
  end
11
15
 
12
16
  def operations_module
@@ -164,7 +168,7 @@ module LedgerSync
164
168
  end
165
169
  end
166
170
 
167
- def self.klass_from(adaptor:, method:, object:)
171
+ def self.class_from(adaptor:, method:, object:)
168
172
  adaptor.base_module.const_get(
169
173
  LedgerSync::Util::StringHelpers.camelcase(object)
170
174
  )::Operations.const_get(
@@ -11,6 +11,8 @@ module LedgerSync
11
11
  ROOT_URI = 'https://quickbooks.api.intuit.com'
12
12
  REVOKE_TOKEN_URI = 'https://developer.api.intuit.com/v2/oauth2/tokens/revoke'
13
13
  ROOT_SANDBOX_URI = 'https://sandbox-quickbooks.api.intuit.com'
14
+ PRODUCTION_APP_URL_BASE = 'https://qbo.intuit.com/app'
15
+ SANDBOX_APP_URL_BASE = 'https://app.sandbox.qbo.intuit.com/app'
14
16
 
15
17
  attr_reader :access_token,
16
18
  :client_id,
@@ -142,7 +144,7 @@ module LedgerSync
142
144
  def update_secrets_in_dotenv
143
145
  return if ENV['TEST_ENV'] && !ENV['USE_DOTENV_ADAPTOR_SECRETS']
144
146
 
145
- filename = File.join(LedgerSync.root, '.env')
147
+ filename = File.join(Dir.pwd, '.env')
146
148
  return unless File.exist?(filename)
147
149
 
148
150
  prefix = 'QUICKBOOKS_ONLINE_'
@@ -172,7 +174,10 @@ module LedgerSync
172
174
  end
173
175
 
174
176
  def url_for(resource:)
175
- DashboardURLHelper.new(resource: resource, test: test).url
177
+ DashboardURLHelper.new(
178
+ resource: resource,
179
+ base_url: (test ? SANDBOX_APP_URL_BASE : PRODUCTION_APP_URL_BASE)
180
+ ).url
176
181
  end
177
182
 
178
183
  def self.ledger_attributes_to_save
@@ -3,22 +3,7 @@
3
3
  module LedgerSync
4
4
  module Adaptors
5
5
  module QuickBooksOnline
6
- class DashboardURLHelper
7
- PRODUCTION_APP_URL_BASE = 'https://qbo.intuit.com/app'
8
- SANDBOX_APP_URL_BASE = 'https://app.sandbox.qbo.intuit.com/app'
9
-
10
- attr_reader :resource,
11
- :test
12
-
13
- def initialize(resource:, test:)
14
- @resource = resource
15
- @test = test
16
- end
17
-
18
- def production_url
19
- @production_url ||= PRODUCTION_APP_URL_BASE + resource_path
20
- end
21
-
6
+ class DashboardURLHelper < LedgerSync::Adaptors::DashboardURLHelper
22
7
  def resource_path
23
8
  @resource_path = case resource
24
9
  when LedgerSync::Account
@@ -43,16 +28,6 @@ module LedgerSync
43
28
  "/vendordetail?nameId=#{resource.ledger_id}"
44
29
  end
45
30
  end
46
-
47
- def sandbox_url
48
- @sandbox_url ||= SANDBOX_APP_URL_BASE + resource_path
49
- end
50
-
51
- def url
52
- return production_url unless test
53
-
54
- sandbox_url
55
- end
56
31
  end
57
32
  end
58
33
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../ledger_serializer_type/department_reference_type'
4
+
3
5
  module LedgerSync
4
6
  module Adaptors
5
7
  module QuickBooksOnline
@@ -16,14 +18,15 @@ module LedgerSync
16
18
  attribute ledger_attribute: 'FullyQualifiedName',
17
19
  resource_attribute: :fully_qualified_name
18
20
 
19
- attribute ledger_attribute: 'ParentRef.value',
20
- resource_attribute: 'parent.ledger_id'
21
+ attribute ledger_attribute: :ParentRef,
22
+ resource_attribute: :parent,
23
+ type: LedgerSerializerType::DepartmentReferenceType
21
24
 
22
25
  # Sending "ParentRef": {"value": null} results in QBO API crash
23
26
  # This patches serialized hash to exclude it unless we don't set value
24
27
  def to_ledger_hash(deep_merge_unmapped_values: {}, only_changes: false)
25
28
  ret = super(only_changes: only_changes)
26
- ret = ret.except('ParentRef') unless resource.parent_changed?
29
+
27
30
  return ret unless deep_merge_unmapped_values.any?
28
31
 
29
32
  deep_merge_if_not_mapped(
@@ -107,7 +107,7 @@ module LedgerSync
107
107
  def deep_merge_if_not_mapped(current_hash:, hash_to_search:)
108
108
  hash_to_search.each do |key, value|
109
109
  current_hash[key] = if current_hash.key?(key)
110
- if value.is_a?(Hash)
110
+ if value.is_a?(Hash) && current_hash[key].present?
111
111
  deep_merge_if_not_mapped(
112
112
  current_hash: current_hash[key],
113
113
  hash_to_search: value
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ module Adaptors
5
+ module QuickBooksOnline
6
+ module LedgerSerializerType
7
+ class DepartmentReferenceType < Adaptors::LedgerSerializerType::ValueType
8
+ def convert_from_ledger(value:)
9
+ return if value.nil?
10
+ return if value['value'].nil?
11
+
12
+ LedgerSync::Department.new(ledger_id: value['value'])
13
+ end
14
+
15
+ def convert_from_local(value:)
16
+ return if value.nil?
17
+
18
+ {
19
+ 'value' => value.ledger_id
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -28,7 +28,6 @@ module LedgerSync
28
28
  merged_serializer = ledger_serializer.class.new(
29
29
  resource: merged_resource
30
30
  )
31
-
32
31
  response_to_operation_result(
33
32
  response: adaptor.post(
34
33
  path: ledger_resource_type_for_path,
@@ -8,7 +8,7 @@ module LedgerSync
8
8
  module Util
9
9
  class AdaptorErrorParser < ErrorParser
10
10
  class ThrottleMatcher < ErrorMatcher
11
- def error_klass
11
+ def error_class
12
12
  Error::AdaptorError::ThrottleError
13
13
  end
14
14
 
@@ -23,7 +23,7 @@ module LedgerSync
23
23
  end
24
24
 
25
25
  class AuthenticationMatcher < ErrorMatcher
26
- def error_klass
26
+ def error_class
27
27
  Error::AdaptorError::AuthenticationError
28
28
  end
29
29
 
@@ -39,7 +39,7 @@ module LedgerSync
39
39
  end
40
40
 
41
41
  class AuthorizationMatcher < ErrorMatcher
42
- def error_klass
42
+ def error_class
43
43
  Error::AdaptorError::AuthorizationError
44
44
  end
45
45
 
@@ -55,7 +55,7 @@ module LedgerSync
55
55
  end
56
56
 
57
57
  class ClientMatcher < ErrorMatcher
58
- def error_klass
58
+ def error_class
59
59
  Error::AdaptorError::ConfigurationError
60
60
  end
61
61
 
@@ -88,7 +88,7 @@ module LedgerSync
88
88
  matcher = parser.new(error: error)
89
89
  next unless matcher.match?
90
90
 
91
- return matcher.error_klass.new(
91
+ return matcher.error_class.new(
92
92
  adaptor: adaptor,
93
93
  message: matcher.output_message,
94
94
  response: error
@@ -19,7 +19,7 @@ module LedgerSync
19
19
  nil
20
20
  end
21
21
 
22
- def error_klass
22
+ def error_class
23
23
  raise NotImplementedError
24
24
  end
25
25
 
@@ -13,7 +13,7 @@ module LedgerSync
13
13
  @error = error
14
14
  end
15
15
 
16
- def error_klass
16
+ def error_class
17
17
  raise NotImplementedError
18
18
  end
19
19
 
@@ -8,7 +8,7 @@ module LedgerSync
8
8
  module Util
9
9
  class OperationErrorParser < ErrorParser
10
10
  class DuplicateNameMatcher < ErrorMatcher
11
- def error_klass
11
+ def error_class
12
12
  Error::OperationError::DuplicateLedgerResourceError
13
13
  end
14
14
 
@@ -23,7 +23,7 @@ module LedgerSync
23
23
  end
24
24
 
25
25
  class NotFoundMatcher < ErrorMatcher
26
- def error_klass
26
+ def error_class
27
27
  Error::OperationError::NotFoundError
28
28
  end
29
29
 
@@ -38,7 +38,7 @@ module LedgerSync
38
38
  end
39
39
 
40
40
  class ValidationError < ErrorMatcher
41
- def error_klass
41
+ def error_class
42
42
  Error::OperationError::LedgerValidationError
43
43
  end
44
44
 
@@ -52,7 +52,7 @@ module LedgerSync
52
52
  end
53
53
 
54
54
  class GenericMatcher < ErrorMatcher
55
- def error_klass
55
+ def error_class
56
56
  Error::OperationError
57
57
  end
58
58
 
@@ -85,7 +85,7 @@ module LedgerSync
85
85
  matcher = parser.new(error: error)
86
86
  next unless matcher.match?
87
87
 
88
- return matcher.error_klass.new(
88
+ return matcher.error_class.new(
89
89
  operation: operation,
90
90
  message: matcher.output_message,
91
91
  response: error
@@ -15,18 +15,10 @@ module LedgerSync
15
15
  end
16
16
 
17
17
  def url_for(resource:)
18
- base_url = 'https://dashboard.stripe.com'
19
- resource_path = case resource
20
- when LedgerSync::Customer
21
- "/customers/#{resource.ledger_id}"
22
- else
23
- raise Error::AdaptorError::UnknownURLFormat.new(
24
- adaptor: self,
25
- resource: resource
26
- )
27
- end
28
-
29
- base_url + resource_path
18
+ DashboardURLHelper.new(
19
+ resource: resource,
20
+ base_url: "https://dashboard.stripe.com"
21
+ ).url
30
22
  end
31
23
 
32
24
  def wrap_perform