ledger_sync 1.1.1 → 1.1.2

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 (124) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +2 -3
  5. data/lib/ledger_sync.rb +12 -6
  6. data/lib/ledger_sync/adaptors/ledger_serializer.rb +124 -0
  7. data/lib/ledger_sync/adaptors/ledger_serializer_attribute.rb +148 -0
  8. data/lib/ledger_sync/adaptors/ledger_serializer_attribute_set.rb +51 -0
  9. data/lib/ledger_sync/adaptors/ledger_serializer_type/mapping_type.rb +41 -0
  10. data/lib/ledger_sync/adaptors/ledger_serializer_type/references_many_type.rb +24 -0
  11. data/lib/ledger_sync/adaptors/ledger_serializer_type/value_type.rb +17 -0
  12. data/lib/ledger_sync/adaptors/operation.rb +15 -9
  13. data/lib/ledger_sync/adaptors/quickbooks_online/account/ledger_serializer.rb +21 -0
  14. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/create.rb +8 -36
  15. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/find.rb +1 -13
  16. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/update.rb +4 -35
  17. data/lib/ledger_sync/adaptors/quickbooks_online/account/searcher.rb +3 -44
  18. data/lib/ledger_sync/adaptors/quickbooks_online/adaptor.rb +75 -28
  19. data/lib/ledger_sync/adaptors/quickbooks_online/bill/ledger_serializer.rb +40 -0
  20. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/create.rb +9 -48
  21. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/find.rb +10 -21
  22. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/update.rb +9 -52
  23. data/lib/ledger_sync/adaptors/quickbooks_online/bill_line_item/ledger_serializer.rb +27 -0
  24. data/lib/ledger_sync/adaptors/quickbooks_online/customer/ledger_serializer.rb +23 -0
  25. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/create.rb +2 -25
  26. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/find.rb +2 -14
  27. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/update.rb +3 -30
  28. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/ledger_serializer.rb +35 -0
  29. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/create.rb +2 -39
  30. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/find.rb +2 -14
  31. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/update.rb +4 -43
  32. data/lib/ledger_sync/adaptors/quickbooks_online/deposit_line_item/ledger_serializer.rb +27 -0
  33. data/lib/ledger_sync/adaptors/quickbooks_online/expense/ledger_serializer.rb +48 -0
  34. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/create.rb +10 -51
  35. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/find.rb +10 -22
  36. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/update.rb +10 -55
  37. data/lib/ledger_sync/adaptors/quickbooks_online/expense_line_item/ledger_serializer.rb +27 -0
  38. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/ledger_serializer.rb +32 -0
  39. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/create.rb +4 -37
  40. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/find.rb +4 -15
  41. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/update.rb +5 -42
  42. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry_line_item/ledger_serializer.rb +31 -0
  43. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer.rb +103 -0
  44. data/lib/ledger_sync/adaptors/quickbooks_online/{account/mapping.rb → ledger_serializer_type/account_sub_type.rb} +128 -148
  45. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/account_type.rb +51 -0
  46. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/amount_type.rb +23 -0
  47. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/classification_type.rb +23 -0
  48. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/date_type.rb +23 -0
  49. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/journal_entry_line_item_type.rb +20 -0
  50. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/payment_type.rb +21 -0
  51. data/lib/ledger_sync/adaptors/quickbooks_online/operation.rb +2 -7
  52. data/lib/ledger_sync/adaptors/quickbooks_online/operation/create.rb +29 -0
  53. data/lib/ledger_sync/adaptors/quickbooks_online/operation/find.rb +31 -0
  54. data/lib/ledger_sync/adaptors/quickbooks_online/operation/full_update.rb +43 -0
  55. data/lib/ledger_sync/adaptors/quickbooks_online/operation/sparse_update.rb +29 -0
  56. data/lib/ledger_sync/adaptors/quickbooks_online/payment/ledger_serializer.rb +23 -0
  57. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/create.rb +1 -24
  58. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/find.rb +1 -13
  59. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/update.rb +2 -29
  60. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/ledger_serializer.rb +33 -0
  61. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/create.rb +1 -29
  62. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/find.rb +1 -13
  63. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/update.rb +4 -35
  64. data/lib/ledger_sync/adaptors/quickbooks_online/util/adaptor_error_parser.rb +2 -1
  65. data/lib/ledger_sync/adaptors/quickbooks_online/util/operation_error_parser.rb +2 -1
  66. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/ledger_serializer.rb +25 -0
  67. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/create.rb +1 -23
  68. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/find.rb +1 -13
  69. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/update.rb +2 -29
  70. data/lib/ledger_sync/adaptors/test/account/operations/create.rb +5 -1
  71. data/lib/ledger_sync/adaptors/test/account/operations/find.rb +5 -1
  72. data/lib/ledger_sync/adaptors/test/account/operations/invalid.rb +1 -0
  73. data/lib/ledger_sync/adaptors/test/account/operations/update.rb +8 -2
  74. data/lib/ledger_sync/adaptors/test/account/operations/valid.rb +1 -0
  75. data/lib/ledger_sync/adaptors/test/customer/operations/create.rb +5 -1
  76. data/lib/ledger_sync/adaptors/test/customer/operations/find.rb +5 -1
  77. data/lib/ledger_sync/adaptors/test/customer/operations/invalid.rb +1 -0
  78. data/lib/ledger_sync/adaptors/test/customer/operations/update.rb +1 -24
  79. data/lib/ledger_sync/adaptors/test/customer/operations/valid.rb +3 -6
  80. data/lib/ledger_sync/adaptors/test/expense/operations/create.rb +8 -3
  81. data/lib/ledger_sync/adaptors/test/expense/operations/find.rb +4 -14
  82. data/lib/ledger_sync/adaptors/test/expense/operations/update.rb +2 -32
  83. data/lib/ledger_sync/adaptors/test/ledger_serializer.rb +64 -0
  84. data/lib/ledger_sync/adaptors/test/operation/create.rb +27 -0
  85. data/lib/ledger_sync/adaptors/test/operation/find.rb +29 -0
  86. data/lib/ledger_sync/adaptors/test/operation/update.rb +34 -0
  87. data/lib/ledger_sync/adaptors/test/payment/operations/create.rb +5 -1
  88. data/lib/ledger_sync/adaptors/test/payment/operations/find.rb +5 -1
  89. data/lib/ledger_sync/adaptors/test/payment/operations/update.rb +1 -23
  90. data/lib/ledger_sync/adaptors/test/transfer/operations/create.rb +1 -22
  91. data/lib/ledger_sync/adaptors/test/transfer/operations/find.rb +1 -13
  92. data/lib/ledger_sync/adaptors/test/transfer/operations/update.rb +1 -25
  93. data/lib/ledger_sync/adaptors/test/vendor/operations/create.rb +5 -1
  94. data/lib/ledger_sync/adaptors/test/vendor/operations/find.rb +7 -1
  95. data/lib/ledger_sync/adaptors/test/vendor/operations/invalid.rb +1 -0
  96. data/lib/ledger_sync/adaptors/test/vendor/operations/update.rb +1 -23
  97. data/lib/ledger_sync/adaptors/test/vendor/operations/valid.rb +1 -0
  98. data/lib/ledger_sync/error/adaptor_errors.rb +17 -5
  99. data/lib/ledger_sync/error/operation_errors.rb +12 -5
  100. data/lib/ledger_sync/error/resource_errors.rb +5 -1
  101. data/lib/ledger_sync/resource.rb +33 -6
  102. data/lib/ledger_sync/resource_attribute.rb +4 -0
  103. data/lib/ledger_sync/resource_attribute/dirty_mixin.rb +16 -0
  104. data/lib/ledger_sync/resource_attribute/mixin.rb +19 -7
  105. data/lib/ledger_sync/resource_attribute/reference/many.rb +55 -1
  106. data/lib/ledger_sync/resource_attribute_set.rb +6 -7
  107. data/lib/ledger_sync/resources/account.rb +1 -1
  108. data/lib/ledger_sync/resources/bill.rb +1 -0
  109. data/lib/ledger_sync/resources/bill_line_item.rb +2 -0
  110. data/lib/ledger_sync/resources/expense.rb +5 -1
  111. data/lib/ledger_sync/resources/expense_line_item.rb +2 -0
  112. data/lib/ledger_sync/resources/journal_entry.rb +1 -0
  113. data/lib/ledger_sync/result.rb +4 -3
  114. data/lib/ledger_sync/type/id.rb +34 -0
  115. data/lib/ledger_sync/type/reference_many.rb +8 -2
  116. data/lib/ledger_sync/type/reference_one.rb +5 -0
  117. data/lib/ledger_sync/type/value_mixin.rb +12 -0
  118. data/lib/ledger_sync/util/hash_helpers.rb +16 -0
  119. data/lib/ledger_sync/version.rb +1 -1
  120. data/qa.rb +142 -0
  121. metadata +38 -5
  122. data/lib/ledger_sync/adaptors/quickbooks_online/expense/mapping.rb +0 -15
  123. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/mapping.rb +0 -14
  124. data/lib/ledger_sync/error/sync_errors.rb +0 -22
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ module Adaptors
5
+ module LedgerSerializerType
6
+ class ReferencesManyType < ValueType
7
+ def convert_from_ledger(resource_class: nil, serializer:, value:)
8
+ resource_class ||= serializer._inferred_resource_class
9
+ return [] if value.nil?
10
+
11
+ value.map do |one_value|
12
+ serializer.new(resource: resource_class.new).deserialize(hash: one_value)
13
+ end
14
+ end
15
+
16
+ def convert_from_local(serializer:, value:)
17
+ value.map do |one_value|
18
+ serializer.new(resource: one_value).to_ledger_hash
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ module Adaptors
5
+ module LedgerSerializerType
6
+ class ValueType
7
+ def convert_from_ledger(value:)
8
+ value
9
+ end
10
+
11
+ def convert_from_local(value:)
12
+ value
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -48,6 +48,7 @@ module LedgerSync
48
48
  :before_operations,
49
49
  :operations,
50
50
  :resource,
51
+ :resource_before_perform,
51
52
  :root_operation,
52
53
  :result,
53
54
  :response,
@@ -64,6 +65,7 @@ module LedgerSync
64
65
  @before_operations = []
65
66
  @operations = []
66
67
  @resource = resource
68
+ @resource_before_perform = resource.dup
67
69
  @result = nil
68
70
  @root_operation = nil
69
71
  end
@@ -104,12 +106,21 @@ module LedgerSync
104
106
  @performed == true
105
107
  end
106
108
 
109
+ def ledger_serializer
110
+ @ledger_serializer ||= begin
111
+ modules = self.class.name.split('::Operations::').first
112
+ Object.const_get("#{modules}::LedgerSerializer").new(resource: resource)
113
+ end
114
+ end
115
+
107
116
  # Results
108
117
 
109
- def failure(error)
118
+ def failure(error, resource: nil)
119
+ @response = error
110
120
  @result = LedgerSync::OperationResult.Failure(
111
121
  error,
112
122
  operation: self,
123
+ resource: resource,
113
124
  response: error
114
125
  )
115
126
  end
@@ -118,10 +129,12 @@ module LedgerSync
118
129
  result.failure?
119
130
  end
120
131
 
121
- def success(response:)
132
+ def success(resource:, response:)
133
+ @response = response
122
134
  @result = LedgerSync::OperationResult.Success(
123
135
  self,
124
136
  operation: self,
137
+ resource: resource,
125
138
  response: response
126
139
  )
127
140
  end
@@ -176,13 +189,6 @@ module LedgerSync
176
189
  end
177
190
  end
178
191
 
179
- def merge_into(from:, to:)
180
- case to
181
- when *(Resource::PRIMITIVES | [Array]) then from
182
- else to.merge!(from) { |_key, new_value, old_value| merge_into(from: old_value, to: new_value) } if to && from
183
- end
184
- end
185
-
186
192
  private
187
193
 
188
194
  def operate
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ module Adaptors
5
+ module QuickBooksOnline
6
+ module Account
7
+ class LedgerSerializer < QuickBooksOnline::LedgerSerializer
8
+ attribute ledger_attribute: 'Id', resource_attribute: :ledger_id
9
+ attribute ledger_attribute: 'Name', resource_attribute: :name
10
+ attribute ledger_attribute: 'AccountType', resource_attribute: :account_type, type: LedgerSerializerType::AccountType
11
+ attribute ledger_attribute: 'AccountSubType', resource_attribute: :account_sub_type, type: LedgerSerializerType::AccountSubType
12
+ attribute ledger_attribute: 'AcctNum', resource_attribute: :number
13
+ attribute ledger_attribute: 'CurrencyRef.value', resource_attribute: :currency
14
+ attribute ledger_attribute: 'Classification', resource_attribute: :classification, type: LedgerSerializerType::ClassificationType
15
+ attribute ledger_attribute: 'Description', resource_attribute: :description
16
+ attribute ledger_attribute: 'Active', resource_attribute: :active
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,51 +1,23 @@
1
- require 'ledger_sync/adaptors/quickbooks_online/account/mapping'
2
-
3
1
  module LedgerSync
4
2
  module Adaptors
5
3
  module QuickBooksOnline
6
4
  module Account
7
5
  module Operations
8
- class Create < Operation::Create
6
+ class Create < QuickBooksOnline::Operation::Create
9
7
  class Contract < LedgerSync::Adaptors::Contract
10
8
  params do
11
- required(:ledger_id).value(:nil)
12
- required(:name).filled(:string)
13
- required(:classification).filled(:string)
14
- required(:account_type).filled(:string)
9
+ required(:external_id).maybe(:string)
15
10
  required(:account_sub_type).filled(:string)
16
- required(:number).maybe(:integer)
11
+ required(:account_type).filled(:string)
12
+ required(:active).maybe(:bool)
13
+ required(:classification).filled(:string)
17
14
  required(:currency).maybe(:string)
18
15
  required(:description).maybe(:string)
19
- required(:active).maybe(:bool)
16
+ required(:ledger_id).value(:nil)
17
+ required(:name).filled(:string)
18
+ required(:number).maybe(:integer)
20
19
  end
21
20
  end
22
-
23
- private
24
-
25
- def operate
26
- response = adaptor.post(
27
- resource: 'account',
28
- payload: local_resource_data
29
- )
30
-
31
- resource.ledger_id = response.dig('Id')
32
- success(response: response)
33
- end
34
-
35
- def local_resource_data
36
- {
37
- 'Name' => resource.name,
38
- 'Classification' => Mapping::ACCOUNT_CLASSIFICATIONS[resource.classification],
39
- 'AccountType' => Mapping::ACCOUNT_TYPES[resource.account_type],
40
- 'AccountSubType' => Mapping::ACCOUNT_SUB_TYPES[resource.account_sub_type],
41
- 'AcctNum' => resource.number,
42
- 'CurrencyRef' => {
43
- 'value' => resource.currency,
44
- },
45
- 'Description' => resource.description,
46
- 'Active' => resource.active
47
- }
48
- end
49
21
  end
50
22
  end
51
23
  end
@@ -6,6 +6,7 @@ module LedgerSync
6
6
  class Find < Operation::Find
7
7
  class Contract < LedgerSync::Adaptors::Contract
8
8
  params do
9
+ required(:external_id).maybe(:string)
9
10
  required(:ledger_id).filled(:string)
10
11
  required(:name).maybe(:string)
11
12
  required(:classification).maybe(:string)
@@ -17,19 +18,6 @@ module LedgerSync
17
18
  required(:active).maybe(:bool)
18
19
  end
19
20
  end
20
-
21
- private
22
-
23
- def operate
24
- return failure(nil) if resource.ledger_id.nil?
25
-
26
- response = adaptor.find(
27
- resource: 'account',
28
- id: resource.ledger_id
29
- )
30
-
31
- success(response: response)
32
- end
33
21
  end
34
22
  end
35
23
  end
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ledger_sync/adaptors/quickbooks_online/account/mapping'
4
-
3
+ # https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/account
4
+ # Requires full update
5
5
  module LedgerSync
6
6
  module Adaptors
7
7
  module QuickBooksOnline
8
8
  module Account
9
9
  module Operations
10
- class Update < Operation::Update
10
+ class Update < Operation::FullUpdate
11
11
  class Contract < LedgerSync::Adaptors::Contract
12
12
  params do
13
+ required(:external_id).maybe(:string)
13
14
  required(:ledger_id).filled(:string)
14
15
  required(:name).filled(:string)
15
16
  required(:classification).filled(:string)
@@ -21,38 +22,6 @@ module LedgerSync
21
22
  required(:active).maybe(:bool)
22
23
  end
23
24
  end
24
-
25
- private
26
-
27
- def operate
28
- ledger_resource_data = adaptor.find(
29
- resource: 'account',
30
- id: resource.ledger_id
31
- )
32
-
33
- response = adaptor.post(
34
- resource: 'account',
35
- payload: merge_into(from: local_resource_data, to: ledger_resource_data)
36
- )
37
-
38
- resource.ledger_id = response.dig('Id')
39
- success(response: response)
40
- end
41
-
42
- def local_resource_data
43
- {
44
- 'Name' => resource.name,
45
- 'Classification' => Mapping::ACCOUNT_CLASSIFICATIONS[resource.classification],
46
- 'AccountType' => Mapping::ACCOUNT_TYPES[resource.account_type],
47
- 'AccountSubType' => Mapping::ACCOUNT_SUB_TYPES[resource.account_sub_type],
48
- 'AcctNum' => resource.number,
49
- 'CurrencyRef' => {
50
- 'value' => resource.currency,
51
- },
52
- 'Description' => resource.description,
53
- 'Active' => resource.active
54
- }
55
- end
56
25
  end
57
26
  end
58
27
  end
@@ -1,20 +1,10 @@
1
- require 'ledger_sync/adaptors/quickbooks_online/account/mapping'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module LedgerSync
4
4
  module Adaptors
5
5
  module QuickBooksOnline
6
6
  module Account
7
- class Searcher < LedgerSync::Adaptors::Searcher
8
- def next_searcher
9
- paginate(limit: limit, offset: offset + limit)
10
- end
11
-
12
- def previous_searcher
13
- return nil if offset <= 1
14
-
15
- paginate(limit: limit, offset: offset - limit)
16
- end
17
-
7
+ class Searcher < QuickBooksOnline::Searcher
18
8
  def resources
19
9
  @resources ||= begin
20
10
  adaptor
@@ -25,41 +15,10 @@ module LedgerSync
25
15
  offset: offset
26
16
  )
27
17
  .map do |c|
28
- LedgerSync::Account.new(
29
- ledger_id: c.fetch('Id'),
30
- name: c.fetch('Name'),
31
- classification: Mapping::ACCOUNT_CLASSIFICATIONS.invert[c.fetch('Classification')] || c.fetch('Classification'),
32
- account_type: Mapping::ACCOUNT_TYPES.invert[c.fetch('AccountType')] || c.fetch('AccountType'),
33
- account_sub_type: Mapping::ACCOUNT_SUB_TYPES.invert[c.fetch('AccountSubType')] || c.fetch('AccountSubType')
34
- )
18
+ LedgerSerializer.new(resource: LedgerSync::Account.new).deserialize(hash: c)
35
19
  end
36
20
  end
37
21
  end
38
-
39
- def search
40
- super
41
- rescue OAuth2::Error => e
42
- @response = e # TODO: Better catch/raise errors as LedgerSync::Error
43
- failure
44
- end
45
-
46
- private
47
- # Pagination uses notation of limit and offset
48
- # limit: number of results per page
49
- #
50
- # offset: position of first result in a list.
51
- # starts from 1, not 0
52
- #
53
- # More here:
54
- # https://developer.intuit.com/app/developer/qbo/docs/develop/explore-the-quickbooks-online-api/data-queries#pagination
55
-
56
- def limit
57
- pagination.fetch(:limit, 10).to_i
58
- end
59
-
60
- def offset
61
- pagination.fetch(:offset, 1).to_i
62
- end
63
22
  end
64
23
  end
65
24
  end
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'oauth2'
2
4
 
3
5
  module LedgerSync
4
6
  module Adaptors
5
7
  module QuickBooksOnline
6
8
  class Adaptor < LedgerSync::Adaptors::Adaptor
7
- AUTHORIZE_URL = 'https://appcenter.intuit.com/connect/oauth2'.freeze
9
+ AUTHORIZE_URL = 'https://appcenter.intuit.com/connect/oauth2'
8
10
  OAUTH_HEADERS = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }.freeze
9
- ROOT_URI = 'https://quickbooks.api.intuit.com'.freeze
10
- ROOT_SANDBOX_URI = 'https://sandbox-quickbooks.api.intuit.com'.freeze
11
- SITE_URL = 'https://appcenter.intuit.com/connect/oauth2'.freeze
12
- TOKEN_URL = 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer'.freeze
11
+ ROOT_URI = 'https://quickbooks.api.intuit.com'
12
+ ROOT_SANDBOX_URI = 'https://sandbox-quickbooks.api.intuit.com'
13
+ SITE_URL = 'https://appcenter.intuit.com/connect/oauth2'
14
+ TOKEN_URL = 'https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer'
13
15
 
14
16
  attr_reader :access_token,
15
17
  :client_id,
@@ -43,7 +45,17 @@ module LedgerSync
43
45
  @root_uri = (test ? ROOT_SANDBOX_URI : ROOT_URI)
44
46
  end
45
47
 
48
+ def authorization_url(redirect_uri:)
49
+ oauth_client.auth_code.authorize_url(
50
+ redirect_uri: redirect_uri,
51
+ response_type: 'code',
52
+ state: SecureRandom.hex(12),
53
+ scope: 'com.intuit.quickbooks.accounting'
54
+ )
55
+ end
56
+
46
57
  def find(resource:, id:)
58
+ resource = resource.to_s
47
59
  url = "#{oauth_base_uri}/#{resourcify(resource)}/#{id}"
48
60
 
49
61
  response = request(:get, url, headers: OAUTH_HEADERS.dup)
@@ -51,7 +63,7 @@ module LedgerSync
51
63
  end
52
64
 
53
65
  def parse_operation_error(error:, operation:)
54
- return nil unless error.kind_of?(OAuth2::Error)
66
+ return nil unless error.is_a?(OAuth2::Error)
55
67
 
56
68
  Util::OperationErrorParser.new(
57
69
  error: error,
@@ -60,6 +72,7 @@ module LedgerSync
60
72
  end
61
73
 
62
74
  def post(resource:, payload:)
75
+ resource = resource.to_s
63
76
  url = "#{oauth_base_uri}/#{resourcify(resource)}"
64
77
 
65
78
  response = request(:post, url, headers: OAUTH_HEADERS.dup, body: payload.to_json)
@@ -67,6 +80,7 @@ module LedgerSync
67
80
  end
68
81
 
69
82
  def query(resource:, query:, limit: 10, offset: 1)
83
+ resource = resource.to_s
70
84
  full_query = "SELECT * FROM #{resource.classify} WHERE #{query} STARTPOSITION #{offset} MAXRESULTS #{limit}"
71
85
  url = "#{oauth_base_uri}/query?query=#{CGI.escape(full_query)}"
72
86
 
@@ -75,35 +89,54 @@ module LedgerSync
75
89
  end
76
90
 
77
91
  def refresh!
78
- refreshed = oauth.refresh!
92
+ set_credentials_from_oauth_token(token: oauth.refresh!)
79
93
 
80
- @previous_access_tokens << access_token
81
- @access_token = refreshed.token
94
+ self
95
+ rescue OAuth2::Error => e
96
+ raise parse_error(error: e)
97
+ end
82
98
 
83
- @expires_at = Time&.at(refreshed.expires_at.to_i)&.to_datetime
84
- @refresh_token_expires_at = Time&.at(Time.now.to_i + refreshed.params['x_refresh_token_expires_in'])&.to_datetime unless refreshed.params['x_refresh_token_expires_in'].nil?
99
+ def set_credentials_from_oauth_code(code:, redirect_uri:)
100
+ oauth_token = oauth_client.auth_code.get_token(
101
+ code,
102
+ redirect_uri: redirect_uri
103
+ )
85
104
 
86
- @previous_refresh_tokens << refresh_token
87
- @refresh_token = refreshed.refresh_token
105
+ set_credentials_from_oauth_token(token: oauth_token)
88
106
 
89
- oauth(force: true) # Ensure we update the memoized @oauth
107
+ oauth_token
108
+ end
90
109
 
91
- self
92
- rescue OAuth2::Error => e
93
- raise parse_error(error: e)
110
+ def url_for(resource:)
111
+ base_url = test ? 'https://app.sandbox.qbo.intuit.com/app' : 'https://app.qbo.intuit.com/app'
112
+ resource_path = case resource
113
+ when LedgerSync::Account
114
+ "/register?accountId=#{resource.ledger_id}"
115
+ when LedgerSync::Bill
116
+ "/bill?txnId=#{resource.ledger_id}"
117
+ when LedgerSync::Customer
118
+ "/customerdetail?nameId=#{resource.ledger_id}"
119
+ when LedgerSync::Deposit
120
+ "/deposit?txnId=#{resource.ledger_id}"
121
+ when LedgerSync::Expense
122
+ "/expense?txnId=#{resource.ledger_id}"
123
+ when LedgerSync::JournalEntry
124
+ "/journal?txnId=#{resource.ledger_id}"
125
+ when LedgerSync::Payment
126
+ "/recvpayment?txnId=#{resource.ledger_id}"
127
+ when LedgerSync::Transfer
128
+ "/transfer?txnId=#{resource.ledger_id}"
129
+ when LedgerSync::Vendor
130
+ "/vendordetail?nameId=#{resource.ledger_id}"
131
+ end
132
+
133
+ base_url + resource_path
94
134
  end
95
135
 
96
136
  def self.ledger_attributes_to_save
97
137
  %i[access_token expires_at refresh_token refresh_token_expires_at]
98
138
  end
99
139
 
100
- # def self.url_for(resource:)
101
- # case resource
102
- # when Customer
103
- # # TODO: How can we get the proper URL? In dev, a random sandbox is used.
104
- # end
105
- # end
106
-
107
140
  private
108
141
 
109
142
  def oauth_base_uri
@@ -135,14 +168,15 @@ module LedgerSync
135
168
  def request(method, *args)
136
169
  oauth.send(method, *args)
137
170
  rescue OAuth2::Error => e
138
- error = parse_error(error: e)
139
- raise (error || e) unless error.is_a?(Error::AdaptorError::AuthenticationError)
171
+ error = parse_error(error: e) || parse_operation_error(error: e, operation: nil) || e
172
+
173
+ raise error unless error.is_a?(Error::AdaptorError::AuthenticationError)
140
174
 
141
175
  begin
142
176
  refresh!
143
177
  oauth.send(method, *args)
144
- rescue OAuth2::Error => e2
145
- raise parse_error(error: e2) || e2
178
+ rescue OAuth2::Error => e
179
+ raise parse_error(error: e) || e
146
180
  end
147
181
  end
148
182
 
@@ -156,6 +190,19 @@ module LedgerSync
156
190
  def resourcify(str)
157
191
  str.tr('_', '')
158
192
  end
193
+
194
+ def set_credentials_from_oauth_token(token:)
195
+ @previous_access_tokens << access_token if access_token.present?
196
+ @access_token = token.token
197
+
198
+ @expires_at = Time&.at(token.expires_at.to_i)&.to_datetime
199
+ @refresh_token_expires_at = Time&.at(Time.now.to_i + token.params['x_refresh_token_expires_in'])&.to_datetime unless token.params['x_refresh_token_expires_in'].nil?
200
+
201
+ @previous_refresh_tokens << refresh_token if refresh_token.present?
202
+ @refresh_token = token.refresh_token
203
+
204
+ oauth(force: true) # Ensure we update the memoized @oauth
205
+ end
159
206
  end
160
207
  end
161
208
  end