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,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