qbfc 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +85 -0
- data/Rakefile +84 -0
- data/VERSION +1 -0
- data/lib/qbfc.rb +41 -0
- data/lib/qbfc/base.rb +82 -0
- data/lib/qbfc/element.rb +243 -0
- data/lib/qbfc/entities/generated.rb +8 -0
- data/lib/qbfc/entity.rb +11 -0
- data/lib/qbfc/info.rb +42 -0
- data/lib/qbfc/infos/generated.rb +9 -0
- data/lib/qbfc/item.rb +29 -0
- data/lib/qbfc/items/generated.rb +11 -0
- data/lib/qbfc/list.rb +84 -0
- data/lib/qbfc/lists/account.rb +24 -0
- data/lib/qbfc/lists/generated.rb +15 -0
- data/lib/qbfc/lists/qb_class.rb +25 -0
- data/lib/qbfc/modifiable.rb +31 -0
- data/lib/qbfc/ole_wrapper.rb +201 -0
- data/lib/qbfc/qb_collection.rb +26 -0
- data/lib/qbfc/qb_types.rb +18 -0
- data/lib/qbfc/qbfc_const.rb +14 -0
- data/lib/qbfc/report.rb +95 -0
- data/lib/qbfc/reports/aging.rb +13 -0
- data/lib/qbfc/reports/budget_summary.rb +13 -0
- data/lib/qbfc/reports/custom_detail.rb +9 -0
- data/lib/qbfc/reports/custom_summary.rb +9 -0
- data/lib/qbfc/reports/general_detail.rb +44 -0
- data/lib/qbfc/reports/general_summary.rb +33 -0
- data/lib/qbfc/reports/job.rb +14 -0
- data/lib/qbfc/reports/payroll_detail.rb +13 -0
- data/lib/qbfc/reports/payroll_summary.rb +13 -0
- data/lib/qbfc/reports/rows.rb +51 -0
- data/lib/qbfc/reports/time.rb +12 -0
- data/lib/qbfc/request.rb +295 -0
- data/lib/qbfc/session.rb +147 -0
- data/lib/qbfc/terms.rb +10 -0
- data/lib/qbfc/terms/generated.rb +10 -0
- data/lib/qbfc/transaction.rb +110 -0
- data/lib/qbfc/transactions/generated.rb +25 -0
- data/lib/qbfc/voidable.rb +11 -0
- data/qbfc.gemspec +166 -0
- data/spec/fixtures/test.lgb +0 -0
- data/spec/fixtures/test.qbw +0 -0
- data/spec/fixtures/test.qbw.TLG +0 -0
- data/spec/integration/add_spec.rb +31 -0
- data/spec/integration/base_spec.rb +18 -0
- data/spec/integration/belongs_to_spec.rb +64 -0
- data/spec/integration/company_spec.rb +30 -0
- data/spec/integration/conditions_spec.rb +59 -0
- data/spec/integration/customer_spec.rb +46 -0
- data/spec/integration/element_finders_spec.rb +20 -0
- data/spec/integration/quick_test.rb +31 -0
- data/spec/integration/request_options_spec.rb +68 -0
- data/spec/rcov.opts +1 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +62 -0
- data/spec/unit/base_spec.rb +138 -0
- data/spec/unit/element_finder_spec.rb +185 -0
- data/spec/unit/element_spec.rb +108 -0
- data/spec/unit/entities/generated_spec.rb +18 -0
- data/spec/unit/entity_spec.rb +18 -0
- data/spec/unit/info/generated_spec.rb +12 -0
- data/spec/unit/info_spec.rb +48 -0
- data/spec/unit/item_spec.rb +33 -0
- data/spec/unit/items/generated_spec.rb +16 -0
- data/spec/unit/list_finders_spec.rb +129 -0
- data/spec/unit/list_spec.rb +86 -0
- data/spec/unit/lists/account_spec.rb +20 -0
- data/spec/unit/lists/generated_spec.rb +15 -0
- data/spec/unit/lists/qb_class_spec.rb +9 -0
- data/spec/unit/modifiable_spec.rb +84 -0
- data/spec/unit/ole_wrapper_spec.rb +337 -0
- data/spec/unit/qb_collection_spec.rb +13 -0
- data/spec/unit/qbfc_const_spec.rb +10 -0
- data/spec/unit/qbfc_spec.rb +10 -0
- data/spec/unit/report_spec.rb +12 -0
- data/spec/unit/request_query_survey.txt +48 -0
- data/spec/unit/request_spec.rb +486 -0
- data/spec/unit/session_spec.rb +144 -0
- data/spec/unit/terms/generated_spec.rb +14 -0
- data/spec/unit/terms_spec.rb +18 -0
- data/spec/unit/transaction_finders_spec.rb +125 -0
- data/spec/unit/transaction_spec.rb +94 -0
- data/spec/unit/transactions/generated_spec.rb +20 -0
- data/spec/unit/voidable_spec.rb +32 -0
- data/tasks/qbfc_tasks.rake +4 -0
- 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)
|
data/lib/qbfc/report.rb
ADDED
@@ -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 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,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,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
|
data/lib/qbfc/request.rb
ADDED
@@ -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
|