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.
- data/.gitignore +11 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +85 -0
- data/Rakefile +81 -0
- data/VERSION +1 -0
- data/init.rb +3 -0
- data/install.rb +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/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
- data/uninstall.rb +1 -0
- 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)
|
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
|