jm81-qbfc 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. data/.gitignore +11 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +85 -0
  4. data/Rakefile +81 -0
  5. data/VERSION +1 -0
  6. data/init.rb +3 -0
  7. data/install.rb +1 -0
  8. data/lib/qbfc.rb +41 -0
  9. data/lib/qbfc/base.rb +82 -0
  10. data/lib/qbfc/element.rb +243 -0
  11. data/lib/qbfc/entities/generated.rb +8 -0
  12. data/lib/qbfc/entity.rb +11 -0
  13. data/lib/qbfc/info.rb +42 -0
  14. data/lib/qbfc/infos/generated.rb +9 -0
  15. data/lib/qbfc/item.rb +29 -0
  16. data/lib/qbfc/items/generated.rb +11 -0
  17. data/lib/qbfc/list.rb +84 -0
  18. data/lib/qbfc/lists/account.rb +24 -0
  19. data/lib/qbfc/lists/generated.rb +15 -0
  20. data/lib/qbfc/lists/qb_class.rb +25 -0
  21. data/lib/qbfc/modifiable.rb +31 -0
  22. data/lib/qbfc/ole_wrapper.rb +201 -0
  23. data/lib/qbfc/qb_collection.rb +26 -0
  24. data/lib/qbfc/qb_types.rb +18 -0
  25. data/lib/qbfc/qbfc_const.rb +14 -0
  26. data/lib/qbfc/report.rb +95 -0
  27. data/lib/qbfc/reports/aging.rb +13 -0
  28. data/lib/qbfc/reports/budget_summary.rb +13 -0
  29. data/lib/qbfc/reports/custom_detail.rb +9 -0
  30. data/lib/qbfc/reports/custom_summary.rb +9 -0
  31. data/lib/qbfc/reports/general_detail.rb +44 -0
  32. data/lib/qbfc/reports/general_summary.rb +33 -0
  33. data/lib/qbfc/reports/job.rb +14 -0
  34. data/lib/qbfc/reports/payroll_detail.rb +13 -0
  35. data/lib/qbfc/reports/payroll_summary.rb +13 -0
  36. data/lib/qbfc/reports/rows.rb +51 -0
  37. data/lib/qbfc/reports/time.rb +12 -0
  38. data/lib/qbfc/request.rb +295 -0
  39. data/lib/qbfc/session.rb +147 -0
  40. data/lib/qbfc/terms.rb +10 -0
  41. data/lib/qbfc/terms/generated.rb +10 -0
  42. data/lib/qbfc/transaction.rb +110 -0
  43. data/lib/qbfc/transactions/generated.rb +25 -0
  44. data/lib/qbfc/voidable.rb +11 -0
  45. data/spec/fixtures/test.lgb +0 -0
  46. data/spec/fixtures/test.qbw +0 -0
  47. data/spec/fixtures/test.qbw.TLG +0 -0
  48. data/spec/integration/add_spec.rb +31 -0
  49. data/spec/integration/base_spec.rb +18 -0
  50. data/spec/integration/belongs_to_spec.rb +64 -0
  51. data/spec/integration/company_spec.rb +30 -0
  52. data/spec/integration/conditions_spec.rb +59 -0
  53. data/spec/integration/customer_spec.rb +46 -0
  54. data/spec/integration/element_finders_spec.rb +20 -0
  55. data/spec/integration/quick_test.rb +31 -0
  56. data/spec/integration/request_options_spec.rb +68 -0
  57. data/spec/rcov.opts +1 -0
  58. data/spec/spec.opts +6 -0
  59. data/spec/spec_helper.rb +62 -0
  60. data/spec/unit/base_spec.rb +138 -0
  61. data/spec/unit/element_finder_spec.rb +185 -0
  62. data/spec/unit/element_spec.rb +108 -0
  63. data/spec/unit/entities/generated_spec.rb +18 -0
  64. data/spec/unit/entity_spec.rb +18 -0
  65. data/spec/unit/info/generated_spec.rb +12 -0
  66. data/spec/unit/info_spec.rb +48 -0
  67. data/spec/unit/item_spec.rb +33 -0
  68. data/spec/unit/items/generated_spec.rb +16 -0
  69. data/spec/unit/list_finders_spec.rb +129 -0
  70. data/spec/unit/list_spec.rb +86 -0
  71. data/spec/unit/lists/account_spec.rb +20 -0
  72. data/spec/unit/lists/generated_spec.rb +15 -0
  73. data/spec/unit/lists/qb_class_spec.rb +9 -0
  74. data/spec/unit/modifiable_spec.rb +84 -0
  75. data/spec/unit/ole_wrapper_spec.rb +337 -0
  76. data/spec/unit/qb_collection_spec.rb +13 -0
  77. data/spec/unit/qbfc_const_spec.rb +10 -0
  78. data/spec/unit/qbfc_spec.rb +10 -0
  79. data/spec/unit/report_spec.rb +12 -0
  80. data/spec/unit/request_query_survey.txt +48 -0
  81. data/spec/unit/request_spec.rb +486 -0
  82. data/spec/unit/session_spec.rb +144 -0
  83. data/spec/unit/terms/generated_spec.rb +14 -0
  84. data/spec/unit/terms_spec.rb +18 -0
  85. data/spec/unit/transaction_finders_spec.rb +125 -0
  86. data/spec/unit/transaction_spec.rb +94 -0
  87. data/spec/unit/transactions/generated_spec.rb +20 -0
  88. data/spec/unit/voidable_spec.rb +32 -0
  89. data/tasks/qbfc_tasks.rake +4 -0
  90. data/uninstall.rb +1 -0
  91. metadata +180 -0
@@ -0,0 +1,26 @@
1
+ module QBFC
2
+
3
+ # A QBCollection object is used as an intermediate object when doing finds,
4
+ # for example, in <tt>qb_session.customers.find(:all)</tt>, +customers+
5
+ # returns a QBCollection instance which then is sent the find call.
6
+ # The instance sends the find method on to the appropriate Class.
7
+ # The reason for having this intermediate class is to be able to
8
+ # pass a reference to the Session to the find method
9
+ # (or other class method).
10
+ #
11
+ # There's probably no reason to use this class directly.
12
+ class QBCollection
13
+
14
+ # +sess+ is a QBFC::Session object, +class_name+ is the name of
15
+ # a class descended from QBFC::Base.
16
+ def initialize(sess, class_name)
17
+ @sess = sess
18
+ @klass = QBFC::const_get(class_name)
19
+ end
20
+
21
+ # Send any missing methods to the class, along with the +Session+ object
22
+ def method_missing(symbol, *params) #:nodoc:
23
+ @klass.send(symbol, @sess, *params)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ # This file sets up the classes for QuickBooks entities, transactions and
2
+ # reports that are not setup elsewhere (at this point, this file is only used
3
+ # for "weird" classes.
4
+
5
+ # Very non-standard elements. I haven't yet formed an approach to dealing
6
+ # with these; I leave them here as a reminder.
7
+ QBFC_NON_STANDARD_TYPES = %w{ DataExt DataExtDef DataEventRecoveryInfo ItemAssembliesCanBuild}
8
+
9
+ # Query types support Query requests only and return an itemized list of some sort;
10
+ # most of these may be integrated as special finders for their types.
11
+ QBFC_QUERY_TYPES = %w{BillToPay ListDeleted ReceivePaymentToDeposit Template TxnDeleted}
12
+
13
+ module QBFC
14
+ # Create QBElement classes
15
+ (QBFC_NON_STANDARD_TYPES + QBFC_QUERY_TYPES).uniq.each do | qb_element_name |
16
+ const_set(qb_element_name, Class.new(Base))
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # QBFC_CONST contains constants defined by QBFC6.QBSessionManager.
2
+ # For example:
3
+ #
4
+ # QBFC_CONST::DmToday # => 1 (for DateMacro)
5
+ #
6
+ # The constants defined in the SDK documents all begin with a lower case letter.
7
+ # In contrast, WIN32OLE capitalizes the constants to follow Ruby naming conventions:
8
+ #
9
+ # QBFC_CONST::DmToday instead of QBFC_CONST::dmToday
10
+
11
+ module QBFC_CONST
12
+ end
13
+
14
+ WIN32OLE.const_load(WIN32OLE.new('QBFC6.QBSessionManager'), QBFC_CONST)
@@ -0,0 +1,95 @@
1
+ module QBFC
2
+ # This class is EXPERIMENTAL!
3
+ # This is a first stab at working with Reports.
4
+ class Report < Base
5
+ class << self
6
+ # Return the class that a report belongs to. Takes a string (or symbol) of
7
+ # the report name. I could see refactoring this to use constants, later.
8
+ def get_class(report_name)
9
+ report_name = report_name.to_s
10
+ CLASSES.find {|klass| klass::REPORTS.include?(report_name)}
11
+ end
12
+
13
+ # Run a query to retrieve a report. Typically called by +new+. See +new+
14
+ # for arguments.
15
+ def query(sess, name, *args)
16
+ # Setup q, options and base_options arguments (base_options is not used)
17
+ q, options, base_options = parse_find_args(*args)
18
+ q ||= create_query(sess)
19
+ q.apply_options(options)
20
+ q.send(qb_name + 'Type').
21
+ SetValue(QBFC_CONST::const_get(self::REPORT_TYPE_PREFIX + name))
22
+ q.response
23
+ end
24
+
25
+ # The QuickBooks name for this Report.
26
+ # It typically matches the last part of class name, plus 'Report'
27
+ # Used in determining names of Requests and other OLE methods.
28
+ def qb_name
29
+ self.name.split('::').last + 'Report'
30
+ end
31
+
32
+ # Get a new Report.
33
+ # This is roughly equivalent with QBFC::Element::find.
34
+ # - <tt>sess</tt>: An open QBFC::Session object that will recieve all requests.
35
+ # - <tt>name</tt>: The name of the report.
36
+ # - <tt>args</tt>: TODO.
37
+ def get(sess, name, *args)
38
+ klass = get_class(name)
39
+ klass.new(sess, klass.query(sess, name, *args))
40
+ end
41
+ end
42
+
43
+ def rows
44
+ @rows ||= QBFC::Reports::Rows::parse(@ole.report_data)
45
+ end
46
+
47
+ def data
48
+ rows[:data]
49
+ end
50
+
51
+ def subtotals
52
+ rows[:subtotals]
53
+ end
54
+
55
+ def totals
56
+ rows[:totals]
57
+ end
58
+
59
+ def text_rows
60
+ rows[:text]
61
+ end
62
+
63
+ def cell(row_name, col_name)
64
+ rows[:data][row_name][col_for(col_name)]
65
+ end
66
+
67
+ def col_for(name)
68
+ @ole.col_descs.each do |col|
69
+ col.col_titles.each do |title|
70
+ if title.value
71
+ if (title.value.GetValue() == name)
72
+ return col.colID.GetValue().to_i - 1
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ # Require subclass files
82
+ Dir.new(File.dirname(__FILE__) + '/reports').each do |file|
83
+ require('qbfc/reports/' + File.basename(file)) if File.extname(file) == ".rb"
84
+ end
85
+
86
+ module QBFC
87
+ class Report < Base
88
+ # Set up CLASSES constant now that the referenced classes are loaded.
89
+ CLASSES = [QBFC::Reports::Aging, QBFC::Reports::BudgetSummary,
90
+ QBFC::Reports::CustomDetail, QBFC::Reports::CustomSummary,
91
+ QBFC::Reports::GeneralDetail, QBFC::Reports::GeneralSummary,
92
+ QBFC::Reports::Job, QBFC::Reports::PayrollDetail,
93
+ QBFC::Reports::PayrollSummary, QBFC::Reports::Time]
94
+ end
95
+ end
@@ -0,0 +1,13 @@
1
+ module QBFC
2
+ module Reports
3
+ class Aging < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Art'
5
+
6
+ REPORTS = %w{APAgingDetail
7
+ APAgingSummary
8
+ ARAgingDetail
9
+ ARAgingSummary
10
+ CollectionsReport}
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module QBFC
2
+ module Reports
3
+ class BudgetSummary < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Bsrt'
5
+
6
+ REPORTS = %w{BalanceSheetBudgetOverview
7
+ BalanceSheetBudgetVsActual
8
+ ProfitAndLossBudgetOverview
9
+ ProfitAndLossBudgetPerformance
10
+ ProfitAndLossBudgetVsActual}
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module QBFC
2
+ module Reports
3
+ class CustomDetail < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Cdrt'
5
+
6
+ REPORTS = %w{CustomTxnDetail}
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module QBFC
2
+ module Reports
3
+ class CustomSummary < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Csrt'
5
+
6
+ REPORTS = %w{CustomSummary}
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ module QBFC
2
+ module Reports
3
+ class GeneralDetail < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Gdrt'
5
+
6
+ REPORTS = %w{1099Detail
7
+ AuditTrail
8
+ BalanceSheetDetail
9
+ CheckDetail
10
+ CustomerBalanceDetail
11
+ DepositDetail
12
+ EstimatesByJob
13
+ ExpenseByVendorDetail
14
+ GeneralLedger
15
+ IncomeByCustomerDetail
16
+ IncomeTaxDetail
17
+ InventoryValuationDetail
18
+ JobProgressInvoicesVsEstimates
19
+ Journal
20
+ MissingChecks
21
+ OpenInvoices
22
+ OpenPOs
23
+ OpenPOsByJob
24
+ OpenSalesOrderByCustomer
25
+ OpenSalesOrderByItem
26
+ PendingSales
27
+ ProfitAndLossDetail
28
+ PurchaseByItemDetail
29
+ PurchaseByVendorDetail
30
+ SalesByCustomerDetail
31
+ SalesByItemDetail
32
+ SalesByRepDetail
33
+ TxnDetailByAccount
34
+ TxnListByCustomer
35
+ TxnListByDate
36
+ TxnListByVendor
37
+ UnpaidBillsDetail
38
+ UnbilledCostsByJob
39
+ VendorBalanceDetail}
40
+ end
41
+ end
42
+ end
43
+
44
+
@@ -0,0 +1,33 @@
1
+ module QBFC
2
+ module Reports
3
+ class GeneralSummary < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Gsrt'
5
+
6
+ REPORTS = %w{BalanceSheetPrevYearComp
7
+ BalanceSheetStandard
8
+ BalanceSheetSummary
9
+ CustomerBalanceSummary
10
+ ExpenseByVendorSummary
11
+ IncomeByCustomerSummary
12
+ InventoryStockStatusByItem
13
+ InventoryStockStatusByVendor
14
+ IncomeTaxSummary
15
+ InventoryValuationSummary
16
+ PhysicalInventoryWorksheet
17
+ ProfitAndLossByClass
18
+ ProfitAndLossByJob
19
+ ProfitAndLossPrevYearComp
20
+ ProfitAndLossStandard
21
+ ProfitAndLossYTDComp
22
+ PurchaseByItemSummary
23
+ PurchaseByVendorSummary
24
+ SalesByCustomerSummary
25
+ SalesByItemSummary
26
+ SalesByRepSummary
27
+ SalesTaxLiability
28
+ SalesTaxRevenueSummary
29
+ TrialBalance
30
+ VendorBalanceSummary}
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ module QBFC
2
+ module Reports
3
+ class Job < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Jrt'
5
+
6
+ REPORTS = %w{ItemEstimatesVsActuals
7
+ ItemProfitability
8
+ JobEstimatesVsActualsDetail
9
+ JobEstimatesVsActualsSummary
10
+ JobProfitabilityDetail
11
+ JobProfitabilitySummary}
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module QBFC
2
+ module Reports
3
+ class PayrollDetail < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Pdrt'
5
+
6
+ REPORTS = %w{EmployeeStateTaxesDetail
7
+ PayrollItemDetail
8
+ PayrollReviewDetail
9
+ PayrollTransactionDetail
10
+ PayrollTransactionsByPayee}
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module QBFC
2
+ module Reports
3
+ class PayrollSummary < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Psrt'
5
+
6
+ REPORTS = %w{EmployeeEarningsSummary
7
+ PayrollLiabilityBalances
8
+ PayrollSummary}
9
+ end
10
+ end
11
+ end
12
+
13
+
@@ -0,0 +1,51 @@
1
+ module QBFC
2
+ module Reports
3
+ class Rows < Array
4
+ class << self
5
+ def get_row(cols)
6
+ ret = []
7
+ if cols
8
+ cols.each do |col|
9
+ ret << col.value.getValue
10
+ end
11
+ end
12
+ ret
13
+ end
14
+
15
+ def parse(report_data)
16
+ data = []
17
+ subtotals = []
18
+ totals = []
19
+ text = []
20
+
21
+ report_data.o_r_report_datas.each do |d|
22
+ if d.DataRow
23
+ data << get_row(d.DataRow.col_datas)
24
+ elsif d.TextRow
25
+ text << d.TextRow.value.getValue
26
+ elsif d.SubtotalRow
27
+ subtotals << get_row(d.SubtotalRow.col_datas)
28
+ elsif d.TotalRow
29
+ totals << get_row(d.TotalRow.col_datas)
30
+ end
31
+
32
+ end
33
+
34
+ return {:data => new(data),
35
+ :subtotals => new(subtotals),
36
+ :text => text,
37
+ :totals => new(totals)}
38
+ end
39
+ end
40
+
41
+ def [](value)
42
+ if value.kind_of? String
43
+ self.detect{ |entry| entry[0] == value }
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,12 @@
1
+ module QBFC
2
+ module Reports
3
+ class Time < QBFC::Report
4
+ REPORT_TYPE_PREFIX = 'Trt'
5
+
6
+ REPORTS = %w{TimeByItem
7
+ TimeByJobDetail
8
+ TimeByJobSummary
9
+ TimeByName}
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,295 @@
1
+ module QBFC
2
+ # A QBFC::Request handles creating and sending a Request, including creating
3
+ # the RequestSet. Most often, RubyQBFC classes create and execute the Request
4
+ # internally, however, the Base.find, for example, accepts a QBFC::Request
5
+ # object as an argument if greater control is needed.
6
+ #
7
+ # The WIN32OLE Request object is wrapped in OLEWrapper, so Ruby-esque methods
8
+ # can be used.
9
+ #
10
+ # req = QBFC::Request.new(qb_session, "CustomerQuery").
11
+ # or_customer_list_query.customer_list_filter.max_returned = 2
12
+ # puts req.response
13
+ class Request
14
+
15
+ # <tt>session</tt> is a QBFC::Session object (or a Session object not created through Ruby QBFC)
16
+ # <tt>request_type</tt> is the name of the request, not including trailing 'Rq',
17
+ # e.g. 'CustomerQuery', 'CustomerMod'
18
+ def initialize(sess, request_type, country = 'US', major_version = 6, minor_version = 0)
19
+ @sess = sess
20
+
21
+ begin
22
+ @request_set = sess.CreateMsgSetRequest(country, major_version, minor_version)
23
+ rescue WIN32OLERuntimeError => error
24
+ if error.to_s =~ /error code:8004030A/
25
+ raise QBFC::QBXMLVersionError, "Unsupported qbXML version"
26
+ else
27
+ raise
28
+ end
29
+ end
30
+
31
+ begin
32
+ @name = request_type
33
+ @request = @request_set.send("Append#{request_type}Rq")
34
+ rescue WIN32OLERuntimeError => error
35
+ if error.to_s =~ /error code:0x80020006/
36
+ raise QBFC::UnknownRequestError, "Unknown request name '#{request_type}'"
37
+ else
38
+ raise
39
+ end
40
+ end
41
+ end
42
+
43
+ # Submit the requests. This returns the full (not wrapped) response object.
44
+ def submit
45
+ @sess.DoRequests(@request_set)
46
+ end
47
+
48
+ # Submit the Request and return the response Detail, wrapped in OLEWrapper (unless nil).
49
+ # The response does not include any MsgSetResponse attributes.
50
+ def response
51
+ submit.ResponseList.GetAt(0).Detail
52
+ end
53
+
54
+ # Is this Query for a Report? The 'conditions' are treated differently
55
+ def for_report?
56
+ @name =~ /Report$/
57
+ end
58
+
59
+ # Get the OR*Query object of the given Request
60
+ # For example, the ORListQuery
61
+ def query
62
+ query_name = @request.ole_methods.detect{|m| m.to_s =~ /Query\Z/}
63
+ return nil if query_name.nil?
64
+ @request.send(query_name.to_s.to_sym)
65
+ end
66
+
67
+ # Get the *Filter object of the given Request
68
+ # For example, the ListFilter
69
+ def filter
70
+ q = self.query
71
+ return nil if q.nil?
72
+ filter_name = q.ole_methods.detect{|m| m.to_s =~ /Filter\Z/}
73
+ return nil if filter_name.nil?
74
+ q.send(filter_name.to_s.to_sym)
75
+ end
76
+
77
+ # Returns where the filter is available for use. That is, that
78
+ # none of the query options other than filter have been used
79
+ def filter_available?
80
+ # -1 = unused, 2 = Filter used
81
+ self.query.ole_object.ortype == -1 ||
82
+ self.query.ole_object.ortype == 2
83
+ end
84
+
85
+ # Applies options from a Hash. This method is generally used by find methods
86
+ # (see Element.find for details)
87
+ def apply_options(options)
88
+ if options.kind_of? Hash
89
+ conditions = options.delete(:conditions) || {}
90
+
91
+ conditions.each do | c_name, c_value |
92
+ c_name = c_name.to_s
93
+
94
+ case c_name
95
+ when /list\Z/i
96
+ # List filters
97
+ list = query.__send__(c_name.camelize)
98
+ c_value = [c_value] unless c_value.kind_of?(Array)
99
+ c_value.each { |i| list.Add(i) }
100
+ when /range\Z/i
101
+ # Range filters
102
+ c_value = parse_range_value(c_value)
103
+ range_filter = filter_for(c_name)
104
+ range_name = c_name.match(/(.*)_range\Z/i)[1]
105
+ if range_name == 'modified_date'
106
+ # Modified Date Range use the IQBDateTimeType which requires a\
107
+ # boolean 'asDateOnly' value.
108
+ range_filter.__send__("from_#{range_name}=", c_value.first, true) if c_value.first
109
+ range_filter.__send__("to_#{range_name}=", c_value.last, true) if c_value.last
110
+ else
111
+ range_filter.__send__("from_#{range_name}=", c_value.first) if c_value.first
112
+ range_filter.__send__("to_#{range_name}=", c_value.last) if c_value.last
113
+ end
114
+ when /status\Z/i
115
+ # Status filters
116
+ filter.__send__("#{c_name}=", c_value)
117
+ else
118
+ # Reference filters - Only using FullNameList for now
119
+ ref_filter = filter_for(c_name)
120
+ c_value = [c_value] unless c_value.respond_to?(:each)
121
+ c_value.each do | val |
122
+ ref_filter.FullNameList.Add(val)
123
+ end
124
+ end
125
+ end
126
+
127
+ add_owner_ids(options.delete(:owner_id))
128
+ add_limit(options.delete(:limit))
129
+ add_includes(options.delete(:include))
130
+
131
+ options.each do |key, value|
132
+ self.send(key.to_s.camelize).SetValue(value)
133
+ end
134
+ end
135
+ end
136
+
137
+ # Add one or more OwnerIDs to the Request. Used in retrieving
138
+ # custom fields (aka private data extensions).
139
+ # Argument should be a single ID or an Array of IDs.
140
+ def add_owner_ids(ids)
141
+ return if ids.nil?
142
+
143
+ ids = [ids] unless ids.respond_to?(:each)
144
+ ids.each do | id |
145
+ @request.OwnerIDList.Add(id)
146
+ end
147
+ end
148
+
149
+ # Set MaxReturned to limit the number of records returned.
150
+ def add_limit(limit)
151
+ filter.max_returned = limit if limit
152
+ end
153
+
154
+ # add_includes accepts an Array of elements to include in the return of the
155
+ # Request. The array may include either or both of elements that are
156
+ # additional to normal returns (such as Line Items, Linked Transactions)
157
+ # or elements that are normally included (to be added to the
158
+ # IncludeRetElementList).
159
+ #
160
+ # If elements are given that would be added to IncludeRetElementList, this
161
+ # limits the elements returned to *only* those included in the array.
162
+ #
163
+ # Another option is to give :all as the argument, which will always return
164
+ # as many elements as possible.
165
+ #
166
+ # add_includes is typically called by #apply_options, typically called
167
+ # from Element.find, as seen in the examples below:
168
+ #
169
+ # @sess.checks.find(:all, :include => [:linked_txns]) -> Include linked transactions
170
+ # @sess.checks.find(:all, :include => [:txn_id]) -> Include +only+ TxnID
171
+ # @sess.checks.find(:all, :include => :all) ->
172
+ # Includes all elements, including LinkedTxns and LineItems.
173
+ def add_includes(inc)
174
+ return if inc.nil?
175
+
176
+ inc = [inc] if (!inc.respond_to?(:each) || inc.kind_of?(String))
177
+
178
+ if inc.include?(:all)
179
+ ole_methods.each do |m|
180
+ m = m.to_s
181
+ if m =~ /\AInclude/ && m != 'IncludeRetElementList'
182
+ @request.__send__("#{m.underscore}=", true)
183
+ end
184
+ end
185
+ return
186
+ end
187
+
188
+ inc.each do |item|
189
+ cam_item = item.to_s.camelize.gsub(/Id/, "ID")
190
+ if @request.respond_to_ole?("Include#{cam_item}")
191
+ @request.__send__("include_#{item}=", true)
192
+ else
193
+ @request.IncludeRetElementList.Add(cam_item)
194
+ end
195
+ end
196
+ end
197
+
198
+ # Parse a value for a range filter. This can be a Range, a one or more
199
+ # element Array or a single value. For a single value or one-element array
200
+ # value#last should return nil. The calling method (#apply_options) will
201
+ # call value#first and value#last to set the from and to values
202
+ # respectively.
203
+ def parse_range_value(value)
204
+ value << nil if value.kind_of?(Array) && value.length == 1
205
+ value = [value, nil] if (value.kind_of?(String) || !value.respond_to?(:first))
206
+ value
207
+ end
208
+
209
+ # Determine and return the Filter object for the given filter name, dealing
210
+ # with OR's and other "weird" circumstances.
211
+ # NB: This method may well miss some situations. Hopefully, it will become
212
+ # more complete in time.
213
+ def filter_for(name)
214
+ name = name.camelize + "Filter"
215
+ f = nil
216
+
217
+ # List queries place the modified_date_range directly in the filter
218
+ if name == 'ModifiedDateRangeFilter'
219
+ return filter if filter.respond_to_ole?('FromModifiedDate')
220
+ end
221
+
222
+ # Report Periods are more or less special circumstance
223
+ if name =~ /^Report[Period|Date]/
224
+ return @request.ORReportPeriod.ReportPeriod
225
+ end
226
+
227
+ # Try to get the filter directly
228
+ if filter.respond_to_ole?(name)
229
+ f = filter.send(name)
230
+ end
231
+
232
+ # Transaction Date Range Filters change name.
233
+ if name == 'TxnDateRangeFilter' &&
234
+ filter.respond_to_ole?('TransactionDateRangeFilter')
235
+ f = filter.send("TransactionDateRangeFilter").
236
+ send("ORTransactionDateRangeFilter").
237
+ send("TxnDateRange")
238
+ end
239
+
240
+ # Check if this is within an 'OR'
241
+ if filter.respond_to_ole?("OR#{name}")
242
+ f = filter.send("OR#{name}").send(name)
243
+ elsif filter.respond_to_ole?("OR#{name.gsub(/Range/, '')}")
244
+ f = filter.send("OR#{name.gsub(/Range/, '')}").send(name)
245
+ end
246
+
247
+ # DateRange OR's
248
+ if filter.respond_to_ole?("ORDateRangeFilter") && name =~ /DateRange/i
249
+ f = filter.send("ORDateRangeFilter").send(name)
250
+ end
251
+
252
+ # It might have a nested OR
253
+ if f && f.respond_to_ole?("OR#{name}")
254
+ f = f.send("OR#{name}")
255
+ end
256
+
257
+ # Ranges might have another step, with the 'Range' removed.
258
+ if f && f.respond_to_ole?(name.gsub(/Range/, ''))
259
+ f = f.send(name.gsub(/Range/, ''))
260
+ end
261
+
262
+ return f
263
+ end
264
+
265
+ private :parse_range_value, :filter_for
266
+
267
+ # Send missing methods to @ole_object (OLEWrapper)
268
+ def method_missing(symbol, *params) #:nodoc:
269
+ @request.qbfc_method_missing(@sess, symbol, *params)
270
+ end
271
+
272
+ # Return Array of ole_methods for request WIN32OLE object.
273
+ # This is mostly useful for debugging.
274
+ def ole_methods
275
+ @request.ole_methods
276
+ end
277
+
278
+ # Return XML for the request WIN32OLE object.
279
+ # This is mostly useful for debugging.
280
+ def to_xml
281
+ @request_set.ToXMLString
282
+ end
283
+
284
+ # Submit Request and return full response as XML.
285
+ # This is mostly useful for debugging.
286
+ def response_xml
287
+ @sess.DoRequests(@request_set).ToXMLString
288
+ end
289
+
290
+ # Return actual WIN32OLE object
291
+ def ole_object
292
+ @request.ole_object
293
+ end
294
+ end
295
+ end