ledger_sync 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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