qbfc 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +11 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +85 -0
  4. data/Rakefile +84 -0
  5. data/VERSION +1 -0
  6. data/lib/qbfc.rb +41 -0
  7. data/lib/qbfc/base.rb +82 -0
  8. data/lib/qbfc/element.rb +243 -0
  9. data/lib/qbfc/entities/generated.rb +8 -0
  10. data/lib/qbfc/entity.rb +11 -0
  11. data/lib/qbfc/info.rb +42 -0
  12. data/lib/qbfc/infos/generated.rb +9 -0
  13. data/lib/qbfc/item.rb +29 -0
  14. data/lib/qbfc/items/generated.rb +11 -0
  15. data/lib/qbfc/list.rb +84 -0
  16. data/lib/qbfc/lists/account.rb +24 -0
  17. data/lib/qbfc/lists/generated.rb +15 -0
  18. data/lib/qbfc/lists/qb_class.rb +25 -0
  19. data/lib/qbfc/modifiable.rb +31 -0
  20. data/lib/qbfc/ole_wrapper.rb +201 -0
  21. data/lib/qbfc/qb_collection.rb +26 -0
  22. data/lib/qbfc/qb_types.rb +18 -0
  23. data/lib/qbfc/qbfc_const.rb +14 -0
  24. data/lib/qbfc/report.rb +95 -0
  25. data/lib/qbfc/reports/aging.rb +13 -0
  26. data/lib/qbfc/reports/budget_summary.rb +13 -0
  27. data/lib/qbfc/reports/custom_detail.rb +9 -0
  28. data/lib/qbfc/reports/custom_summary.rb +9 -0
  29. data/lib/qbfc/reports/general_detail.rb +44 -0
  30. data/lib/qbfc/reports/general_summary.rb +33 -0
  31. data/lib/qbfc/reports/job.rb +14 -0
  32. data/lib/qbfc/reports/payroll_detail.rb +13 -0
  33. data/lib/qbfc/reports/payroll_summary.rb +13 -0
  34. data/lib/qbfc/reports/rows.rb +51 -0
  35. data/lib/qbfc/reports/time.rb +12 -0
  36. data/lib/qbfc/request.rb +295 -0
  37. data/lib/qbfc/session.rb +147 -0
  38. data/lib/qbfc/terms.rb +10 -0
  39. data/lib/qbfc/terms/generated.rb +10 -0
  40. data/lib/qbfc/transaction.rb +110 -0
  41. data/lib/qbfc/transactions/generated.rb +25 -0
  42. data/lib/qbfc/voidable.rb +11 -0
  43. data/qbfc.gemspec +166 -0
  44. data/spec/fixtures/test.lgb +0 -0
  45. data/spec/fixtures/test.qbw +0 -0
  46. data/spec/fixtures/test.qbw.TLG +0 -0
  47. data/spec/integration/add_spec.rb +31 -0
  48. data/spec/integration/base_spec.rb +18 -0
  49. data/spec/integration/belongs_to_spec.rb +64 -0
  50. data/spec/integration/company_spec.rb +30 -0
  51. data/spec/integration/conditions_spec.rb +59 -0
  52. data/spec/integration/customer_spec.rb +46 -0
  53. data/spec/integration/element_finders_spec.rb +20 -0
  54. data/spec/integration/quick_test.rb +31 -0
  55. data/spec/integration/request_options_spec.rb +68 -0
  56. data/spec/rcov.opts +1 -0
  57. data/spec/spec.opts +6 -0
  58. data/spec/spec_helper.rb +62 -0
  59. data/spec/unit/base_spec.rb +138 -0
  60. data/spec/unit/element_finder_spec.rb +185 -0
  61. data/spec/unit/element_spec.rb +108 -0
  62. data/spec/unit/entities/generated_spec.rb +18 -0
  63. data/spec/unit/entity_spec.rb +18 -0
  64. data/spec/unit/info/generated_spec.rb +12 -0
  65. data/spec/unit/info_spec.rb +48 -0
  66. data/spec/unit/item_spec.rb +33 -0
  67. data/spec/unit/items/generated_spec.rb +16 -0
  68. data/spec/unit/list_finders_spec.rb +129 -0
  69. data/spec/unit/list_spec.rb +86 -0
  70. data/spec/unit/lists/account_spec.rb +20 -0
  71. data/spec/unit/lists/generated_spec.rb +15 -0
  72. data/spec/unit/lists/qb_class_spec.rb +9 -0
  73. data/spec/unit/modifiable_spec.rb +84 -0
  74. data/spec/unit/ole_wrapper_spec.rb +337 -0
  75. data/spec/unit/qb_collection_spec.rb +13 -0
  76. data/spec/unit/qbfc_const_spec.rb +10 -0
  77. data/spec/unit/qbfc_spec.rb +10 -0
  78. data/spec/unit/report_spec.rb +12 -0
  79. data/spec/unit/request_query_survey.txt +48 -0
  80. data/spec/unit/request_spec.rb +486 -0
  81. data/spec/unit/session_spec.rb +144 -0
  82. data/spec/unit/terms/generated_spec.rb +14 -0
  83. data/spec/unit/terms_spec.rb +18 -0
  84. data/spec/unit/transaction_finders_spec.rb +125 -0
  85. data/spec/unit/transaction_spec.rb +94 -0
  86. data/spec/unit/transactions/generated_spec.rb +20 -0
  87. data/spec/unit/voidable_spec.rb +32 -0
  88. data/tasks/qbfc_tasks.rake +4 -0
  89. metadata +182 -0
@@ -0,0 +1,11 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ .project
7
+ .loadpath
8
+ *qt_temp*
9
+ *~
10
+ *.qbw.nd
11
+ spec/tmp
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Jared E Morgan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,85 @@
1
+ ==QBFC-Ruby
2
+
3
+ QBFC-Ruby provides a wrapper around QuickBooks' QBFC COM object, while
4
+ allowing more or less direct access to the actual COM object.
5
+
6
+ Obviously, test before using on your production data...
7
+
8
+ ==Find
9
+
10
+ QBFC-Ruby supports +find+ queries with options, which utitilize QBFC's Query
11
+ Requests. See QBFC::Element.find for details and options.
12
+
13
+ QBFC::session do | qb |
14
+ checks = qb.checks.find(:first, :conditions => {:entity => 'ABC Supplies'})
15
+ end
16
+
17
+ ==Relationships
18
+
19
+ QBFC-Ruby supports loading of related records. These are records
20
+ represented by "*Ref" in the QBFC documentation. For example, a Check
21
+ has, among others, a PayeeEntityRef and an AccountRef. These can be
22
+ accessed via, respectively, <tt>check.payee</tt> and <tt>check.account</tt>.
23
+
24
+ <tt>check.payee.name</tt> returns the name of the payee.
25
+
26
+ You can also access the *ID and Name fields of referenced records.
27
+ Example: for the payee of a Check, <tt>check.payee_id</tt> and
28
+ <tt>check.payee_name</tt>.
29
+
30
+ ==General Examples
31
+
32
+ # A very simple example, finding a single Customer by name
33
+ QBFC::session do | qb |
34
+ puts qb.customer('Customer Name').full_name
35
+ end
36
+
37
+ # Find all Customer, then return the first in the Array
38
+ # Next, find the first Customer only
39
+ QBFC::session do | qb |
40
+ customers = qb.customers.find(:all)
41
+ puts customers[0].full_name
42
+ puts qb.customers.find(:first).full_name
43
+ end
44
+
45
+ # Same as previous, but not using a block
46
+ sess = QBFC::Session.new
47
+ customers = QBFC::Customer.find(sess, :all)
48
+ puts customers[0].full_name
49
+ puts QBFC::Customer.find(sess, :first).full_name
50
+ sess.close
51
+
52
+ # Use a QBFC::Session object, but access the COM object
53
+ # more directly.
54
+ QBFC::session do | qb |
55
+ request_set = qb.CreateMsgSetRequest("US", 6, 0)
56
+ customer_query = request_set.AppendCustomerQueryRq
57
+ response = qb.DoRequests(request_set)
58
+ customer_set = response.ResponseList[0]
59
+ first_customer = customer_set.Detail[0]
60
+ puts first_customer.full_name
61
+ end
62
+
63
+ ==Alternatives
64
+
65
+ [QuickBooks for Ruby]
66
+ (docs: http://quickbooks.rubyforge.org,
67
+ homepage: http://rubyforge.org/projects/quickbooks)
68
+
69
+ This is a project with similar goals to QBFC-Ruby. I believe the approach
70
+ is creating Ruby classes that mirror the QuickBooks types and generate / parse
71
+ qbXML. In my opinion, this approach is more stable and flexible than what I'm
72
+ doing with QBFC-Ruby, but at the cost of slower development. (As in, QBFC-Ruby
73
+ was intended as a collection of quick and dirty shortcuts; QuickBooks for Ruby
74
+ seems to be intended as a much more *complete* project)
75
+
76
+ [QBFC/qbXML COM Objects]
77
+ Using the SDK directly is an option. Unless you need to use QBWC (QuickBooks
78
+ Web Connector) or have some other reason for *wanting* to us qbXML, I suggest
79
+ using QBFC. The SDK documents (see http://developer.intuit.com/ ) are pretty
80
+ easy to use and navigate.
81
+
82
+ There are some other libraries on rubyforge in early stages which I haven't
83
+ really explored.
84
+
85
+ Copyright (c) 2008 Jared E. Morgan, released under the MIT license
@@ -0,0 +1,84 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "qbfc"
8
+ gem.rubyforge_project = "qbfc"
9
+ gem.summary = "A wrapper around the QBFC COM object of the Quickbooks SDK"
10
+ gem.description = %q{QBFC-Ruby wraps the QBFC COM object of the QuickBooks
11
+ SDK, providing ease-of-use improvements, such as lower-case method names,
12
+ and find, save, delete, void, and create operations.}
13
+ gem.email = "jmorgan@morgancreative.net"
14
+ gem.homepage = "http://rubyforge.org/projects/qbfc/"
15
+ gem.authors = ["Jared Morgan"]
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+
19
+ rf = Jeweler::RubyforgeTasks.new
20
+ rf.remote_doc_path = ''
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+
28
+ QBFC_ROOT = File.dirname(__FILE__)
29
+
30
+ desc "Run all specs in spec/unit directory"
31
+ Spec::Rake::SpecTask.new(:spec) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.pattern = 'spec/unit/**/*_spec.rb'
34
+ spec.spec_opts = ['--options', "\"#{QBFC_ROOT}/spec/spec.opts\""]
35
+ end
36
+
37
+ namespace :spec do
38
+ desc "Run all specs in spec/unit directory with RCov"
39
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
40
+ spec.libs << 'lib' << 'spec'
41
+ spec.pattern = 'spec/unit/**/*_spec.rb'
42
+ spec.spec_opts = ['--options', "\"#{QBFC_ROOT}/spec/spec.opts\""]
43
+ spec.rcov = true
44
+ spec.rcov_opts = lambda do
45
+ IO.readlines("#{QBFC_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
46
+ end
47
+ end
48
+
49
+ desc "Run all specs in spec/integration directory"
50
+ Spec::Rake::SpecTask.new(:integration) do |spec|
51
+ spec.libs << 'lib' << 'spec'
52
+ spec.pattern = 'spec/integration/**/*_spec.rb'
53
+ spec.spec_opts = ['--options', "\"#{QBFC_ROOT}/spec/spec.opts\""]
54
+ end
55
+
56
+ desc "Run all specs in spec/integration directory with RCov"
57
+ Spec::Rake::SpecTask.new(:integration_rcov) do |spec|
58
+ spec.libs << 'lib' << 'spec'
59
+ spec.pattern = 'spec/unit/**/*_spec.rb'
60
+ spec.spec_opts = ['--options', "\"#{QBFC_ROOT}/spec/spec.opts\""]
61
+ spec.rcov = true
62
+ spec.rcov_opts = lambda do
63
+ IO.readlines("#{QBFC_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ task :default => :spec
70
+
71
+ require 'rake/rdoctask'
72
+ Rake::RDocTask.new do |rdoc|
73
+ if File.exist?('VERSION')
74
+ version = File.read('VERSION')
75
+ else
76
+ version = ""
77
+ end
78
+
79
+ rdoc.rdoc_dir = 'rdoc'
80
+ rdoc.title = "QBFC-Ruby #{version}"
81
+ rdoc.options << '--line-numbers' << '--inline-source'
82
+ rdoc.rdoc_files.include('README*')
83
+ rdoc.rdoc_files.include('lib/**/*.rb')
84
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,41 @@
1
+ require 'win32ole'
2
+ require 'time'
3
+
4
+ # ActiveSupport is used for camelize, singularize and similar String inflectors.
5
+ unless Object.const_defined?(:ActiveSupport)
6
+ gem 'activesupport'
7
+ require 'active_support'
8
+ end
9
+
10
+ module QBFC
11
+ VERSION = '0.3.0'
12
+
13
+ class << self
14
+
15
+ # Opens and yields a QBFC::Session
16
+ def session(*args, &block)
17
+ QBFC::Session::open(*args, &block)
18
+ end
19
+
20
+ # Generate classes.
21
+ # - +names+: Array of class names.
22
+ # - +superclass+: Superclass of classes to be generated.
23
+ # - +includes+: hash of Module => names of classes to include this module.
24
+ def generate(names, superclass, include_modules = {})
25
+ names.each do | class_name |
26
+ const_set(class_name, Class.new(superclass))
27
+ end
28
+
29
+ include_modules.each do | mod, classes |
30
+ classes.each do | class_name |
31
+ const_get(class_name).__send__(:include, mod)
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ %w{ ole_wrapper qbfc_const session request base element info report qb_collection qb_types }.each do |file|
40
+ require 'qbfc/' + file
41
+ end
@@ -0,0 +1,82 @@
1
+ module QBFC
2
+ # Base is the...um..."base" class from which Element, Info, and
3
+ # Report inherit. It defines methods that the three share.
4
+ class Base
5
+ class << self
6
+
7
+ # is_base_class? is used by Element and subclasses. It is included
8
+ # in Base because some Base methods may check for it.
9
+ def is_base_class? #:nodoc:
10
+ false
11
+ end
12
+
13
+ # Element::find and Info::get receive optional arguments which can include
14
+ # a Request object and/or an options Hash. <tt>parse_find_args</tt>
15
+ # gets these arguments into a set that is easier to deal with.
16
+ def parse_find_args(*args)
17
+ request = args[0].kind_of?(QBFC::Request) ? args[0].dup : nil
18
+ options = args[-1].kind_of?(Hash) ? args[-1].dup : {}
19
+
20
+ # base classes will need to pass a subset of options to
21
+ # the ChildClass.find . Also, the actually options to the
22
+ # BaseClass.find Request cannot include owner_id.
23
+ if is_base_class?
24
+ base_options = options.dup
25
+ base_options.delete(:conditions)
26
+ options.delete(:owner_id)
27
+ else
28
+ base_options = nil
29
+ end
30
+
31
+ return request, options, base_options
32
+ end
33
+
34
+ # A convenience method for creating and returning
35
+ # a Query Request for this class.
36
+ def create_query(sess)
37
+ QBFC::Request.new(sess, "#{qb_name}Query")
38
+ end
39
+
40
+ protected :parse_find_args, :create_query
41
+
42
+ # The QuickBooks name for this Element or Report.
43
+ # It typically matches the last part of class name.
44
+ # Used in determining names of Requests and other
45
+ # OLE methods.
46
+ def qb_name
47
+ self.name.split('::').last
48
+ end
49
+ end
50
+
51
+ # Create an instance of this Element or Report.
52
+ # - <tt>sess</tt>: An open QBFC::Session object that will recieve all requests
53
+ # - <tt>ole</tt>: An optional QBFC::OLEWrapper object representing
54
+ # a response to a QueryRq. It is unlikely that this will be used directly.
55
+ def initialize(sess, ole = nil)
56
+ @sess, @ole = sess, ole
57
+ @ole = QBFC::OLEWrapper.new(@ole) if @ole.kind_of?(WIN32OLE)
58
+ end
59
+
60
+ # List the methods of the OLE object
61
+ def ole_methods
62
+ @ole.ole_methods
63
+ end
64
+
65
+ # Check if the OLE object responds to a given method
66
+ def respond_to_ole?(symbol)
67
+ @ole.respond_to_ole?(symbol)
68
+ end
69
+
70
+ # Pass missing methods to OLEWrapper#qbfc_method_missing
71
+ # to handle checking if there is a related OLE method to run.
72
+ def method_missing(symbol, *params)
73
+ @ole.qbfc_method_missing(@sess, symbol, *params)
74
+ end
75
+
76
+ # Name of the QuickBooks Element or Query represented by this class.
77
+ def qb_name
78
+ self.class.qb_name
79
+ end
80
+ end
81
+ end
82
+
@@ -0,0 +1,243 @@
1
+ require 'qbfc/modifiable'
2
+
3
+ module QBFC
4
+
5
+ # An Element is a Transaction or a List; that is any QuickBooks objects that can
6
+ # be created, edited (possibly), deleted and read. Contrast to a Report or Info
7
+ # which are read-only.
8
+ #
9
+ # Inheritance from Element implies a set of shared methods, such as find, but the
10
+ # only shared implementation defined here is #custom, for getting custom field information.
11
+ class Element < Base
12
+
13
+ class << self
14
+ # Set that this is a "base class", one which is inherited,
15
+ # such as List, Transaction, Entity, or Terms.
16
+ # Base classes do not accept Add Requests, and their finders
17
+ # will return an instance of an inherited class.
18
+ def is_base_class
19
+ @is_base_class = true
20
+ end
21
+
22
+ # Check if this is a "base class" (see is_base_class)
23
+ def is_base_class?
24
+ @is_base_class ? true : false
25
+ end
26
+
27
+ # Find a QuickBooks record for the given session.
28
+ #
29
+ # session.[qb_name] aliases this functionality. The following
30
+ # pairs are equivalent:
31
+ #
32
+ # QBFC::Vendor.find(session, :first) <-> session.vendor
33
+ # QBFC::Vendor.find(session, "Supply Shop") <-> session.vendor("Supply Shop")
34
+ # QBFC::Vendor.find(session, :all) <-> session.vendors.find(:all)
35
+ #
36
+ # Find requires a +session+ (representing a QBFC::Session) and
37
+ # a +what+ argument. +what+ can be one of:
38
+ # - <tt>:first</tt> - finds the first record fitting any given conditions.
39
+ # - <tt>:finds</tt> - finds all records fitting any given conditions.
40
+ # - An unique identifier, such as a ListID, FullName, RefNumber or TxnID
41
+ #
42
+ # .find can also receive a optional Request object followed
43
+ # by an optional options Hash. Valid argument sets are:
44
+ #
45
+ # QBFC::Vendor.find(session, :first)
46
+ # QBFC::Vendor.find(session, :first, request)
47
+ # QBFC::Vendor.find(session, :first, request, options)
48
+ # QBFC::Vendor.find(session, :first, options)
49
+ #
50
+ # The options hash accepts the following:
51
+ # - <tt>:owner_id</tt>: One or more OwnerIDs, used in accessing
52
+ # custom fields (aka private data extensions).
53
+ # - <tt>:limit</tt>: The maximum number of records to be returned.
54
+ # - <tt>:include</tt>: Elements to include (see below for details)
55
+ # - <tt>:conditions</tt>: A hash of conditions (generally 'Filters' in
56
+ # QuickBooks SDK. See below:
57
+ #
58
+ # Additional options are planned, but not really supported in this version.
59
+ # Passing a Request object is the current recommended way of applying
60
+ # unsupported conditions (Filters) or other options to the Query Request.
61
+ #
62
+ # ==Include
63
+ #
64
+ # The :include option accepts an Array of elements to include in the
65
+ # return of the Query. The array may include either or both of elements
66
+ # that are additional to normal returns (such as Line Items, Linked
67
+ # Transactions) or elements that are normally included (to be added to the
68
+ # IncludeRetElementList).
69
+ #
70
+ # If elements are given that would be added to IncludeRetElementList, this
71
+ # limits the elements returned to *only* those included in the array.
72
+ #
73
+ # Another option is to give :all as the argument, which will always return
74
+ # as many elements as possible.
75
+ #
76
+ # @sess.checks.find(:all, :include => [:linked_txns]) -> Include linked transactions
77
+ # @sess.checks.find(:all, :include => [:txn_id]) -> Include +only+ TxnID
78
+ # @sess.checks.find(:all, :include => :all) ->
79
+ # Includes all elements, including LinkedTxns and LineItems.
80
+ #
81
+ # ==Conditions
82
+ #
83
+ # Conditions are dependent on the particular Request. See the QuickBooks
84
+ # SDK documentation for applicable filters for each Query Request. Note
85
+ # that not all Filters are supported.
86
+ #
87
+ # Typically the condition is given as :filter_name => value where
88
+ # +filter_name+ is the name of the filter less the word 'Filter' (see
89
+ # examples below).
90
+ #
91
+ # Here are some general rules:
92
+ #
93
+ # [List filters]
94
+ # These are filters that end in List. They take an Array of values.
95
+ #
96
+ # :ref_number_list => ["1001", "1003", "1003a"]
97
+ # :txn_id_list => ["123-456"]
98
+ #
99
+ # [Range Filters]
100
+ # Filters which take a range of values. These accept any
101
+ # object which responds to +first+ and +last+, such as a Range or Array.
102
+ # +first+ is used to set the From value, +last+ sets the To value. If a
103
+ # scalar value is given (or a single element Array), To is set and From
104
+ # is left empty. nil can also be given for either value.
105
+ #
106
+ # :txn_date_range => ['2008-01', nil]
107
+ # :txn_date_range => ['2008-01']
108
+ # :txn_date_range => '2008-01'
109
+ #
110
+ # :ref_number_range => "1001".."1003"
111
+ # :ref_number_range => ["1001", "1003"]
112
+ #
113
+ # [Reference Filters]
114
+ # Filters which reference another object (belongs to
115
+ # filters). These current only accept Name arguments, as a single value
116
+ # or an Array.
117
+ #
118
+ # :account => 'Checking'
119
+ # :entity => ['ABC Supplies', 'CompuStuff']
120
+ #
121
+ def find(sess, what, *args)
122
+ if what.kind_of?(String) # Single FullName or ListID
123
+ return find_by_unique_id(sess, what, *args)
124
+ end
125
+
126
+ # Setup q, options and base_options arguments
127
+ q, options, base_options = parse_find_args(*args)
128
+ q ||= create_query(sess)
129
+ ignore_base_class = options.kind_of?(Hash) ? options.delete(:ignore_base_class) : false
130
+
131
+ q.apply_options(options)
132
+
133
+ # QuickBooks only needs to return one record if .find is
134
+ # only returning a single record.
135
+ if what == :first && q.filter_available?
136
+ q.add_limit(1)
137
+ end
138
+
139
+ # Send the query so far to base_class_find if this is
140
+ # a base class to handle special base class functionality.
141
+ if is_base_class? && !ignore_base_class
142
+ return base_class_find(sess, what, q, base_options)
143
+ end
144
+
145
+ # Get response and return an Array, a QBFC::Element-inherited
146
+ # object, or nil, depending on <tt>what</tt> argument and whether
147
+ # the query returned any records.
148
+ list = q.response
149
+
150
+ if list.nil?
151
+ (what == :all) ? [] : nil
152
+ elsif what == :all
153
+ (0..(list.Count - 1)).collect { |i|
154
+ new(sess, list.GetAt(i))
155
+ }
156
+ else
157
+ new(sess, list.GetAt(0))
158
+ end
159
+ end
160
+
161
+ # Base classes can be used to find members of any of their
162
+ # inherited classes. Since QuickBooks has limited options
163
+ # for these type of queries (in particular, no custom fields
164
+ # are returned), an initial query is run which retrieves
165
+ # only IDs and the class type and then individual subsequent
166
+ # queries are run for each returned object.
167
+ def base_class_find(sess, what, q, options)
168
+ q.IncludeRetElementList.Add(self::ID_NAME)
169
+ list = q.response
170
+
171
+ if list.nil?
172
+ (what == :all) ? [] : nil
173
+ else
174
+ ary = (0..(list.Count - 1)).collect { |i|
175
+ element = list.GetAt(i)
176
+ ret_name = element.ole_methods.detect { |m|
177
+ m.to_s =~ /(.*)Ret\Z/ && element.send(m.to_s)
178
+ }.to_s
179
+ ret_class = QBFC::const_get($1)
180
+ ret_class.find(sess, element.send(ret_name).send(ret_class::ID_NAME).GetValue, options.dup)
181
+ }
182
+
183
+ if what == :all
184
+ ary
185
+ else
186
+ ary[0]
187
+ end
188
+ end
189
+ end
190
+
191
+ private :base_class_find, :is_base_class
192
+
193
+ end
194
+
195
+ is_base_class
196
+
197
+ # Extends Base#initialize to allow for an Add Request if
198
+ # .new is called directly (instead of by .find)
199
+ def initialize(sess, ole_object = nil)
200
+ super
201
+
202
+ if @ole.nil?
203
+ add_rq = QBFC::Request.new(sess, "#{qb_name}Add")
204
+ @ole = QBFC::OLEWrapper.new(add_rq.ole_object)
205
+ @new_record = true
206
+ @setter = add_rq
207
+ end
208
+ end
209
+
210
+ # Is this a new record, i.e. are we doing an Add Request?
211
+ def new_record?
212
+ @new_record ? true : false
213
+ end
214
+
215
+ # Access information from a custom field.
216
+ def custom(field_name, owner_id = '0')
217
+ if @ole.DataExtRetList
218
+ @ole.data_ext.each do |field|
219
+ if field.data_ext_name == field_name && field.owner_id == owner_id.to_s
220
+ return field.data_ext_value
221
+ end
222
+ end
223
+ end
224
+
225
+ return nil
226
+ end
227
+
228
+ # Save (create or update) this record
229
+ def save
230
+ if @setter
231
+ @setter.submit
232
+ else
233
+ raise NotSavableError, "This record cannot be saved (Probably because it does not support Mod Requests)."
234
+ end
235
+ end
236
+
237
+ end
238
+ end
239
+
240
+ # Require subclass files
241
+ %w{ list transaction }.each do |file|
242
+ require 'qbfc/' + file
243
+ end