ledger_sync 1.1.3 → 1.3.1

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 (216) hide show
  1. checksums.yaml +4 -4
  2. data/.env.template +25 -0
  3. data/.gitignore +3 -1
  4. data/.rubocop_todo.yml +4 -1
  5. data/.travis.yml +1 -1
  6. data/Gemfile.lock +59 -25
  7. data/README.md +160 -8
  8. data/_config.yml +1 -0
  9. data/bin/qa +3 -0
  10. data/ledger_sync.gemspec +4 -0
  11. data/lib/ledger_sync.rb +40 -30
  12. data/lib/ledger_sync/adaptor_configuration.rb +17 -11
  13. data/lib/ledger_sync/adaptor_configuration_store.rb +13 -9
  14. data/lib/ledger_sync/adaptors/adaptor.rb +9 -1
  15. data/lib/ledger_sync/adaptors/ledger_serializer.rb +3 -6
  16. data/lib/ledger_sync/adaptors/ledger_serializer_attribute_set.rb +1 -1
  17. data/lib/ledger_sync/adaptors/ledger_serializer_type/references_many_type.rb +1 -1
  18. data/lib/ledger_sync/adaptors/mixins/infer_adaptor_class_mixin.rb +24 -0
  19. data/lib/ledger_sync/adaptors/mixins/infer_ledger_serializer_mixin.rb +31 -0
  20. data/lib/ledger_sync/adaptors/mixins/infer_resource_class_mixin.rb +22 -0
  21. data/lib/ledger_sync/adaptors/mixins/offset_and_limit_pagination_searcher_mixin.rb +37 -0
  22. data/lib/ledger_sync/adaptors/netsuite/account/ledger_deserializer.rb +24 -0
  23. data/lib/ledger_sync/adaptors/netsuite/account/ledger_serializer.rb +22 -0
  24. data/lib/ledger_sync/adaptors/netsuite/account/operations/create.rb +28 -0
  25. data/lib/ledger_sync/adaptors/netsuite/account/operations/find.rb +28 -0
  26. data/lib/ledger_sync/adaptors/netsuite/account/searcher.rb +28 -0
  27. data/lib/ledger_sync/adaptors/netsuite/adaptor.rb +145 -0
  28. data/lib/ledger_sync/adaptors/netsuite/config.rb +6 -0
  29. data/lib/ledger_sync/adaptors/netsuite/currency/deledger_serializer.rb +28 -0
  30. data/lib/ledger_sync/adaptors/netsuite/currency/ledger_serializer.rb +25 -0
  31. data/lib/ledger_sync/adaptors/netsuite/currency/operations/create.rb +25 -0
  32. data/lib/ledger_sync/adaptors/netsuite/currency/operations/delete.rb +23 -0
  33. data/lib/ledger_sync/adaptors/netsuite/currency/operations/find.rb +23 -0
  34. data/lib/ledger_sync/adaptors/netsuite/currency/operations/update.rb +23 -0
  35. data/lib/ledger_sync/adaptors/netsuite/customer/ledger_deserializer.rb +27 -0
  36. data/lib/ledger_sync/adaptors/netsuite/customer/ledger_serializer.rb +38 -0
  37. data/lib/ledger_sync/adaptors/netsuite/customer/operations/create.rb +24 -0
  38. data/lib/ledger_sync/adaptors/netsuite/customer/operations/delete.rb +24 -0
  39. data/lib/ledger_sync/adaptors/netsuite/customer/operations/find.rb +24 -0
  40. data/lib/ledger_sync/adaptors/netsuite/customer/operations/update.rb +24 -0
  41. data/lib/ledger_sync/adaptors/netsuite/ledger_serializer.rb +30 -0
  42. data/lib/ledger_sync/adaptors/netsuite/ledger_serializer_type/reference_type.rb +24 -0
  43. data/lib/ledger_sync/adaptors/netsuite/ledger_serializer_type/subsidiary_type.rb +22 -0
  44. data/lib/ledger_sync/adaptors/netsuite/operation.rb +25 -0
  45. data/lib/ledger_sync/adaptors/netsuite/operation/create.rb +65 -0
  46. data/lib/ledger_sync/adaptors/netsuite/operation/delete.rb +49 -0
  47. data/lib/ledger_sync/adaptors/netsuite/operation/find.rb +56 -0
  48. data/lib/ledger_sync/adaptors/netsuite/operation/update.rb +68 -0
  49. data/lib/ledger_sync/adaptors/netsuite/record/http_method.rb +38 -0
  50. data/lib/ledger_sync/adaptors/netsuite/record/metadata.rb +75 -0
  51. data/lib/ledger_sync/adaptors/netsuite/record/property.rb +45 -0
  52. data/lib/ledger_sync/adaptors/netsuite/searcher.rb +17 -0
  53. data/lib/ledger_sync/adaptors/netsuite/token.rb +162 -0
  54. data/lib/ledger_sync/adaptors/netsuite/vendor/ledger_deserializer.rb +32 -0
  55. data/lib/ledger_sync/adaptors/netsuite/vendor/ledger_serializer.rb +35 -0
  56. data/lib/ledger_sync/adaptors/netsuite/vendor/operations/create.rb +27 -0
  57. data/lib/ledger_sync/adaptors/netsuite/vendor/operations/delete.rb +27 -0
  58. data/lib/ledger_sync/adaptors/netsuite/vendor/operations/find.rb +27 -0
  59. data/lib/ledger_sync/adaptors/netsuite/vendor/operations/update.rb +27 -0
  60. data/lib/ledger_sync/adaptors/netsuite_soap/adaptor.rb +92 -0
  61. data/lib/ledger_sync/adaptors/netsuite_soap/config.rb +5 -0
  62. data/lib/ledger_sync/adaptors/netsuite_soap/customer/ledger_serializer.rb +13 -0
  63. data/lib/ledger_sync/adaptors/netsuite_soap/customer/operations/create.rb +64 -0
  64. data/lib/ledger_sync/adaptors/netsuite_soap/customer/operations/find.rb +49 -0
  65. data/lib/ledger_sync/adaptors/netsuite_soap/ledger_serializer.rb +25 -0
  66. data/lib/ledger_sync/adaptors/netsuite_soap/operation.rb +40 -0
  67. data/lib/ledger_sync/adaptors/netsuite_soap/operation/create.rb +29 -0
  68. data/lib/ledger_sync/adaptors/netsuite_soap/operation/find.rb +15 -0
  69. data/lib/ledger_sync/adaptors/netsuite_soap/subsidiary/operations/create.rb +42 -0
  70. data/lib/ledger_sync/adaptors/netsuite_soap/subsidiary/operations/find.rb +38 -0
  71. data/lib/ledger_sync/adaptors/operation.rb +7 -11
  72. data/lib/ledger_sync/adaptors/quickbooks_online/account/searcher.rb +2 -13
  73. data/lib/ledger_sync/adaptors/quickbooks_online/adaptor.rb +158 -109
  74. data/lib/ledger_sync/adaptors/quickbooks_online/bill/searcher.rb +1 -15
  75. data/lib/ledger_sync/adaptors/quickbooks_online/config.rb +3 -2
  76. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/create.rb +1 -0
  77. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/find.rb +1 -0
  78. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/update.rb +1 -0
  79. data/lib/ledger_sync/adaptors/quickbooks_online/customer/searcher.rb +2 -16
  80. data/lib/ledger_sync/adaptors/quickbooks_online/dashboard_url_helper.rb +57 -0
  81. data/lib/ledger_sync/adaptors/quickbooks_online/department/ledger_serializer.rb +38 -0
  82. data/lib/ledger_sync/adaptors/quickbooks_online/department/operations/create.rb +23 -0
  83. data/lib/ledger_sync/adaptors/quickbooks_online/department/operations/find.rb +23 -0
  84. data/lib/ledger_sync/adaptors/quickbooks_online/department/operations/update.rb +25 -0
  85. data/lib/ledger_sync/adaptors/quickbooks_online/department/searcher.rb +17 -0
  86. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/ledger_serializer.rb +3 -0
  87. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/create.rb +1 -0
  88. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/find.rb +1 -0
  89. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/update.rb +1 -0
  90. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/searcher.rb +0 -15
  91. data/lib/ledger_sync/adaptors/quickbooks_online/expense/ledger_serializer.rb +0 -2
  92. data/lib/ledger_sync/adaptors/quickbooks_online/expense/searcher.rb +1 -15
  93. data/lib/ledger_sync/adaptors/quickbooks_online/invoice/ledger_serializer.rb +35 -0
  94. data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/create.rb +27 -0
  95. data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/find.rb +25 -0
  96. data/lib/ledger_sync/adaptors/quickbooks_online/invoice/operations/update.rb +25 -0
  97. data/lib/ledger_sync/adaptors/quickbooks_online/invoice_sales_line_item/ledger_serializer.rb +30 -0
  98. data/lib/ledger_sync/adaptors/quickbooks_online/item/ledger_serializer.rb +13 -0
  99. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/searcher.rb +1 -15
  100. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_class/ledger_serializer.rb +25 -0
  101. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_class/operations/create.rb +23 -0
  102. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_class/operations/find.rb +23 -0
  103. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_class/operations/update.rb +25 -0
  104. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_class/searcher.rb +15 -0
  105. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer.rb +8 -5
  106. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/entity_reference_type.rb +4 -14
  107. data/lib/ledger_sync/adaptors/quickbooks_online/ledger_serializer_type/transaction_reference_type.rb +49 -0
  108. data/lib/ledger_sync/adaptors/quickbooks_online/oauth_client.rb +93 -0
  109. data/lib/ledger_sync/adaptors/quickbooks_online/operation.rb +42 -7
  110. data/lib/ledger_sync/adaptors/quickbooks_online/operation/create.rb +5 -8
  111. data/lib/ledger_sync/adaptors/quickbooks_online/operation/find.rb +9 -9
  112. data/lib/ledger_sync/adaptors/quickbooks_online/operation/full_update.rb +17 -15
  113. data/lib/ledger_sync/adaptors/quickbooks_online/payment/ledger_serializer.rb +25 -0
  114. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/create.rb +9 -6
  115. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/find.rb +9 -2
  116. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/update.rb +8 -1
  117. data/lib/ledger_sync/adaptors/quickbooks_online/payment_line_item/ledger_serializer.rb +19 -0
  118. data/lib/ledger_sync/adaptors/quickbooks_online/request.rb +98 -0
  119. data/lib/ledger_sync/adaptors/quickbooks_online/searcher.rb +29 -13
  120. data/lib/ledger_sync/adaptors/quickbooks_online/util/operation_error_parser.rb +1 -1
  121. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/create.rb +3 -0
  122. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/find.rb +3 -0
  123. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/update.rb +3 -0
  124. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/searcher.rb +5 -54
  125. data/lib/ledger_sync/adaptors/quickbooks_online/webhook.rb +58 -0
  126. data/lib/ledger_sync/adaptors/quickbooks_online/webhook_event.rb +79 -0
  127. data/lib/ledger_sync/adaptors/quickbooks_online/webhook_notification.rb +42 -0
  128. data/lib/ledger_sync/adaptors/request.rb +53 -0
  129. data/lib/ledger_sync/adaptors/response.rb +55 -0
  130. data/lib/ledger_sync/adaptors/searcher.rb +3 -1
  131. data/lib/ledger_sync/adaptors/stripe/adaptor.rb +45 -0
  132. data/lib/ledger_sync/adaptors/stripe/config.rb +6 -0
  133. data/lib/ledger_sync/adaptors/stripe/customer/operations/create.rb +44 -0
  134. data/lib/ledger_sync/adaptors/stripe/customer/operations/delete.rb +35 -0
  135. data/lib/ledger_sync/adaptors/stripe/customer/operations/find.rb +44 -0
  136. data/lib/ledger_sync/adaptors/stripe/customer/operations/update.rb +43 -0
  137. data/lib/ledger_sync/adaptors/stripe/operation.rb +35 -0
  138. data/lib/ledger_sync/adaptors/stripe/operation/create.rb +17 -0
  139. data/lib/ledger_sync/adaptors/stripe/operation/delete.rb +17 -0
  140. data/lib/ledger_sync/adaptors/stripe/operation/find.rb +15 -0
  141. data/lib/ledger_sync/adaptors/stripe/operation/update.rb +17 -0
  142. data/lib/ledger_sync/adaptors/test/config.rb +0 -1
  143. data/lib/ledger_sync/adaptors/test/customer/operations/create.rb +1 -0
  144. data/lib/ledger_sync/adaptors/test/customer/operations/find.rb +1 -0
  145. data/lib/ledger_sync/adaptors/test/customer/operations/invalid.rb +1 -0
  146. data/lib/ledger_sync/adaptors/test/customer/operations/update.rb +1 -0
  147. data/lib/ledger_sync/adaptors/test/customer/operations/valid.rb +1 -0
  148. data/lib/ledger_sync/adaptors/test/payment/operations/create.rb +8 -1
  149. data/lib/ledger_sync/adaptors/test/payment/operations/find.rb +9 -2
  150. data/lib/ledger_sync/adaptors/test/payment/operations/update.rb +9 -5
  151. data/lib/ledger_sync/adaptors/test/vendor/operations/create.rb +5 -0
  152. data/lib/ledger_sync/adaptors/test/vendor/operations/find.rb +3 -0
  153. data/lib/ledger_sync/adaptors/test/vendor/operations/invalid.rb +5 -0
  154. data/lib/ledger_sync/adaptors/test/vendor/operations/update.rb +5 -0
  155. data/lib/ledger_sync/adaptors/test/vendor/operations/valid.rb +5 -0
  156. data/lib/ledger_sync/error/adaptor_errors.rb +18 -5
  157. data/lib/ledger_sync/error/operation_errors.rb +13 -4
  158. data/lib/ledger_sync/resource.rb +6 -0
  159. data/lib/ledger_sync/resource_attribute/dirty_mixin.rb +16 -15
  160. data/lib/ledger_sync/resources/bill.rb +7 -1
  161. data/lib/ledger_sync/resources/bill_line_item.rb +2 -0
  162. data/lib/ledger_sync/resources/currency.rb +9 -0
  163. data/lib/ledger_sync/resources/customer.rb +16 -0
  164. data/lib/ledger_sync/resources/department.rb +14 -0
  165. data/lib/ledger_sync/resources/deposit.rb +7 -0
  166. data/lib/ledger_sync/resources/deposit_line_item.rb +6 -0
  167. data/lib/ledger_sync/resources/expense.rb +5 -0
  168. data/lib/ledger_sync/resources/expense_line_item.rb +2 -0
  169. data/lib/ledger_sync/resources/invoice.rb +23 -0
  170. data/lib/ledger_sync/resources/invoice_sales_line_item.rb +17 -0
  171. data/lib/ledger_sync/resources/item.rb +5 -0
  172. data/lib/ledger_sync/resources/journal_entry.rb +5 -1
  173. data/lib/ledger_sync/resources/journal_entry_line_item.rb +4 -0
  174. data/lib/ledger_sync/resources/ledger_class.rb +14 -0
  175. data/lib/ledger_sync/resources/payment.rb +13 -0
  176. data/lib/ledger_sync/resources/payment_line_item.rb +15 -0
  177. data/lib/ledger_sync/resources/subsidiary.rb +8 -0
  178. data/lib/ledger_sync/resources/transfer.rb +4 -0
  179. data/lib/ledger_sync/resources/vendor.rb +9 -2
  180. data/lib/ledger_sync/type/reference_many.rb +12 -1
  181. data/lib/ledger_sync/type/reference_one.rb +1 -1
  182. data/lib/ledger_sync/util/resonad.rb +18 -0
  183. data/lib/ledger_sync/util/signer.rb +36 -0
  184. data/lib/ledger_sync/util/string_helpers.rb +12 -12
  185. data/lib/ledger_sync/version.rb +1 -1
  186. data/qa/env_spec.rb +5 -0
  187. data/qa/netsuite/account_spec.rb +20 -0
  188. data/qa/netsuite/currency_spec.rb +16 -0
  189. data/qa/netsuite/customer.rb +76 -0
  190. data/qa/netsuite/customer_spec.rb +21 -0
  191. data/qa/netsuite/vendor.rb +76 -0
  192. data/qa/netsuite/vendor_spec.rb +23 -0
  193. data/qa/netsuite_soap/customer_spec.rb +21 -0
  194. data/qa/netsuite_soap/subsidiary_spec.rb +0 -0
  195. data/qa/netsuite_test.rb +42 -0
  196. data/qa/qa_helper.rb +58 -0
  197. data/qa/quickbooks_online/account_spec.rb +14 -0
  198. data/qa/quickbooks_online/customer_spec.rb +13 -0
  199. data/qa/quickbooks_online/expense_spec.rb +31 -0
  200. data/qa/quickbooks_online/vendor_spec.rb +13 -0
  201. data/qa/stripe/customers_spec.rb +15 -0
  202. data/qa/support/adaptor_helpers.rb +99 -0
  203. data/qa/support/adaptor_support_setup.rb +23 -0
  204. data/qa/support/netsuite_helpers.rb +33 -0
  205. data/qa/support/netsuite_shared_examples.rb +30 -0
  206. data/qa/support/netsuite_soap_helpers.rb +33 -0
  207. data/qa/support/netsuite_soap_shared_examples.rb +10 -0
  208. data/qa/support/quickbooks_online_helpers.rb +42 -0
  209. data/qa/support/quickbooks_online_shared_examples.rb +82 -0
  210. data/qa/support/shared_examples.rb +127 -0
  211. data/qa/support/stripe_helpers.rb +22 -0
  212. data/qa/support/stripe_shared_examples.rb +10 -0
  213. data/qa/test.rb +67 -0
  214. data/release.sh +13 -1
  215. metadata +189 -3
  216. data/qa.rb +0 -142
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ module Adaptors
5
+ module NetSuiteSOAP
6
+ module Subsidiary
7
+ module Operations
8
+ class Create < NetSuiteSOAP::Operation::Create
9
+ class Contract < LedgerSync::Adaptors::Contract
10
+ params do
11
+ required(:external_id).filled(:string)
12
+ required(:ledger_id).value(:nil)
13
+ required(:name).filled(:string)
14
+ required(:state).filled(:string)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def operate
21
+ netsuite_resource = ::NetSuite::Records::Subsidiary.new(
22
+ external_id: resource.external_id,
23
+ name: resource.name,
24
+ state: resource.state
25
+ )
26
+
27
+ return netsuite_failure(netsuite_resource: netsuite_resource) unless netsuite_resource.add
28
+
29
+ resource.name = netsuite_resource.name
30
+ resource.ledger_id = netsuite_resource.internal_id
31
+
32
+ success(
33
+ resource: resource,
34
+ response: netsuite_resource
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ module Adaptors
5
+ module NetSuiteSOAP
6
+ module Subsidiary
7
+ module Operations
8
+ class Find < NetSuiteSOAP::Operation::Find
9
+ class Contract < LedgerSync::Adaptors::Contract
10
+ params do
11
+ required(:external_id).maybe(:string)
12
+ required(:ledger_id).filled(:string)
13
+ required(:name).maybe(:string)
14
+ required(:state).maybe(:string)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def operate
21
+ netsuite_resource = ::NetSuite::Records::Subsidiary.get(
22
+ internal_id: resource.ledger_id
23
+ )
24
+
25
+ resource.name = netsuite_resource.name
26
+ resource.ledger_id = netsuite_resource.internal_id
27
+
28
+ success(
29
+ resource: resource,
30
+ response: netsuite_resource
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -3,7 +3,7 @@
3
3
  module LedgerSync
4
4
  module Adaptors
5
5
  module Operation
6
- TYPES = %i[create find update].freeze
6
+ TYPES = %i[create delete find update].freeze
7
7
 
8
8
  module Mixin
9
9
  module ClassMethods
@@ -11,6 +11,10 @@ module LedgerSync
11
11
  @adaptor_klass ||= Class.const_get("#{name.split('::')[0..2].join('::')}::Adaptor")
12
12
  end
13
13
 
14
+ def operations_module
15
+ @operations_module ||= Object.const_get(name.split('::Operations::').first + '::Operations')
16
+ end
17
+
14
18
  def resource_klass
15
19
  @resource_klass ||= LedgerSync.const_get(
16
20
  name
@@ -25,7 +29,7 @@ module LedgerSync
25
29
  def self.included(base)
26
30
  base.include SimplySerializable::Mixin
27
31
  base.include Fingerprintable::Mixin
28
- # base.include Validatable
32
+ base.include Adaptors::Mixins::InferLedgerSerializerMixin
29
33
  base.extend ClassMethods
30
34
 
31
35
  base.class_eval do
@@ -107,10 +111,7 @@ module LedgerSync
107
111
  end
108
112
 
109
113
  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
+ self.class.inferred_ledger_serializer(resource: resource)
114
115
  end
115
116
 
116
117
  # Results
@@ -182,11 +183,6 @@ module LedgerSync
182
183
  define_method "#{type.to_s.downcase}?" do
183
184
  false
184
185
  end
185
-
186
- define_method "convert_to_#{type.to_s.downcase}" do
187
- update_klass_name = self.class.name.split('::')[0..-2].append(LedgerSync::Util::StringHelpers.camelcase(type.to_s)).join('::')
188
- Object.const_get(update_klass_name).new(adaptor: adaptor, resource: resource)
189
- end
190
186
  end
191
187
 
192
188
  private
@@ -5,19 +5,8 @@ module LedgerSync
5
5
  module QuickBooksOnline
6
6
  module Account
7
7
  class Searcher < QuickBooksOnline::Searcher
8
- def resources
9
- @resources ||= begin
10
- adaptor
11
- .query(
12
- resource: 'account',
13
- query: "Name LIKE '%#{query}%'",
14
- limit: limit,
15
- offset: offset
16
- )
17
- .map do |c|
18
- LedgerSerializer.new(resource: LedgerSync::Account.new).deserialize(hash: c)
19
- end
20
- end
8
+ def query_string
9
+ "Name LIKE '%#{query}%'"
21
10
  end
22
11
  end
23
12
  end
@@ -1,17 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'oauth2'
3
+ require_relative 'oauth_client'
4
+ require_relative 'dashboard_url_helper'
4
5
 
5
6
  module LedgerSync
6
7
  module Adaptors
7
8
  module QuickBooksOnline
8
9
  class Adaptor < LedgerSync::Adaptors::Adaptor
9
- AUTHORIZE_URL = 'https://appcenter.intuit.com/connect/oauth2'
10
10
  OAUTH_HEADERS = { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }.freeze
11
11
  ROOT_URI = 'https://quickbooks.api.intuit.com'
12
+ REVOKE_TOKEN_URI = 'https://developer.api.intuit.com/v2/oauth2/tokens/revoke'
12
13
  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'
14
+ API_RESOURCE_MAPPING_OVERRIDE = {
15
+ LedgerSync::Expense => 'purchase',
16
+ LedgerSync::LedgerClass => 'class'
17
+ }
18
+ API_RESOURCE_MAPPING_OVERRIDE_REVERSE = API_RESOURCE_MAPPING_OVERRIDE.invert
15
19
 
16
20
  attr_reader :access_token,
17
21
  :client_id,
@@ -23,7 +27,8 @@ module LedgerSync
23
27
  :refresh_token,
24
28
  :refresh_token_expires_at,
25
29
  :root_uri,
26
- :test
30
+ :test,
31
+ :update_dotenv
27
32
 
28
33
  def initialize(
29
34
  access_token:,
@@ -31,167 +36,209 @@ module LedgerSync
31
36
  client_secret:,
32
37
  realm_id:,
33
38
  refresh_token:,
34
- test: false
39
+ test: false,
40
+ update_dotenv: true
35
41
  )
36
42
  @access_token = access_token
37
43
  @client_id = client_id
38
44
  @client_secret = client_secret
39
- @previous_access_tokens = []
40
- @previous_refresh_tokens = []
41
45
  @realm_id = realm_id
42
46
  @refresh_token = refresh_token
43
47
  @test = test
48
+ @update_dotenv = update_dotenv
49
+
50
+ @previous_access_tokens = []
51
+ @previous_refresh_tokens = []
44
52
 
45
53
  @root_uri = (test ? ROOT_SANDBOX_URI : ROOT_URI)
54
+
55
+ update_secrets_in_dotenv if update_dotenv
46
56
  end
47
57
 
48
58
  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
- )
59
+ oauth_client.authorization_url(redirect_uri: redirect_uri)
55
60
  end
56
61
 
57
- def find(resource:, id:)
58
- resource = resource.to_s
59
- url = "#{oauth_base_uri}/#{resourcify(resource)}/#{id}"
62
+ def find(path:)
63
+ url = "#{oauth_base_uri}/#{path}"
60
64
 
61
- response = request(:get, url, headers: OAUTH_HEADERS.dup)
62
- JSON.parse(response.body).dig(resource.classify)
65
+ request(
66
+ headers: OAUTH_HEADERS.dup,
67
+ method: :get,
68
+ url: url
69
+ )
63
70
  end
64
71
 
65
- def parse_operation_error(error:, operation:)
66
- return nil unless error.is_a?(OAuth2::Error)
72
+ def oauth
73
+ OAuth2::AccessToken.new(
74
+ oauth_client.client,
75
+ access_token,
76
+ refresh_token: refresh_token
77
+ )
78
+ end
67
79
 
68
- Util::OperationErrorParser.new(
69
- error: error,
70
- operation: operation
71
- ).parse
80
+ def oauth_client
81
+ @oauth_client ||= OAuthClient.new(
82
+ client_id: client_id,
83
+ client_secret: client_secret
84
+ )
72
85
  end
73
86
 
74
- def post(resource:, payload:)
75
- resource = resource.to_s
76
- url = "#{oauth_base_uri}/#{resourcify(resource)}"
87
+ def post(path:, payload:)
88
+ url = "#{oauth_base_uri}/#{path}"
77
89
 
78
- response = request(:post, url, headers: OAUTH_HEADERS.dup, body: payload.to_json)
79
- JSON.parse(response.body).dig(resource.classify)
90
+ request(
91
+ headers: OAUTH_HEADERS.dup,
92
+ method: :post,
93
+ body: payload.to_json,
94
+ url: url
95
+ )
80
96
  end
81
97
 
82
- def query(resource:, query:, limit: 10, offset: 1)
83
- resource = resource.to_s
84
- full_query = "SELECT * FROM #{resource.classify} WHERE #{query} STARTPOSITION #{offset} MAXRESULTS #{limit}"
98
+ def query(limit: 10, offset: 1, query:, resource_class:)
99
+ ledger_resource_type = self.class.ledger_resource_type_for(
100
+ resource_class: resource_class
101
+ ).classify
102
+ full_query = "SELECT * FROM #{ledger_resource_type} WHERE #{query} STARTPOSITION #{offset} MAXRESULTS #{limit}"
85
103
  url = "#{oauth_base_uri}/query?query=#{CGI.escape(full_query)}"
86
104
 
87
- response = request(:get, url, headers: OAUTH_HEADERS.dup)
88
- JSON.parse(response.body).dig('QueryResponse', resource.classify) || []
105
+ request(
106
+ headers: OAUTH_HEADERS.dup,
107
+ method: :get,
108
+ url: url
109
+ )
89
110
  end
90
111
 
91
112
  def refresh!
92
- set_credentials_from_oauth_token(token: oauth.refresh!)
93
-
113
+ set_credentials_from_oauth_token(
114
+ token: Request.new(
115
+ adaptor: self
116
+ ).refresh!
117
+ )
94
118
  self
95
- rescue OAuth2::Error => e
96
- raise parse_error(error: e)
97
119
  end
98
120
 
99
- def set_credentials_from_oauth_code(code:, redirect_uri:)
100
- oauth_token = oauth_client.auth_code.get_token(
101
- code,
121
+ def revoke_token!
122
+ headers = OAUTH_HEADERS.dup.merge('Authorization' => 'Basic ' + Base64.strict_encode64(client_id + ':' + client_secret))
123
+ LedgerSync::Adaptors::Request.new(
124
+ body: {
125
+ token: access_token
126
+ },
127
+ headers: headers,
128
+ method: :post,
129
+ url: REVOKE_TOKEN_URI
130
+ ).perform.status == 200
131
+ end
132
+
133
+ def set_credentials_from_oauth_code(code:, realm_id: nil, redirect_uri:)
134
+ oauth_token = oauth_client.get_token(
135
+ code: code,
102
136
  redirect_uri: redirect_uri
103
137
  )
104
138
 
105
- set_credentials_from_oauth_token(token: oauth_token)
139
+ set_credentials_from_oauth_token(
140
+ realm_id: realm_id,
141
+ token: oauth_token
142
+ )
106
143
 
107
144
  oauth_token
108
145
  end
109
146
 
147
+ def update_secrets_in_dotenv
148
+ return if ENV['TEST_ENV'] && !ENV['USE_DOTENV_ADAPTOR_SECRETS']
149
+
150
+ filename = File.join(LedgerSync.root, '.env')
151
+ return unless File.exist?(filename)
152
+
153
+ prefix = 'QUICKBOOKS_ONLINE_'
154
+
155
+ Tempfile.open(".#{File.basename(filename)}", File.dirname(filename)) do |tempfile|
156
+ File.open(filename).each do |line|
157
+ env_key = line.split('=').first
158
+ adaptor_method = env_key.split(prefix).last.downcase
159
+
160
+ if line =~ /\A#{prefix}/ && respond_to?(adaptor_method)
161
+ env_value = ENV[env_key]
162
+ new_value = send(adaptor_method)
163
+ tempfile.puts "#{env_key}=#{new_value}"
164
+ next if env_value == new_value
165
+
166
+ ENV[env_key] = new_value
167
+ tempfile.puts "# #{env_key}=#{env_value} # Updated on #{Time.now}"
168
+ else
169
+ tempfile.puts line
170
+ end
171
+ end
172
+ tempfile.close
173
+ FileUtils.mv tempfile.path, filename
174
+ end
175
+
176
+ Dotenv.load
177
+ end
178
+
110
179
  def url_for(resource:)
111
- base_url = test ? 'https://app.sandbox.qbo.intuit.com/app' : 'https://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
180
+ DashboardURLHelper.new(resource: resource, test: test).url
134
181
  end
135
182
 
136
183
  def self.ledger_attributes_to_save
137
184
  %i[access_token expires_at refresh_token refresh_token_expires_at]
138
185
  end
139
186
 
140
- private
141
-
142
- def oauth_base_uri
143
- @oauth_base_uri ||= "#{root_uri}/v3/company/#{realm_id}"
144
- end
145
-
146
- def oauth(force: false)
147
- if @oauth.nil? || force
148
- @oauth = OAuth2::AccessToken.new(
149
- oauth_client,
150
- access_token,
151
- refresh_token: refresh_token
152
- )
153
- end
154
-
155
- @oauth
187
+ def self.ledger_resource_type_for(resource_class:)
188
+ API_RESOURCE_MAPPING_OVERRIDE[resource_class] || resource_class.resource_type.to_s
156
189
  end
157
190
 
158
- def oauth_client
159
- @oauth_client ||= OAuth2::Client.new(
160
- client_id,
161
- client_secret,
162
- authorize_url: AUTHORIZE_URL,
163
- site: SITE_URL,
164
- token_url: TOKEN_URL
191
+ def self.new_from_env(**override)
192
+ new(
193
+ {
194
+ access_token: ENV.fetch('QUICKBOOKS_ONLINE_ACCESS_TOKEN'),
195
+ client_id: ENV.fetch('QUICKBOOKS_ONLINE_CLIENT_ID'),
196
+ client_secret: ENV.fetch('QUICKBOOKS_ONLINE_CLIENT_SECRET'),
197
+ realm_id: ENV.fetch('QUICKBOOKS_ONLINE_REALM_ID'),
198
+ refresh_token: ENV.fetch('QUICKBOOKS_ONLINE_REFRESH_TOKEN')
199
+ }.merge(override)
165
200
  )
166
201
  end
167
202
 
168
- def request(method, *args)
169
- oauth.send(method, *args)
170
- rescue OAuth2::Error => e
171
- error = parse_error(error: e) || parse_operation_error(error: e, operation: nil) || e
203
+ def self.new_from_oauth_client_uri(oauth_client:, uri:, **override)
204
+ parsed_uri = OAuthClient::RedirectURIParser.new(uri: uri)
205
+ oauth_token = oauth_client.get_token(
206
+ code: parsed_uri.code,
207
+ redirect_uri: parsed_uri.redirect_uri
208
+ )
172
209
 
173
- raise error unless error.is_a?(Error::AdaptorError::AuthenticationError)
210
+ new(
211
+ {
212
+ access_token: oauth_token.token,
213
+ client_id: oauth_client.client_id,
214
+ client_secret: oauth_client.client_secret,
215
+ realm_id: parsed_uri.realm_id,
216
+ refresh_token: oauth_token.refresh_token
217
+ }.merge(override)
218
+ )
219
+ end
174
220
 
175
- begin
176
- refresh!
177
- oauth.send(method, *args)
178
- rescue OAuth2::Error => e
179
- raise parse_error(error: e) || e
180
- end
221
+ def self.resource_from_ledger_type(type:)
222
+ API_RESOURCE_MAPPING_OVERRIDE_REVERSE[type.downcase] || LedgerSync.resources[type.downcase.to_sym]
181
223
  end
182
224
 
183
- def parse_error(error:)
184
- Util::AdaptorErrorParser.new(
185
- error: error,
186
- adaptor: self
187
- ).parse
225
+ private
226
+
227
+ def oauth_base_uri
228
+ @oauth_base_uri ||= "#{root_uri}/v3/company/#{realm_id}"
188
229
  end
189
230
 
190
- def resourcify(str)
191
- str.tr('_', '')
231
+ def request(body: nil, headers: {}, method:, url:)
232
+ Request.new(
233
+ adaptor: self,
234
+ body: body,
235
+ headers: headers,
236
+ method: method,
237
+ url: url
238
+ ).perform
192
239
  end
193
240
 
194
- def set_credentials_from_oauth_token(token:)
241
+ def set_credentials_from_oauth_token(realm_id: nil, token:)
195
242
  @previous_access_tokens << access_token if access_token.present?
196
243
  @access_token = token.token
197
244
 
@@ -201,7 +248,9 @@ module LedgerSync
201
248
  @previous_refresh_tokens << refresh_token if refresh_token.present?
202
249
  @refresh_token = token.refresh_token
203
250
 
204
- oauth(force: true) # Ensure we update the memoized @oauth
251
+ @realm_id = realm_id unless realm_id.nil?
252
+ ensure
253
+ update_secrets_in_dotenv if update_dotenv
205
254
  end
206
255
  end
207
256
  end