quickbooks 0.4.0 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/History.txt +23 -0
- data/License.txt +54 -0
- data/Manifest.txt +73 -0
- data/README.txt +79 -0
- data/Rakefile +17 -78
- data/lib/quickbooks.rb +153 -7
- data/lib/quickbooks/adaptability.rb +67 -0
- data/lib/quickbooks/adapters/http_adapter.rb +94 -0
- data/lib/quickbooks/adapters/https_adapter.rb +57 -0
- data/lib/quickbooks/adapters/ole_adapter.rb +141 -110
- data/lib/quickbooks/adapters/spew_adapter.rb +39 -0
- data/lib/quickbooks/adapters/tcp_adapter.rb +71 -0
- data/lib/quickbooks/adapters/test_adapter.rb +62 -0
- data/lib/quickbooks/element.rb +385 -0
- data/lib/quickbooks/element_collection.rb +105 -0
- data/lib/quickbooks/extlib.rb +8 -0
- data/lib/quickbooks/extlib/assertions.rb +8 -0
- data/lib/quickbooks/extlib/class.rb +98 -0
- data/lib/quickbooks/extlib/days_and_times.rb +10 -0
- data/lib/quickbooks/extlib/days_and_times/duration.rb +362 -0
- data/lib/quickbooks/extlib/days_and_times/numeric.rb +48 -0
- data/lib/quickbooks/extlib/days_and_times/object.rb +19 -0
- data/lib/quickbooks/extlib/days_and_times/time.rb +137 -0
- data/lib/quickbooks/extlib/hash.rb +327 -0
- data/lib/quickbooks/extlib/hook.rb +366 -0
- data/lib/quickbooks/extlib/inflection.rb +436 -0
- data/lib/quickbooks/extlib/logger.rb +202 -0
- data/lib/quickbooks/extlib/object.rb +162 -0
- data/lib/quickbooks/extlib/pathname.rb +15 -0
- data/lib/quickbooks/extlib/rubygems.rb +38 -0
- data/lib/quickbooks/extlib/string.rb +32 -0
- data/lib/quickbooks/extlib/time.rb +41 -0
- data/lib/quickbooks/model.rb +503 -121
- data/lib/quickbooks/option.rb +95 -0
- data/lib/quickbooks/property.rb +69 -0
- data/lib/quickbooks/ruby_ext.rb +251 -0
- data/lib/quickbooks/type.rb +75 -0
- data/lib/quickbooks/types.rb +58 -0
- data/lib/quickbooks/version.rb +10 -0
- data/lib/quickbooks/xsd.rb +243 -0
- data/lib/quickbooks/xsd/attribute.rb +57 -0
- data/lib/quickbooks/xsd/choice.rb +94 -0
- data/lib/quickbooks/xsd/complex_type.rb +77 -0
- data/lib/quickbooks/xsd/element.rb +214 -0
- data/lib/quickbooks/xsd/group.rb +94 -0
- data/lib/quickbooks/xsd/restriction.rb +51 -0
- data/lib/quickbooks/xsd/sequence.rb +85 -0
- data/lib/quickbooks/xsd/simple_type.rb +87 -0
- data/lib/quickbooks/xsd/union.rb +70 -0
- data/lib/quickbooks/xsd/validation.rb +89 -0
- data/lib/quickbooks/xsd/xml_parse.rb +80 -0
- data/quickbooks.gemspec +36 -0
- data/source/qbxml21_demo.xsd +2223 -0
- data/source/qbxmlops_demo.xsd +483 -0
- data/source/qbxmltypes.xsd +160 -0
- data/spec/association_spec.rb +5 -0
- data/spec/element_collection_spec.rb +8 -0
- data/spec/find_spec.rb +30 -0
- data/spec/lib/quickbooks/element_spec.rb +83 -0
- data/spec/lib/quickbooks/elements/sales_order_add_spec.rb +14 -0
- data/spec/lib/quickbooks/elements/vendor_add_spec.rb +78 -0
- data/spec/lib/quickbooks/model_spec.rb +89 -0
- data/spec/lib/quickbooks/models/customer_spec.rb +52 -0
- data/spec/lib/quickbooks/models/invoice_spec.rb +12 -0
- data/spec/lib/quickbooks/models/sales_order_line_spec.rb +0 -0
- data/spec/lib/quickbooks/models/sales_order_spec.rb +145 -0
- data/spec/lib/quickbooks/xsd/choice_spec.rb +18 -0
- data/spec/pending_spec.rb +11 -0
- data/spec/quickbooks_spec.rb +38 -0
- data/spec/repeatable_spec.rb +20 -0
- data/spec/requires_spec.rb +10 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/validation_spec.rb +24 -0
- metadata +133 -285
- metadata.gz.sig +0 -0
- data/LICENSE +0 -16
- data/README +0 -49
- data/doc/classes/Array.html +0 -368
- data/doc/classes/Array.src/M000026.html +0 -18
- data/doc/classes/Array.src/M000027.html +0 -21
- data/doc/classes/Array.src/M000028.html +0 -18
- data/doc/classes/Array.src/M000029.html +0 -19
- data/doc/classes/Array.src/M000030.html +0 -18
- data/doc/classes/Array.src/M000031.html +0 -23
- data/doc/classes/Array.src/M000032.html +0 -18
- data/doc/classes/Array.src/M000033.html +0 -18
- data/doc/classes/Array.src/M000034.html +0 -18
- data/doc/classes/Array.src/M000035.html +0 -19
- data/doc/classes/Array.src/M000036.html +0 -18
- data/doc/classes/Array.src/M000037.html +0 -23
- data/doc/classes/Array.src/M000038.html +0 -18
- data/doc/classes/Array.src/M000039.html +0 -21
- data/doc/classes/Array.src/M000040.html +0 -18
- data/doc/classes/Array.src/M000041.html +0 -18
- data/doc/classes/Class.html +0 -171
- data/doc/classes/Class.src/M000042.html +0 -18
- data/doc/classes/Class.src/M000043.html +0 -18
- data/doc/classes/Hash.html +0 -516
- data/doc/classes/Hash.src/M000001.html +0 -18
- data/doc/classes/Hash.src/M000002.html +0 -21
- data/doc/classes/Hash.src/M000003.html +0 -18
- data/doc/classes/Hash.src/M000004.html +0 -21
- data/doc/classes/Hash.src/M000005.html +0 -18
- data/doc/classes/Hash.src/M000006.html +0 -20
- data/doc/classes/Hash.src/M000007.html +0 -18
- data/doc/classes/Hash.src/M000008.html +0 -22
- data/doc/classes/Hash.src/M000009.html +0 -18
- data/doc/classes/Hash.src/M000010.html +0 -21
- data/doc/classes/Hash.src/M000011.html +0 -24
- data/doc/classes/Hash.src/M000012.html +0 -18
- data/doc/classes/Hash.src/M000013.html +0 -21
- data/doc/classes/Hash.src/M000014.html +0 -18
- data/doc/classes/Hash.src/M000015.html +0 -21
- data/doc/classes/Hash.src/M000016.html +0 -18
- data/doc/classes/Hash.src/M000017.html +0 -21
- data/doc/classes/Hash.src/M000018.html +0 -27
- data/doc/classes/Hash.src/M000019.html +0 -20
- data/doc/classes/Hash.src/M000020.html +0 -18
- data/doc/classes/Hash.src/M000021.html +0 -18
- data/doc/classes/Hash.src/M000022.html +0 -18
- data/doc/classes/Hash.src/M000023.html +0 -18
- data/doc/classes/Hash.src/M000024.html +0 -18
- data/doc/classes/Hash.src/M000025.html +0 -28
- data/doc/classes/Object.html +0 -278
- data/doc/classes/Object.src/M000044.html +0 -24
- data/doc/classes/Object.src/M000045.html +0 -19
- data/doc/classes/Object.src/M000046.html +0 -24
- data/doc/classes/Object.src/M000047.html +0 -18
- data/doc/classes/Object.src/M000048.html +0 -19
- data/doc/classes/Object.src/M000049.html +0 -18
- data/doc/classes/Object.src/M000050.html +0 -18
- data/doc/classes/Object.src/M000051.html +0 -18
- data/doc/classes/Qbxml.html +0 -131
- data/doc/classes/Qbxml/Request.html +0 -224
- data/doc/classes/Qbxml/Request.src/M000132.html +0 -66
- data/doc/classes/Qbxml/Request.src/M000133.html +0 -19
- data/doc/classes/Qbxml/Request.src/M000134.html +0 -83
- data/doc/classes/Qbxml/RequestSet.html +0 -199
- data/doc/classes/Qbxml/RequestSet.src/M000135.html +0 -22
- data/doc/classes/Qbxml/RequestSet.src/M000136.html +0 -26
- data/doc/classes/Qbxml/RequestSet.src/M000137.html +0 -18
- data/doc/classes/Qbxml/RequestSet.src/M000138.html +0 -22
- data/doc/classes/Qbxml/Response.html +0 -302
- data/doc/classes/Qbxml/Response.src/M000123.html +0 -24
- data/doc/classes/Qbxml/Response.src/M000124.html +0 -19
- data/doc/classes/Qbxml/Response.src/M000125.html +0 -55
- data/doc/classes/Qbxml/Response.src/M000126.html +0 -18
- data/doc/classes/Qbxml/Response.src/M000127.html +0 -18
- data/doc/classes/Qbxml/Response.src/M000128.html +0 -18
- data/doc/classes/Qbxml/Response.src/M000129.html +0 -18
- data/doc/classes/Qbxml/Response.src/M000130.html +0 -18
- data/doc/classes/Qbxml/Response.src/M000131.html +0 -63
- data/doc/classes/Qbxml/ResponseSet.html +0 -244
- data/doc/classes/Qbxml/ResponseSet.src/M000116.html +0 -22
- data/doc/classes/Qbxml/ResponseSet.src/M000117.html +0 -26
- data/doc/classes/Qbxml/ResponseSet.src/M000118.html +0 -24
- data/doc/classes/Qbxml/ResponseSet.src/M000119.html +0 -20
- data/doc/classes/Qbxml/ResponseSet.src/M000120.html +0 -27
- data/doc/classes/Qbxml/ResponseSet.src/M000121.html +0 -18
- data/doc/classes/Qbxml/ResponseSet.src/M000122.html +0 -18
- data/doc/classes/Quickbooks.html +0 -196
- data/doc/classes/Quickbooks/Address.html +0 -139
- data/doc/classes/Quickbooks/Address.src/M000115.html +0 -19
- data/doc/classes/Quickbooks/Base.html +0 -567
- data/doc/classes/Quickbooks/Base.src/M000095.html +0 -18
- data/doc/classes/Quickbooks/Base.src/M000096.html +0 -20
- data/doc/classes/Quickbooks/Base.src/M000097.html +0 -19
- data/doc/classes/Quickbooks/Base.src/M000098.html +0 -18
- data/doc/classes/Quickbooks/Base.src/M000099.html +0 -19
- data/doc/classes/Quickbooks/Base.src/M000100.html +0 -30
- data/doc/classes/Quickbooks/Base.src/M000101.html +0 -22
- data/doc/classes/Quickbooks/Base.src/M000102.html +0 -30
- data/doc/classes/Quickbooks/Base.src/M000103.html +0 -19
- data/doc/classes/Quickbooks/Base.src/M000104.html +0 -19
- data/doc/classes/Quickbooks/Base.src/M000105.html +0 -18
- data/doc/classes/Quickbooks/Base.src/M000106.html +0 -18
- data/doc/classes/Quickbooks/Base.src/M000107.html +0 -19
- data/doc/classes/Quickbooks/Base.src/M000108.html +0 -18
- data/doc/classes/Quickbooks/Base.src/M000109.html +0 -45
- data/doc/classes/Quickbooks/Base.src/M000110.html +0 -18
- data/doc/classes/Quickbooks/Base.src/M000111.html +0 -18
- data/doc/classes/Quickbooks/Base.src/M000112.html +0 -20
- data/doc/classes/Quickbooks/BillAddress.html +0 -113
- data/doc/classes/Quickbooks/CreditCardInfo.html +0 -113
- data/doc/classes/Quickbooks/Customer.html +0 -113
- data/doc/classes/Quickbooks/CustomerTypeRef.html +0 -113
- data/doc/classes/Quickbooks/Deleted.html +0 -113
- data/doc/classes/Quickbooks/ItemSalesTaxRef.html +0 -113
- data/doc/classes/Quickbooks/JobTypeRef.html +0 -113
- data/doc/classes/Quickbooks/ListDeleted.html +0 -139
- data/doc/classes/Quickbooks/ListDeleted.src/M000114.html +0 -18
- data/doc/classes/Quickbooks/ListItem.html +0 -170
- data/doc/classes/Quickbooks/ListItem.src/M000093.html +0 -20
- data/doc/classes/Quickbooks/ListItem.src/M000094.html +0 -18
- data/doc/classes/Quickbooks/Model.html +0 -462
- data/doc/classes/Quickbooks/Model.src/M000073.html +0 -24
- data/doc/classes/Quickbooks/Model.src/M000074.html +0 -19
- data/doc/classes/Quickbooks/Model.src/M000075.html +0 -18
- data/doc/classes/Quickbooks/Model.src/M000076.html +0 -18
- data/doc/classes/Quickbooks/Model.src/M000077.html +0 -18
- data/doc/classes/Quickbooks/Model.src/M000078.html +0 -22
- data/doc/classes/Quickbooks/Model.src/M000079.html +0 -32
- data/doc/classes/Quickbooks/Model.src/M000080.html +0 -32
- data/doc/classes/Quickbooks/Model.src/M000081.html +0 -18
- data/doc/classes/Quickbooks/Model.src/M000082.html +0 -18
- data/doc/classes/Quickbooks/Model.src/M000083.html +0 -22
- data/doc/classes/Quickbooks/Model.src/M000084.html +0 -23
- data/doc/classes/Quickbooks/Model.src/M000085.html +0 -18
- data/doc/classes/Quickbooks/Model.src/M000086.html +0 -21
- data/doc/classes/Quickbooks/Model.src/M000087.html +0 -26
- data/doc/classes/Quickbooks/Model.src/M000088.html +0 -26
- data/doc/classes/Quickbooks/Model.src/M000089.html +0 -26
- data/doc/classes/Quickbooks/Model.src/M000090.html +0 -21
- data/doc/classes/Quickbooks/Model.src/M000091.html +0 -23
- data/doc/classes/Quickbooks/OleAdapter.html +0 -129
- data/doc/classes/Quickbooks/OleAdapter/Connection.html +0 -343
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000058.html +0 -18
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000059.html +0 -19
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000060.html +0 -24
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000061.html +0 -18
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000062.html +0 -18
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000063.html +0 -21
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000064.html +0 -24
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000065.html +0 -20
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000066.html +0 -24
- data/doc/classes/Quickbooks/OleAdapter/Connection.src/M000067.html +0 -24
- data/doc/classes/Quickbooks/OleAdapter/Ole.html +0 -209
- data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000068.html +0 -21
- data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000069.html +0 -22
- data/doc/classes/Quickbooks/OleAdapter/Ole.src/M000070.html +0 -18
- data/doc/classes/Quickbooks/ParentRef.html +0 -113
- data/doc/classes/Quickbooks/PreferredPaymentMethodRef.html +0 -113
- data/doc/classes/Quickbooks/PriceLevelRef.html +0 -113
- data/doc/classes/Quickbooks/QbxmlDebugAdapter.html +0 -111
- data/doc/classes/Quickbooks/QbxmlDebugAdapter/Connection.html +0 -139
- data/doc/classes/Quickbooks/QbxmlDebugAdapter/Connection.src/M000057.html +0 -18
- data/doc/classes/Quickbooks/Ref.html +0 -146
- data/doc/classes/Quickbooks/Ref.src/M000113.html +0 -18
- data/doc/classes/Quickbooks/SalesRepRef.html +0 -113
- data/doc/classes/Quickbooks/SalesTaxCodeRef.html +0 -113
- data/doc/classes/Quickbooks/ShipAddress.html +0 -113
- data/doc/classes/Quickbooks/TermsRef.html +0 -113
- data/doc/classes/Quickbooks/Transaction.html +0 -154
- data/doc/classes/Quickbooks/Transaction.src/M000071.html +0 -19
- data/doc/classes/Quickbooks/Transaction.src/M000072.html +0 -18
- data/doc/classes/Quickbooks/TxnDeleted.html +0 -139
- data/doc/classes/Quickbooks/TxnDeleted.src/M000092.html +0 -18
- data/doc/classes/String.html +0 -203
- data/doc/classes/String.src/M000052.html +0 -18
- data/doc/classes/String.src/M000053.html +0 -18
- data/doc/classes/String.src/M000054.html +0 -18
- data/doc/classes/String.src/M000055.html +0 -18
- data/doc/classes/String.src/M000056.html +0 -18
- data/doc/created.rid +0 -1
- data/doc/files/LICENSE.html +0 -129
- data/doc/files/README.html +0 -225
- data/doc/files/lib/qbxml/request_rb.html +0 -110
- data/doc/files/lib/qbxml/response_rb.html +0 -109
- data/doc/files/lib/qbxml/support_rb.html +0 -101
- data/doc/files/lib/qbxml_rb.html +0 -122
- data/doc/files/lib/quickbooks/adapters/ole_adapter_rb.html +0 -108
- data/doc/files/lib/quickbooks/adapters/qbxml_debug_adapter_rb.html +0 -108
- data/doc/files/lib/quickbooks/base_rb.html +0 -327
- data/doc/files/lib/quickbooks/model_rb.html +0 -108
- data/doc/files/lib/quickbooks/models/common/address_rb.html +0 -101
- data/doc/files/lib/quickbooks/models/common/all_refs_rb.html +0 -101
- data/doc/files/lib/quickbooks/models/customer/credit_card_info_rb.html +0 -101
- data/doc/files/lib/quickbooks/models/customer_rb.html +0 -110
- data/doc/files/lib/quickbooks/models/deleted_rb.html +0 -101
- data/doc/files/lib/quickbooks/models/list_item_rb.html +0 -101
- data/doc/files/lib/quickbooks/models/transaction_rb.html +0 -101
- data/doc/files/lib/quickbooks/ruby_magic_rb.html +0 -120
- data/doc/files/lib/quickbooks_rb.html +0 -125
- data/doc/fr_class_index.html +0 -64
- data/doc/fr_file_index.html +0 -45
- data/doc/fr_method_index.html +0 -164
- data/doc/index.html +0 -24
- data/doc/rdoc-style.css +0 -208
- data/lib/qbxml.rb +0 -3
- data/lib/qbxml/request.rb +0 -207
- data/lib/qbxml/response.rb +0 -197
- data/lib/qbxml/support.rb +0 -130
- data/lib/quickbooks/adapters/qbxml_debug_adapter.rb +0 -10
- data/lib/quickbooks/base.rb +0 -277
- data/lib/quickbooks/models/common/address.rb +0 -14
- data/lib/quickbooks/models/common/all_refs.rb +0 -37
- data/lib/quickbooks/models/customer.rb +0 -70
- data/lib/quickbooks/models/customer/credit_card_info.rb +0 -5
- data/lib/quickbooks/models/deleted.rb +0 -22
- data/lib/quickbooks/models/list_item.rb +0 -41
- data/lib/quickbooks/models/transaction.rb +0 -49
- 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
|