quickbooks 0.4.0 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (293) hide show
  1. data.tar.gz.sig +0 -0
  2. data/History.txt +23 -0
  3. data/License.txt +54 -0
  4. data/Manifest.txt +73 -0
  5. data/README.txt +79 -0
  6. data/Rakefile +17 -78
  7. data/lib/quickbooks.rb +153 -7
  8. data/lib/quickbooks/adaptability.rb +67 -0
  9. data/lib/quickbooks/adapters/http_adapter.rb +94 -0
  10. data/lib/quickbooks/adapters/https_adapter.rb +57 -0
  11. data/lib/quickbooks/adapters/ole_adapter.rb +141 -110
  12. data/lib/quickbooks/adapters/spew_adapter.rb +39 -0
  13. data/lib/quickbooks/adapters/tcp_adapter.rb +71 -0
  14. data/lib/quickbooks/adapters/test_adapter.rb +62 -0
  15. data/lib/quickbooks/element.rb +385 -0
  16. data/lib/quickbooks/element_collection.rb +105 -0
  17. data/lib/quickbooks/extlib.rb +8 -0
  18. data/lib/quickbooks/extlib/assertions.rb +8 -0
  19. data/lib/quickbooks/extlib/class.rb +98 -0
  20. data/lib/quickbooks/extlib/days_and_times.rb +10 -0
  21. data/lib/quickbooks/extlib/days_and_times/duration.rb +362 -0
  22. data/lib/quickbooks/extlib/days_and_times/numeric.rb +48 -0
  23. data/lib/quickbooks/extlib/days_and_times/object.rb +19 -0
  24. data/lib/quickbooks/extlib/days_and_times/time.rb +137 -0
  25. data/lib/quickbooks/extlib/hash.rb +327 -0
  26. data/lib/quickbooks/extlib/hook.rb +366 -0
  27. data/lib/quickbooks/extlib/inflection.rb +436 -0
  28. data/lib/quickbooks/extlib/logger.rb +202 -0
  29. data/lib/quickbooks/extlib/object.rb +162 -0
  30. data/lib/quickbooks/extlib/pathname.rb +15 -0
  31. data/lib/quickbooks/extlib/rubygems.rb +38 -0
  32. data/lib/quickbooks/extlib/string.rb +32 -0
  33. data/lib/quickbooks/extlib/time.rb +41 -0
  34. data/lib/quickbooks/model.rb +503 -121
  35. data/lib/quickbooks/option.rb +95 -0
  36. data/lib/quickbooks/property.rb +69 -0
  37. data/lib/quickbooks/ruby_ext.rb +251 -0
  38. data/lib/quickbooks/type.rb +75 -0
  39. data/lib/quickbooks/types.rb +58 -0
  40. data/lib/quickbooks/version.rb +10 -0
  41. data/lib/quickbooks/xsd.rb +243 -0
  42. data/lib/quickbooks/xsd/attribute.rb +57 -0
  43. data/lib/quickbooks/xsd/choice.rb +94 -0
  44. data/lib/quickbooks/xsd/complex_type.rb +77 -0
  45. data/lib/quickbooks/xsd/element.rb +214 -0
  46. data/lib/quickbooks/xsd/group.rb +94 -0
  47. data/lib/quickbooks/xsd/restriction.rb +51 -0
  48. data/lib/quickbooks/xsd/sequence.rb +85 -0
  49. data/lib/quickbooks/xsd/simple_type.rb +87 -0
  50. data/lib/quickbooks/xsd/union.rb +70 -0
  51. data/lib/quickbooks/xsd/validation.rb +89 -0
  52. data/lib/quickbooks/xsd/xml_parse.rb +80 -0
  53. data/quickbooks.gemspec +36 -0
  54. data/source/qbxml21_demo.xsd +2223 -0
  55. data/source/qbxmlops_demo.xsd +483 -0
  56. data/source/qbxmltypes.xsd +160 -0
  57. data/spec/association_spec.rb +5 -0
  58. data/spec/element_collection_spec.rb +8 -0
  59. data/spec/find_spec.rb +30 -0
  60. data/spec/lib/quickbooks/element_spec.rb +83 -0
  61. data/spec/lib/quickbooks/elements/sales_order_add_spec.rb +14 -0
  62. data/spec/lib/quickbooks/elements/vendor_add_spec.rb +78 -0
  63. data/spec/lib/quickbooks/model_spec.rb +89 -0
  64. data/spec/lib/quickbooks/models/customer_spec.rb +52 -0
  65. data/spec/lib/quickbooks/models/invoice_spec.rb +12 -0
  66. data/spec/lib/quickbooks/models/sales_order_line_spec.rb +0 -0
  67. data/spec/lib/quickbooks/models/sales_order_spec.rb +145 -0
  68. data/spec/lib/quickbooks/xsd/choice_spec.rb +18 -0
  69. data/spec/pending_spec.rb +11 -0
  70. data/spec/quickbooks_spec.rb +38 -0
  71. data/spec/repeatable_spec.rb +20 -0
  72. data/spec/requires_spec.rb +10 -0
  73. data/spec/spec_helper.rb +5 -0
  74. data/spec/validation_spec.rb +24 -0
  75. metadata +133 -285
  76. metadata.gz.sig +0 -0
  77. data/LICENSE +0 -16
  78. data/README +0 -49
  79. data/doc/classes/Array.html +0 -368
  80. data/doc/classes/Array.src/M000026.html +0 -18
  81. data/doc/classes/Array.src/M000027.html +0 -21
  82. data/doc/classes/Array.src/M000028.html +0 -18
  83. data/doc/classes/Array.src/M000029.html +0 -19
  84. data/doc/classes/Array.src/M000030.html +0 -18
  85. data/doc/classes/Array.src/M000031.html +0 -23
  86. data/doc/classes/Array.src/M000032.html +0 -18
  87. data/doc/classes/Array.src/M000033.html +0 -18
  88. data/doc/classes/Array.src/M000034.html +0 -18
  89. data/doc/classes/Array.src/M000035.html +0 -19
  90. data/doc/classes/Array.src/M000036.html +0 -18
  91. data/doc/classes/Array.src/M000037.html +0 -23
  92. data/doc/classes/Array.src/M000038.html +0 -18
  93. data/doc/classes/Array.src/M000039.html +0 -21
  94. data/doc/classes/Array.src/M000040.html +0 -18
  95. data/doc/classes/Array.src/M000041.html +0 -18
  96. data/doc/classes/Class.html +0 -171
  97. data/doc/classes/Class.src/M000042.html +0 -18
  98. data/doc/classes/Class.src/M000043.html +0 -18
  99. data/doc/classes/Hash.html +0 -516
  100. data/doc/classes/Hash.src/M000001.html +0 -18
  101. data/doc/classes/Hash.src/M000002.html +0 -21
  102. data/doc/classes/Hash.src/M000003.html +0 -18
  103. data/doc/classes/Hash.src/M000004.html +0 -21
  104. data/doc/classes/Hash.src/M000005.html +0 -18
  105. data/doc/classes/Hash.src/M000006.html +0 -20
  106. data/doc/classes/Hash.src/M000007.html +0 -18
  107. data/doc/classes/Hash.src/M000008.html +0 -22
  108. data/doc/classes/Hash.src/M000009.html +0 -18
  109. data/doc/classes/Hash.src/M000010.html +0 -21
  110. data/doc/classes/Hash.src/M000011.html +0 -24
  111. data/doc/classes/Hash.src/M000012.html +0 -18
  112. data/doc/classes/Hash.src/M000013.html +0 -21
  113. data/doc/classes/Hash.src/M000014.html +0 -18
  114. data/doc/classes/Hash.src/M000015.html +0 -21
  115. data/doc/classes/Hash.src/M000016.html +0 -18
  116. data/doc/classes/Hash.src/M000017.html +0 -21
  117. data/doc/classes/Hash.src/M000018.html +0 -27
  118. data/doc/classes/Hash.src/M000019.html +0 -20
  119. data/doc/classes/Hash.src/M000020.html +0 -18
  120. data/doc/classes/Hash.src/M000021.html +0 -18
  121. data/doc/classes/Hash.src/M000022.html +0 -18
  122. data/doc/classes/Hash.src/M000023.html +0 -18
  123. data/doc/classes/Hash.src/M000024.html +0 -18
  124. data/doc/classes/Hash.src/M000025.html +0 -28
  125. data/doc/classes/Object.html +0 -278
  126. data/doc/classes/Object.src/M000044.html +0 -24
  127. data/doc/classes/Object.src/M000045.html +0 -19
  128. data/doc/classes/Object.src/M000046.html +0 -24
  129. data/doc/classes/Object.src/M000047.html +0 -18
  130. data/doc/classes/Object.src/M000048.html +0 -19
  131. data/doc/classes/Object.src/M000049.html +0 -18
  132. data/doc/classes/Object.src/M000050.html +0 -18
  133. data/doc/classes/Object.src/M000051.html +0 -18
  134. data/doc/classes/Qbxml.html +0 -131
  135. data/doc/classes/Qbxml/Request.html +0 -224
  136. data/doc/classes/Qbxml/Request.src/M000132.html +0 -66
  137. data/doc/classes/Qbxml/Request.src/M000133.html +0 -19
  138. data/doc/classes/Qbxml/Request.src/M000134.html +0 -83
  139. data/doc/classes/Qbxml/RequestSet.html +0 -199
  140. data/doc/classes/Qbxml/RequestSet.src/M000135.html +0 -22
  141. data/doc/classes/Qbxml/RequestSet.src/M000136.html +0 -26
  142. data/doc/classes/Qbxml/RequestSet.src/M000137.html +0 -18
  143. data/doc/classes/Qbxml/RequestSet.src/M000138.html +0 -22
  144. data/doc/classes/Qbxml/Response.html +0 -302
  145. data/doc/classes/Qbxml/Response.src/M000123.html +0 -24
  146. data/doc/classes/Qbxml/Response.src/M000124.html +0 -19
  147. data/doc/classes/Qbxml/Response.src/M000125.html +0 -55
  148. data/doc/classes/Qbxml/Response.src/M000126.html +0 -18
  149. data/doc/classes/Qbxml/Response.src/M000127.html +0 -18
  150. data/doc/classes/Qbxml/Response.src/M000128.html +0 -18
  151. data/doc/classes/Qbxml/Response.src/M000129.html +0 -18
  152. data/doc/classes/Qbxml/Response.src/M000130.html +0 -18
  153. data/doc/classes/Qbxml/Response.src/M000131.html +0 -63
  154. data/doc/classes/Qbxml/ResponseSet.html +0 -244
  155. data/doc/classes/Qbxml/ResponseSet.src/M000116.html +0 -22
  156. data/doc/classes/Qbxml/ResponseSet.src/M000117.html +0 -26
  157. data/doc/classes/Qbxml/ResponseSet.src/M000118.html +0 -24
  158. data/doc/classes/Qbxml/ResponseSet.src/M000119.html +0 -20
  159. data/doc/classes/Qbxml/ResponseSet.src/M000120.html +0 -27
  160. data/doc/classes/Qbxml/ResponseSet.src/M000121.html +0 -18
  161. data/doc/classes/Qbxml/ResponseSet.src/M000122.html +0 -18
  162. data/doc/classes/Quickbooks.html +0 -196
  163. data/doc/classes/Quickbooks/Address.html +0 -139
  164. data/doc/classes/Quickbooks/Address.src/M000115.html +0 -19
  165. data/doc/classes/Quickbooks/Base.html +0 -567
  166. data/doc/classes/Quickbooks/Base.src/M000095.html +0 -18
  167. data/doc/classes/Quickbooks/Base.src/M000096.html +0 -20
  168. data/doc/classes/Quickbooks/Base.src/M000097.html +0 -19
  169. data/doc/classes/Quickbooks/Base.src/M000098.html +0 -18
  170. data/doc/classes/Quickbooks/Base.src/M000099.html +0 -19
  171. data/doc/classes/Quickbooks/Base.src/M000100.html +0 -30
  172. data/doc/classes/Quickbooks/Base.src/M000101.html +0 -22
  173. data/doc/classes/Quickbooks/Base.src/M000102.html +0 -30
  174. data/doc/classes/Quickbooks/Base.src/M000103.html +0 -19
  175. data/doc/classes/Quickbooks/Base.src/M000104.html +0 -19
  176. data/doc/classes/Quickbooks/Base.src/M000105.html +0 -18
  177. data/doc/classes/Quickbooks/Base.src/M000106.html +0 -18
  178. data/doc/classes/Quickbooks/Base.src/M000107.html +0 -19
  179. data/doc/classes/Quickbooks/Base.src/M000108.html +0 -18
  180. data/doc/classes/Quickbooks/Base.src/M000109.html +0 -45
  181. data/doc/classes/Quickbooks/Base.src/M000110.html +0 -18
  182. data/doc/classes/Quickbooks/Base.src/M000111.html +0 -18
  183. data/doc/classes/Quickbooks/Base.src/M000112.html +0 -20
  184. data/doc/classes/Quickbooks/BillAddress.html +0 -113
  185. data/doc/classes/Quickbooks/CreditCardInfo.html +0 -113
  186. data/doc/classes/Quickbooks/Customer.html +0 -113
  187. data/doc/classes/Quickbooks/CustomerTypeRef.html +0 -113
  188. data/doc/classes/Quickbooks/Deleted.html +0 -113
  189. data/doc/classes/Quickbooks/ItemSalesTaxRef.html +0 -113
  190. data/doc/classes/Quickbooks/JobTypeRef.html +0 -113
  191. data/doc/classes/Quickbooks/ListDeleted.html +0 -139
  192. data/doc/classes/Quickbooks/ListDeleted.src/M000114.html +0 -18
  193. data/doc/classes/Quickbooks/ListItem.html +0 -170
  194. data/doc/classes/Quickbooks/ListItem.src/M000093.html +0 -20
  195. data/doc/classes/Quickbooks/ListItem.src/M000094.html +0 -18
  196. data/doc/classes/Quickbooks/Model.html +0 -462
  197. data/doc/classes/Quickbooks/Model.src/M000073.html +0 -24
  198. data/doc/classes/Quickbooks/Model.src/M000074.html +0 -19
  199. data/doc/classes/Quickbooks/Model.src/M000075.html +0 -18
  200. data/doc/classes/Quickbooks/Model.src/M000076.html +0 -18
  201. data/doc/classes/Quickbooks/Model.src/M000077.html +0 -18
  202. data/doc/classes/Quickbooks/Model.src/M000078.html +0 -22
  203. data/doc/classes/Quickbooks/Model.src/M000079.html +0 -32
  204. data/doc/classes/Quickbooks/Model.src/M000080.html +0 -32
  205. data/doc/classes/Quickbooks/Model.src/M000081.html +0 -18
  206. data/doc/classes/Quickbooks/Model.src/M000082.html +0 -18
  207. data/doc/classes/Quickbooks/Model.src/M000083.html +0 -22
  208. data/doc/classes/Quickbooks/Model.src/M000084.html +0 -23
  209. data/doc/classes/Quickbooks/Model.src/M000085.html +0 -18
  210. data/doc/classes/Quickbooks/Model.src/M000086.html +0 -21
  211. data/doc/classes/Quickbooks/Model.src/M000087.html +0 -26
  212. data/doc/classes/Quickbooks/Model.src/M000088.html +0 -26
  213. data/doc/classes/Quickbooks/Model.src/M000089.html +0 -26
  214. data/doc/classes/Quickbooks/Model.src/M000090.html +0 -21
  215. data/doc/classes/Quickbooks/Model.src/M000091.html +0 -23
  216. data/doc/classes/Quickbooks/OleAdapter.html +0 -129
  217. data/doc/classes/Quickbooks/OleAdapter/Connection.html +0 -343
  218. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000058.html +0 -18
  219. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000059.html +0 -19
  220. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000060.html +0 -24
  221. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000061.html +0 -18
  222. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000062.html +0 -18
  223. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000063.html +0 -21
  224. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000064.html +0 -24
  225. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000065.html +0 -20
  226. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000066.html +0 -24
  227. data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000067.html +0 -24
  228. data/doc/classes/Quickbooks/OleAdapter/Ole.html +0 -209
  229. data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000068.html +0 -21
  230. data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000069.html +0 -22
  231. data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000070.html +0 -18
  232. data/doc/classes/Quickbooks/ParentRef.html +0 -113
  233. data/doc/classes/Quickbooks/PreferredPaymentMethodRef.html +0 -113
  234. data/doc/classes/Quickbooks/PriceLevelRef.html +0 -113
  235. data/doc/classes/Quickbooks/QbxmlDebugAdapter.html +0 -111
  236. data/doc/classes/Quickbooks/QbxmlDebugAdapter/Connection.html +0 -139
  237. data/doc/classes/Quickbooks/QbxmlDebugAdapter/Connection.src/M000057.html +0 -18
  238. data/doc/classes/Quickbooks/Ref.html +0 -146
  239. data/doc/classes/Quickbooks/Ref.src/M000113.html +0 -18
  240. data/doc/classes/Quickbooks/SalesRepRef.html +0 -113
  241. data/doc/classes/Quickbooks/SalesTaxCodeRef.html +0 -113
  242. data/doc/classes/Quickbooks/ShipAddress.html +0 -113
  243. data/doc/classes/Quickbooks/TermsRef.html +0 -113
  244. data/doc/classes/Quickbooks/Transaction.html +0 -154
  245. data/doc/classes/Quickbooks/Transaction.src/M000071.html +0 -19
  246. data/doc/classes/Quickbooks/Transaction.src/M000072.html +0 -18
  247. data/doc/classes/Quickbooks/TxnDeleted.html +0 -139
  248. data/doc/classes/Quickbooks/TxnDeleted.src/M000092.html +0 -18
  249. data/doc/classes/String.html +0 -203
  250. data/doc/classes/String.src/M000052.html +0 -18
  251. data/doc/classes/String.src/M000053.html +0 -18
  252. data/doc/classes/String.src/M000054.html +0 -18
  253. data/doc/classes/String.src/M000055.html +0 -18
  254. data/doc/classes/String.src/M000056.html +0 -18
  255. data/doc/created.rid +0 -1
  256. data/doc/files/LICENSE.html +0 -129
  257. data/doc/files/README.html +0 -225
  258. data/doc/files/lib/qbxml/request_rb.html +0 -110
  259. data/doc/files/lib/qbxml/response_rb.html +0 -109
  260. data/doc/files/lib/qbxml/support_rb.html +0 -101
  261. data/doc/files/lib/qbxml_rb.html +0 -122
  262. data/doc/files/lib/quickbooks/adapters/ole_adapter_rb.html +0 -108
  263. data/doc/files/lib/quickbooks/adapters/qbxml_debug_adapter_rb.html +0 -108
  264. data/doc/files/lib/quickbooks/base_rb.html +0 -327
  265. data/doc/files/lib/quickbooks/model_rb.html +0 -108
  266. data/doc/files/lib/quickbooks/models/common/address_rb.html +0 -101
  267. data/doc/files/lib/quickbooks/models/common/all_refs_rb.html +0 -101
  268. data/doc/files/lib/quickbooks/models/customer/credit_card_info_rb.html +0 -101
  269. data/doc/files/lib/quickbooks/models/customer_rb.html +0 -110
  270. data/doc/files/lib/quickbooks/models/deleted_rb.html +0 -101
  271. data/doc/files/lib/quickbooks/models/list_item_rb.html +0 -101
  272. data/doc/files/lib/quickbooks/models/transaction_rb.html +0 -101
  273. data/doc/files/lib/quickbooks/ruby_magic_rb.html +0 -120
  274. data/doc/files/lib/quickbooks_rb.html +0 -125
  275. data/doc/fr_class_index.html +0 -64
  276. data/doc/fr_file_index.html +0 -45
  277. data/doc/fr_method_index.html +0 -164
  278. data/doc/index.html +0 -24
  279. data/doc/rdoc-style.css +0 -208
  280. data/lib/qbxml.rb +0 -3
  281. data/lib/qbxml/request.rb +0 -207
  282. data/lib/qbxml/response.rb +0 -197
  283. data/lib/qbxml/support.rb +0 -130
  284. data/lib/quickbooks/adapters/qbxml_debug_adapter.rb +0 -10
  285. data/lib/quickbooks/base.rb +0 -277
  286. data/lib/quickbooks/models/common/address.rb +0 -14
  287. data/lib/quickbooks/models/common/all_refs.rb +0 -37
  288. data/lib/quickbooks/models/customer.rb +0 -70
  289. data/lib/quickbooks/models/customer/credit_card_info.rb +0 -5
  290. data/lib/quickbooks/models/deleted.rb +0 -22
  291. data/lib/quickbooks/models/list_item.rb +0 -41
  292. data/lib/quickbooks/models/transaction.rb +0 -49
  293. data/lib/quickbooks/ruby_magic.rb +0 -213
@@ -0,0 +1,39 @@
1
+ # This is a very simple adapter that just throws back your Qbxml Requests. You could use it if you want to
2
+ # handle the sending and receiving of data asynchronously, such as with the Quickbooks Web Connector.
3
+ module Quickbooks
4
+ module Adapters
5
+ module SpewAdapter
6
+ class Connection
7
+ def initialize(*args)
8
+ end
9
+
10
+ # Returns true if there is an open connection to Quickbooks, false if not. Use session? to determine an open session.
11
+ def connected?
12
+ @connected ||= false
13
+ end
14
+
15
+ def close
16
+ end
17
+
18
+ # Sends a request to Quickbooks. This request should be a valid QBXML request. Use Qbxml::Request to generate valid requests.
19
+ def send_xml(xml)
20
+ warn "Quickbooks Spew:\n#{xml}" if $DEBUG
21
+ throw :response, xml;
22
+ end
23
+
24
+ # Returns the active connection to Quickbooks, or creates a new one if none exists.
25
+ def connection
26
+ @connected = true
27
+ end
28
+
29
+ # We can support any version!
30
+ def qbxml_versions
31
+ ['1.0','1.1','2.0','2.1','3.0','4.0','4.1','5.0','6.0','7.0','8.0']
32
+ end
33
+ def latest_qbxml_version
34
+ @latest_qbxml_version ||= qbxml_versions.sort.last
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,71 @@
1
+ require 'net/http'
2
+
3
+ # THIS IS A LEGACY ADAPTER, ITS USE IS NOT RECOMMENDED! It is insecure and unstable.
4
+ # This adapter sends QBXML via an HTTP connection which is kept open for each session, and expects intelligent real-time QB responses from the other end.
5
+ module Quickbooks
6
+ module Adapters
7
+ module HttpAdapter
8
+ # Whatever we do here, we just have to be sure that decrypt(encrypt(data)) == data
9
+ module SimpleEncryption
10
+ def encrypt(data)
11
+ data
12
+ end
13
+
14
+ def decrypt(data)
15
+ data
16
+ end
17
+ end
18
+
19
+ class Connection
20
+ include SimpleEncryption
21
+ def initialize(ip, port, shared_key)
22
+ @ip = ip
23
+ @port = port
24
+ @shared_key = shared_key
25
+ end
26
+
27
+ # Returns true if there is an open connection to Quickbooks, false if not. Use session? to determine an open session.
28
+ def connected?
29
+ (@connection ||= new_connection).started?
30
+ end
31
+
32
+ def close
33
+ @connection.finish rescue nil
34
+ end
35
+
36
+ # Sends a request to Quickbooks. This request should be a valid QBXML request. Use Qbxml::Request to generate valid requests.
37
+ def send_xml(xml)
38
+ # TODO: request.basic_auth url.user, url.password
39
+ res = try_retry(1, EOFError, :before_retry => lambda {@connection.finish}) {
40
+ connection.post('/ProcessRequest', encrypt(xml), {'Content-Type' => 'application/xml'})
41
+ }
42
+ response = decrypt(res.body)
43
+ return response
44
+ # rescue => e
45
+ # Rescue from the error: I guess we have to return a pretend response that says the network failed.
46
+ # raise "Failsafe not yet implemented!: #{e.inspect}"
47
+ end
48
+
49
+ def qbxml_versions
50
+ res = try_retry(1, EOFError, :before_retry => lambda {@connection.finish}) { connection.get('/QBXMLVersionsForSession') }
51
+ response = decrypt(res.body)
52
+ return response.chomp.split(/,/)
53
+ end
54
+ def latest_qbxml_version
55
+ @latest_qbxml_version ||= qbxml_versions.sort.last
56
+ end
57
+
58
+ # Returns the active connection to Quickbooks, or creates a new one if none exists.
59
+ def connection
60
+ @connection.start unless connected?
61
+ @connection
62
+ end
63
+
64
+ private
65
+ def new_connection
66
+ Net::HTTP.new(@ip, @port)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,62 @@
1
+ require 'sha1'
2
+ # This is a fake adapter simply for testing Qbxml Requests.
3
+ module Quickbooks
4
+ module Adapters
5
+ module TestAdapter
6
+ class Connection
7
+ def initialize(*args)
8
+ @stored_responses = {}
9
+ @next_value = []
10
+ @next_block = []
11
+ end
12
+
13
+ def next_response(value, &block)
14
+ @next_value << value
15
+ @next_block << block
16
+ end
17
+
18
+ def clear_responses!
19
+ @next_value = []
20
+ @next_block = []
21
+ end
22
+
23
+ # Returns true if there is an open connection to Quickbooks, false if not. Use session? to determine an open session.
24
+ def connected?
25
+ @connected ||= false
26
+ end
27
+
28
+ def close
29
+ end
30
+
31
+ # Sends a request to Quickbooks. This request should be a valid QBXML request. Use Qbxml::Request to generate valid requests.
32
+ def send_xml(xml)
33
+ xml = xml.gsub(/ requestID=\"\d+\"/,'')
34
+ @next_block[0].call(xml) if @next_block[0]
35
+ cached_response = caching(xml, @next_value[0])
36
+ raise RuntimeError, "You must call next_response to set up the next response xml manually for the TestAdapter." unless cached_response
37
+ @next_value.shift
38
+ @next_block.shift
39
+ return cached_response
40
+ end
41
+
42
+ # Returns the active connection to Quickbooks, or creates a new one if none exists.
43
+ def connection
44
+ @connected = true
45
+ end
46
+
47
+ # We can support any version!
48
+ def qbxml_versions
49
+ ['1.0','1.1','2.0','2.1','3.0','4.0','4.1','5.0','6.0','7.0','8.0']
50
+ end
51
+ def latest_qbxml_version
52
+ @latest_qbxml_version ||= qbxml_versions.sort.last
53
+ end
54
+
55
+ private
56
+ def caching(key,value=nil)
57
+ @stored_responses[SHA1.hexdigest(key)] ||= value
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,385 @@
1
+ require 'quickbooks/property'
2
+ require 'quickbooks/option'
3
+ module Quickbooks
4
+ module Elements
5
+ def self.singularize
6
+ Quickbooks::Element
7
+ end
8
+ end
9
+ # Element is the parent of all QBXML elements (tags) that have children. This includes the outer wrapping QBXML tag, request and response tags such as CustomerAddRq and AccountModRs, data return tags such as AccountRet, and any data fields inside them that also have children. If you don't know what those tags are, don't worry, Model takes care of knowing how to handle them for you.
10
+ #
11
+ # Element acts a lot like a Model but without the record-handling parts, and also acts a little like a Property.
12
+ class Element
13
+ def self.pluralize
14
+ Quickbooks::Elements
15
+ end
16
+ class << self
17
+ attr_reader :properties, :options, :associations
18
+ attr_accessor :xsd
19
+ def xsd=(xsd_element)
20
+ @xsd = xsd_element
21
+ # Initialize the properties, options and associations
22
+ @properties = @xsd.children
23
+ @options = Options.from_xsd(xsd)
24
+ # TODO: Create the associations for Elements!
25
+ @associations = {}
26
+ end
27
+
28
+ def order(obj, a, b) #:nodoc:
29
+ c = (
30
+ obj.class.xsd.index(a.class.short_name) || obj.class.xsd.index(a.class.short_name + obj.send(:suffix))
31
+ ) <=> (
32
+ obj.class.xsd.index(b.class.short_name) || obj.class.xsd.index(b.class.short_name + obj.send(:suffix))
33
+ )
34
+ c == 0 ? a.instance_variable_get(:@collection_index).to_i <=> b.instance_variable_get(:@collection_index).to_i : c
35
+ end
36
+
37
+ # Provides the class equivalent to the current class but without the Ref suffix.
38
+ def unref
39
+ QB[short_name[0..-4]]
40
+ end
41
+
42
+ # This is usually used internally, but is safe to use if you need it for edge cases. It is meant to create a new Element object as if you just read it from Quickbooks -- marks it as not a new record. Call on an Element class, such as QB::Customer.instantiate(attributes).
43
+ def instantiate(obj_or_attrs={})
44
+ obj = allocate
45
+ if obj_or_attrs.is_a?(Hash)
46
+ obj_or_attrs.each do |key,value|
47
+ key = key.to_s
48
+ if options.include?(key)
49
+ obj[key] = value
50
+ next
51
+ end
52
+ next unless xsd.include?(key)
53
+ attr_klass = QB[key]
54
+ # Add the new value
55
+ if value.is_a?(ElementCollection)
56
+ value.each do |v|
57
+ obj.attributes << v
58
+ end
59
+ else
60
+ value = value.is_a?(attr_klass) ? value : attr_klass.new(value)
61
+ obj.attributes << value if xsd.index(value.class.short_name)
62
+ end
63
+ end
64
+ elsif obj_or_attrs.respond_to?(:to_element) && elem = obj_or_attrs.to_element && elem.is_a?(self)
65
+ return elem
66
+ else
67
+ raise ArgumentError, "must supply a hash of arguments or a model object"
68
+ end
69
+ # Sort the values to the proper order
70
+ suffix = short_name =~ /(Add|Mod|Ret)$/ ? $1 : ''
71
+ obj.attributes.sort! do |a,b|
72
+ begin
73
+ Quickbooks::Element.order(obj,a,b)
74
+ rescue => e
75
+ raise e, "-> Comparing #{a.class.short_name.inspect} <=> #{b.class.short_name.inspect} in #{obj.class.short_name} XSD"
76
+ end
77
+ end
78
+ obj.send(:clean!)
79
+ obj
80
+ end
81
+ end
82
+
83
+ # Create a new Element object, such as Account.new or ItemInventory.new. Marks item as a new record, so .save will send a create command to Quickbooks.
84
+ def initialize(attrs={})
85
+ attrs = attrs.attributes if attrs.is_a?(Element)
86
+ attrs = attrs.attributes.values if attrs.is_a?(Model)
87
+ if attrs.is_a?(Array)
88
+ # Allows just passing an array of attributes in.
89
+ set_attrs = attrs.select {|a| self.class.xsd.index(a.class.short_name)}
90
+ reject_attrs = attrs - set_attrs
91
+ raise AttributeAssignmentError, "Attribute#{'s' if reject_attrs.length > 1} not available: #{reject_attrs.join(', ')}!\nAvailable attributes: #{self.class.xsd.children.collect {|i| i.name}.join(', ')}" unless reject_attrs.empty?
92
+ attributes.concat(set_attrs.sort! {|a,b| Quickbooks::Element.order(self,a,b) })
93
+ else
94
+ self.attributes = attrs
95
+ end
96
+ end
97
+
98
+ # Validate all the necessary elements and attributes are included, using the object class's spec
99
+ def valid?
100
+ validate.perfect?
101
+ end
102
+
103
+ # Use this instead of valid? to get the Valean result instead of just a true or false value.
104
+ def validate
105
+ r = self.class.xsd.validate(self)
106
+ errors.replace(r.errors)
107
+ r
108
+ end
109
+
110
+ # Holds any errors from the last time valid? is run.
111
+ def errors
112
+ @errors ||= []
113
+ end
114
+ def add_error(msg) #:nodoc:
115
+ errors << [nil, msg]
116
+ end
117
+
118
+ def inspect #:nodoc:
119
+ "<#{self.class.short_name}:##{object_id}#{' ' + options.collect {|k,v| "#{k}=#{v.inspect}"}.join(' ')}#{" [#{@collection_index}]" if instance_variables.include?('@collection_index')}\n #{attributes.collect {|a| a.inspect}.join("\n").gsub(/\n/, "\n ")}>"
120
+ end
121
+
122
+ # An array of attribute values. Each value is of course instantiated as its own type (some subclass of Element or Property). These attributes are automatically kept in order, and can be accessed somewhat like a hash using the #[] method.
123
+ def attributes
124
+ @attributes ||= []
125
+ end
126
+
127
+ # Sets whatever attributes you give it using []=. Does not remove attributes not given.
128
+ def attributes=(attrs)
129
+ attrs.each do |k,v|
130
+ if self.class.xsd.include?(k.to_s)
131
+ self[k.to_s] = v
132
+ else
133
+ raise AttributeAssignmentError, "Attribute not available: #{k.to_s}!\nAvailable attributes: #{self.class.xsd.children.collect {|i| i.name}.join(', ')}"
134
+ end
135
+ end
136
+ end
137
+
138
+ # Access an attribute value by its type -- use a string, a symbol, or its Class constant.
139
+ def [](key)
140
+ return self.attributes = key if key.is_a?(Hash)
141
+ key = case key
142
+ when String
143
+ key
144
+ when Symbol
145
+ key.to_s
146
+ when Module
147
+ key.short_name
148
+ else
149
+ raise RuntimeError, "boy, that is a weird key (type:#{key.class.name})"
150
+ end
151
+ return get_associated(key) if self.class.associations.include?(key)
152
+ return options.has_key?(key) ? options[key] : self.class.options[key].default if self.class.options.include?(key)
153
+ vals = attributes.select {|a| a.class.short_name == key}
154
+ property_xsd = self.class.xsd.find(key.to_s)
155
+ property_xsd ? (self.class.xsd.repeatable?(key.to_s) ? vals : vals[0]) : nil
156
+ end
157
+
158
+ # Set the value of an attribute. If key refers to an association, it associates the given value. If it is an option, it sets the option.
159
+ # Otherwise if it's a valid attribute, it sets the attribute value.
160
+ #
161
+ # Note: If multiple of this attribute are allowed, you must send an array of values as the value.
162
+ def []=(key,value)
163
+ key = case key
164
+ when String
165
+ key
166
+ when Symbol
167
+ key.to_s
168
+ when Module
169
+ key.short_name
170
+ else
171
+ raise RuntimeError, "man is that a weird kind of key to use (type:#{key.class.name})!"
172
+ end
173
+ return associate(key, value) if self.class.associations.include?(key)
174
+ unless self.class.xsd.include?(key)
175
+ if self.class.short_name =~ /(Add|Mod|Ret)$/ && self.class.xsd.include?(key + $1)
176
+ key = key + $1
177
+ else
178
+ return options[key] = value if self.class.options.include?(key)
179
+ raise Quickbooks::InvalidAttributeError, "'#{key}' is not a valid property or option name for #{self.class.name}!"
180
+ end
181
+ end
182
+ # Instantiate the value into the attribute class, if necessary
183
+ attr_klass = Quickbooks.get_constant(key.to_s)
184
+ if self.class.xsd.repeatable?(key.to_s)
185
+ if value.is_a?(ElementCollection) || value.is_a?(Array)
186
+ attributes.reject! {|a| a.is_a?(attr_klass)}
187
+ value.each do |val|
188
+ val = attr_klass.new(val) unless val.is_a?(attr_klass)
189
+ val.send(:dirty!)
190
+ attributes << val
191
+ end
192
+ else
193
+ # If it *should* be an array element, it shouldn't be set here as a single value. This is just for safeguard, so that syntax
194
+ # always shows what is going on. For an array element, set it with: object.some_attr = [value]
195
+ raise RuntimeError, "You can't set a single value into a multi-value element to using equals(=). Use \"element[:#{key}] << value\" to append, or wrap the value in an array -- \"element[:#{key}] = [ value ]\" -- if you want to completely replace the current value set."
196
+ end
197
+ else
198
+ value = attr_klass.new(value) unless value.is_a?(attr_klass)
199
+ value.send(:dirty!)
200
+ # Remove the previous value
201
+ attributes.reject! {|a| a.is_a?(attr_klass)}
202
+ # Add the new value
203
+ attributes << value
204
+ end
205
+ # Sort the values to the proper order
206
+ attributes.sort! {|a,b| Quickbooks::Element.order(self, a, b) }
207
+ end
208
+
209
+ # Remove an attribute from the object. Setting an attribute to nil will be an attribute set to nil, but if you remove the
210
+ # attribute completely, it won't be considered in create/update operations.
211
+ def delete(*keys)
212
+ keys.each do |key|
213
+ key = case key
214
+ when String
215
+ key
216
+ when Symbol
217
+ key.to_s
218
+ when Module
219
+ key.short_name
220
+ else
221
+ raise RuntimeError, "boy, that is a weird key (type:#{key.class.name})"
222
+ end
223
+ unless self.class.xsd.include?(key) || (self.class.short_name =~ /(Add|Mod|Ret)$/ && self.class.xsd.include?(key + $1))
224
+ return options.delete(key) if self.class.options.include?(key)
225
+ raise RuntimeError, "'#{key}' is not a valid property or option name for #{self.class.name}!"
226
+ end
227
+ # Instantiate the value into the attribute class, if necessary
228
+ attr_klass = Quickbooks.get_constant(key.to_s)
229
+
230
+ attributes.reject! {|a| a.is_a?(attr_klass)}
231
+ end
232
+ end
233
+
234
+ # The attributes that have been changed.
235
+ def dirty_attributes(include_required=false)
236
+ attr_hash = attributes_hash # (Speedier when we only do it once)
237
+ include_required ?
238
+ attributes.select {|a| a.dirty? || self.class.xsd.required?(a.class.short_name) || !self.class.xsd.validate(self.class.short_name => attr_hash.except(a.class.short_name))} :
239
+ attributes.select {|a| a.dirty?}
240
+ end
241
+
242
+ # The attributes that have not been changed.
243
+ def clean_attributes
244
+ attributes.reject {|a| a.dirty?}
245
+ end
246
+
247
+ # Has anything in this object (or its descendents) changed?
248
+ def dirty?
249
+ # Test for any dirty elements
250
+ @dirty || attributes.any? {|e| e.dirty?}
251
+ end
252
+
253
+ # Returns self. Just a convenience method for interoperability with Model.
254
+ def to_element
255
+ self
256
+ end
257
+
258
+ # Attemps to get the Model object being referenced, if this is a Ref class.
259
+ def unref
260
+ @retrieve_full ||= begin
261
+ filter = {}
262
+ case
263
+ when self[:ListID]
264
+ filter[:ListID] = [self[:ListID]]
265
+ when self[:TxnID]
266
+ filter[:TxnID] = [self[:TxnID]]
267
+ when self[:FullName]
268
+ filter[:FullName] = [self[:FullName]]
269
+ end
270
+ self.class.unref.first(filter)
271
+ end
272
+ end
273
+
274
+ # Converts the element to a model if it is an appropriate class type.
275
+ def to_model
276
+ # List clean attributes
277
+ clean_attrs = attributes_to_hash(clean_attributes).inject({}) {|h,(k,v)| h[k.gsub(/(Add|Mod|Ret)$/,'')] = v.respond_to?(:to_model) ? v.to_model : v; h}
278
+ # List dirty attributes
279
+ dirty_attrs = attributes_to_hash(dirty_attributes).inject({}) {|h,(k,v)| h[k.gsub(/(Add|Mod|Ret)$/,'')] = v.respond_to?(:to_model) ? v.to_model : v; h}
280
+ # Set up the corresponding element with corresponding properties
281
+ model = model_klass.instantiate(clean_attrs)
282
+ model.attributes = dirty_attrs
283
+ # Transfer the order index (for attribute sorting) from elements that were in a collection.
284
+ model.instance_variable_set(:@collection_index, @collection_index) if instance_variables.include?('@collection_index')
285
+ model
286
+ end
287
+
288
+ def to_update_element
289
+ update_element = self.class.new
290
+ update_element.attributes.replace(dirty_attributes(true))
291
+ update_element
292
+ end
293
+
294
+ # Dumps all data into QBXML.
295
+ def to_xml
296
+ '<' + self.class.short_name + [[nil] + options.collect {|k,v| "#{k}=#{v.inspect}"}].join(' ') + '>' + ("\n" + attributes.collect {|a| a.to_xml}.join("\n")).gsub(/\n( *)</, "\n \\1<") + "\n</" + self.class.short_name + '>'
297
+ end
298
+
299
+ # Dumps only the changed data into QBXML. Useful for updating only specific attributes without touching the rest. Set include_required to true if you need valid qbxml to send.
300
+ def to_dirty_xml(include_required=false)
301
+ '<' + self.class.short_name + '>' + ("\n" + dirty_attributes(include_required).collect {|a| a.to_dirty_xml(include_required)}.join("\n")).gsub(/\n( *)</, "\n \\1<") + "\n</" + self.class.short_name + '>'
302
+ end
303
+
304
+ # Simply calls to_dirty_xml(true). Returns the minimal xml necessary to update the record in QuickBooks.
305
+ def update_xml
306
+ to_dirty_xml(true)
307
+ end
308
+
309
+ def save_associations #:nodoc:
310
+ # first save any of my associated items that aren't already existing
311
+ self.class.associations.each_key do |association_name|
312
+ if instance_variable_get("@#{association_name}") && self[association_name].new_record?
313
+ self[association_name].save
314
+ self[association_name] = self[association_name] # re-assigns the associated Ref
315
+ end
316
+ end
317
+ # then save any of my children's associated items that aren't already existing
318
+ attributes.each { |attv| attv.save_associations if attv.respond_to?(:save_associations) }
319
+ end
320
+
321
+ # I don't remember why this nil? had to be modified. If the need pops back up, CREATE A TEST to show the need!!
322
+ # def nil? #:nodoc:
323
+ # if self.class.xsd.include?('FullName') && self.class.xsd.include?('ListID') || (self.class.xsd.include?('TxnID') && !self.class.xsd.name =~ /DataExt/)
324
+ # self[:FullName].nil? && self[:ListID].nil? && self[:TxnID].nil?
325
+ # else
326
+ # super
327
+ # end
328
+ # end
329
+
330
+ def new_record?
331
+
332
+ end
333
+
334
+ private
335
+
336
+ def model_klass
337
+ Quickbooks.get_constant(self.class.short_name.gsub(/(Add|Mod|Ret)$/,''))
338
+ end
339
+
340
+ def clean!
341
+ @dirty = false
342
+ attributes.each {|e| e.send(:clean!)}
343
+ end
344
+
345
+ def dirty!
346
+ @dirty = true
347
+ end
348
+
349
+ def get_associated(association_name)
350
+ instance_variable_get("@#{association_name}") || begin
351
+ ref_klass = self.class.associations[association_name].to_constant
352
+ self[ref_klass].nil? ? nil : instance_variable_set("@#{association_name}", self[ref_klass].unref)
353
+ end
354
+ end
355
+
356
+ def associate(association_name, value)
357
+ instance_variable_set("@#{association_name}", value)
358
+ ref = value.to_ref
359
+ ref_klass = self.class.associations[association_name].to_constant
360
+ !ref.nil? ?
361
+ self[ref_klass] = ref :
362
+ self.delete(ref_klass)
363
+ end
364
+
365
+ def attributes_to_hash(some_attributes)
366
+ some_attributes.inject({}) do |h,att|
367
+ k = att.class.short_name
368
+ h[k] = self.class.xsd.repeatable?(k) ? ElementCollection.new(self,k,self[k]) : att
369
+ h
370
+ end
371
+ end
372
+
373
+ def attributes_hash
374
+ attributes_to_hash(attributes)
375
+ end
376
+
377
+ def options
378
+ @options ||= self.class.options.defaults
379
+ end
380
+
381
+ def suffix
382
+ self.class.short_name =~ /(Add|Mod|Ret)$/ ? $1 : ''
383
+ end
384
+ end
385
+ end