qbfc 0.1.0-x86-mswin32-60
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/MIT-LICENSE +20 -0
- data/README +41 -0
- data/Rakefile +82 -0
- data/lib/qbfc.rb +39 -0
- data/lib/qbfc/base.rb +82 -0
- data/lib/qbfc/element.rb +178 -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 +10 -0
- data/lib/qbfc/items/generated.rb +11 -0
- data/lib/qbfc/list.rb +84 -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 +193 -0
- data/lib/qbfc/qb_collection.rb +26 -0
- data/lib/qbfc/qb_types.rb +34 -0
- data/lib/qbfc/qbfc_const.rb +14 -0
- data/lib/qbfc/request.rb +158 -0
- data/lib/qbfc/session.rb +136 -0
- data/lib/qbfc/terms.rb +10 -0
- data/lib/qbfc/terms/generated.rb +10 -0
- data/lib/qbfc/transaction.rb +83 -0
- data/lib/qbfc/transactions/generated.rb +18 -0
- data/lib/qbfc/voidable.rb +11 -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 +180 -0
- data/spec/unit/element_spec.rb +118 -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 +18 -0
- data/spec/unit/items/generated_spec.rb +16 -0
- data/spec/unit/list_finders_spec.rb +128 -0
- data/spec/unit/list_spec.rb +86 -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 +293 -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/request_query_survey.txt +48 -0
- data/spec/unit/request_spec.rb +236 -0
- data/spec/unit/session_spec.rb +138 -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 +124 -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
- metadata +140 -0
data/lib/qbfc/entity.rb
ADDED
data/lib/qbfc/info.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module QBFC
|
2
|
+
# Info objects are those which have only one instance within Quickbooks,
|
3
|
+
# which are: Company, CompanyActivity, Host and Preferences.
|
4
|
+
#
|
5
|
+
# Access through QBFC for these objects is read-only.
|
6
|
+
#
|
7
|
+
# QBFC::Info can be accessed via session::company, for example, or
|
8
|
+
# through QBFC::Company::get(session).
|
9
|
+
class Info < Base
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Get the Info object for the given session.
|
13
|
+
# session.[qb_name] aliases this functionality.
|
14
|
+
# For example QBFC::Company.get(session) and
|
15
|
+
# session.company are equivalent.
|
16
|
+
#
|
17
|
+
# It accepts the follow options as a hash:
|
18
|
+
# - <tt>:owner_id</tt>: One or more OwnerIDs, used in accessing
|
19
|
+
# custom fields (aka private data extensions).
|
20
|
+
def get(sess, *args)
|
21
|
+
# Setup q, options and base_options arguments
|
22
|
+
q, options, base_options = parse_find_args(*args)
|
23
|
+
q ||= create_query(sess)
|
24
|
+
q.apply_options(options)
|
25
|
+
|
26
|
+
new(sess, q.response)
|
27
|
+
end
|
28
|
+
|
29
|
+
# This is a convenience alias for +get+.
|
30
|
+
# It exists solely so that I don't have to modify
|
31
|
+
# Session#method_missing.
|
32
|
+
def find(sess, what, *args) #:nodoc:
|
33
|
+
get(sess, *args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Require subclass files
|
40
|
+
Dir[File.dirname(__FILE__) + '/infos/*.rb'].each do |file|
|
41
|
+
require file
|
42
|
+
end
|
data/lib/qbfc/item.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module QBFC
|
2
|
+
|
3
|
+
# Generated Item Types (Inherit from List)
|
4
|
+
ITEM_TYPES = %w{ItemService ItemNonInventory ItemOtherCharge ItemInventory ItemInventoryAssembly ItemFixedAsset
|
5
|
+
ItemSubtotal ItemDiscount ItemPayment ItemSalesTax ItemSalesTaxGroup ItemGroup}
|
6
|
+
|
7
|
+
# Generate Item subclasses
|
8
|
+
# NB: All Items are Modifiable
|
9
|
+
generate(ITEM_TYPES, Item, {Modifiable => ITEM_TYPES})
|
10
|
+
|
11
|
+
end
|
data/lib/qbfc/list.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module QBFC
|
2
|
+
# List objects are those with names, such as Accounts, Entities, and Items.
|
3
|
+
#
|
4
|
+
# Note on the name: it doesn't make sense since a List is actually a single object,
|
5
|
+
# but it fits with the SDK's naming scheme, and I couldn't think of a better one.
|
6
|
+
class List < Element
|
7
|
+
is_base_class
|
8
|
+
ID_NAME = "ListID"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Find by name (actually, FullName) of List record.
|
13
|
+
# +options+ are the same as those for in +find+.
|
14
|
+
def find_by_name(sess, full_name, options = {})
|
15
|
+
q = create_query(sess)
|
16
|
+
q.query.FullNameList.Add(full_name)
|
17
|
+
find(sess, :first, q, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :find_by_full_name, :find_by_name
|
21
|
+
|
22
|
+
# Find by ListID of List record.
|
23
|
+
# +options+ are the same as those for in +find+.
|
24
|
+
def find_by_id(sess, id, options = {})
|
25
|
+
q = create_query(sess)
|
26
|
+
q.query.ListIDList.Add(id)
|
27
|
+
find(sess, :first, q, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Find by either name or id. Tries id first, then name.
|
31
|
+
def find_by_name_or_id(*args)
|
32
|
+
find_by_id(*args) || find_by_name(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
alias_method :find_by_unique_id, :find_by_name_or_id
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
# Alias of ListID for this record. This is a unique within each type of List.
|
40
|
+
def id
|
41
|
+
@ole.list_id
|
42
|
+
end
|
43
|
+
|
44
|
+
# If an entity has a Name field but not a FullName field,
|
45
|
+
# use Name (which, by implication, is the FullName)
|
46
|
+
def full_name
|
47
|
+
respond_to_ole?("FullName") ?
|
48
|
+
@ole.full_name :
|
49
|
+
@ole.name
|
50
|
+
end
|
51
|
+
|
52
|
+
# Delete this List record.
|
53
|
+
def delete
|
54
|
+
req = QBFC::Request.new(@sess, "ListDel")
|
55
|
+
req.list_del_type = QBFC_CONST::const_get("Ldt#{qb_name}")
|
56
|
+
req.list_id = id
|
57
|
+
req.submit
|
58
|
+
return true
|
59
|
+
end
|
60
|
+
|
61
|
+
# Display the Transaction add (for new records) or edit dialog box
|
62
|
+
def display
|
63
|
+
if new_record?
|
64
|
+
req = QBFC::Request.new(@sess, "ListDisplayAdd")
|
65
|
+
req.list_display_add_type = QBFC_CONST::const_get("Ldat#{qb_name}")
|
66
|
+
else
|
67
|
+
req = QBFC::Request.new(@sess, "ListDisplayMod")
|
68
|
+
req.list_display_mod_type = QBFC_CONST::const_get("Ldmt#{qb_name}")
|
69
|
+
req.list_id = id
|
70
|
+
end
|
71
|
+
req.submit
|
72
|
+
return true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Require subclass files
|
78
|
+
%w{ entity item terms }.each do |file|
|
79
|
+
require File.dirname(__FILE__) + '/' + file
|
80
|
+
end
|
81
|
+
|
82
|
+
Dir[File.dirname(__FILE__) + '/lists/*.rb'].each do |file|
|
83
|
+
require file
|
84
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module QBFC
|
2
|
+
|
3
|
+
# Generated List types that will inherit directly from QBFC::List
|
4
|
+
LIST_TYPES = %w{Account BillingRate CustomerMsg CustomerType JobType PaymentMethod
|
5
|
+
PayrollItemNonWage PayrollItemWage PriceLevel SalesRep SalesTaxCode ShipMethod ToDo
|
6
|
+
Vehicle VendorType}
|
7
|
+
|
8
|
+
# Generated List types that do not accept Mod Requests
|
9
|
+
LIST_NO_MOD_TYPES = %w{ BillingRate CustomerMsg CustomerType JobType PaymentMethod
|
10
|
+
PayrollItemWage SalesTaxCode ShipMethod ToDo VendorType}
|
11
|
+
|
12
|
+
# Generate List subclasses
|
13
|
+
generate(LIST_TYPES, List, {Modifiable => LIST_TYPES - LIST_NO_MOD_TYPES})
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module QBFC
|
2
|
+
# QBClass objects represent QuickBooks SDK's <tt>Class</tt> objects.
|
3
|
+
# As naming this Class <tt>Class</tt> would be impractical, it is
|
4
|
+
# instead called QBClass. It is otherwise similar to the other List
|
5
|
+
# classes.
|
6
|
+
#
|
7
|
+
# From QBFC6 SDK docs:
|
8
|
+
#
|
9
|
+
# Classes can be used to separate transactions into meaningful categories.
|
10
|
+
# (For example, transactions could be classified according to department,
|
11
|
+
# business location, or type of work.) In QuickBooks, class tracking is
|
12
|
+
# off by default.
|
13
|
+
class QBClass < List
|
14
|
+
class << self
|
15
|
+
|
16
|
+
# The QuickBooks SDK class is called 'Class'.
|
17
|
+
# Calling this class QBClass avoids making ruby
|
18
|
+
# very angry; the qb_name method ensures that calls
|
19
|
+
# to QBFC use the correct name.
|
20
|
+
def qb_name
|
21
|
+
"Class"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module QBFC
|
2
|
+
module Modifiable
|
3
|
+
|
4
|
+
# Extend initialize of including classes to set up a Mod Request
|
5
|
+
# if the record is existing.
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
|
9
|
+
unless @new_record
|
10
|
+
setup_mod_request
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Setup a Mod Request for this object and attach it
|
15
|
+
# to the ole object.
|
16
|
+
def setup_mod_request
|
17
|
+
@setter = QBFC::Request.new(@sess, "#{self.qb_name}Mod")
|
18
|
+
|
19
|
+
if self.kind_of?(List)
|
20
|
+
@setter.list_id = id
|
21
|
+
else # Transaction
|
22
|
+
@setter.txn_id = id
|
23
|
+
end
|
24
|
+
|
25
|
+
@setter.edit_sequence = @ole.edit_sequence
|
26
|
+
@ole.setter = @setter.ole_object
|
27
|
+
end
|
28
|
+
|
29
|
+
private :setup_mod_request
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module QBFC
|
2
|
+
|
3
|
+
# OLEWrapper is more or less the centerpiece of RubyQBFC. (Nearly) every
|
4
|
+
# WIN32OLE object accessed within the library is wrapped in this class, which
|
5
|
+
# is responsible for allowing Ruby-esque methods in place of the OLE methods.
|
6
|
+
#
|
7
|
+
# customer.full_name # => Customer.FullName.GetValue
|
8
|
+
# customer.full_name=(val) # => Customer.FullName.SetValue(val)
|
9
|
+
#
|
10
|
+
# It also creates referenced objects when accessed.
|
11
|
+
#
|
12
|
+
# check.payee # => Entity.find_by_list_id(check.PayeeEntityRef.ListID.GetValue)
|
13
|
+
# check.account # => Account.find_by_list_id(check.AccountRef.ListID.GetValue)
|
14
|
+
#
|
15
|
+
# When an OLE method called via OLEWrapper returns a WIN32OLE object, a new
|
16
|
+
# OLEWrapper object is created with the WIN32OLE object and returned.
|
17
|
+
#
|
18
|
+
# Now, the fun (and +really+ hackish) part. In many cases within the QBFC
|
19
|
+
# library, the wrapper is actually wrapping two WIN32OLE objects, the additional
|
20
|
+
# being a 'setter' object. This object is used when creating a ModRequest. In
|
21
|
+
# such cases, a method ending in '=' is always sent to both the primary and the
|
22
|
+
# setter objects. To facilitate this, traversing child ole_objects also
|
23
|
+
# traverses the child setter objects.
|
24
|
+
class OLEWrapper
|
25
|
+
attr_reader :ole_object
|
26
|
+
attr_accessor :setter
|
27
|
+
|
28
|
+
# Set up wrapped object, by passing a WIN32OLE object
|
29
|
+
# (or a String with the name of a WIN32OLE server)
|
30
|
+
# Optionally, pass a +setter+ WIN32OLE object.
|
31
|
+
def initialize(ole_object, setter = nil)
|
32
|
+
ole_object = WIN32OLE.new(ole_object) if ole_object.kind_of?(String)
|
33
|
+
@ole_object = ole_object
|
34
|
+
@setter = setter
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return Array of ole_methods for request WIN32OLE object.
|
38
|
+
def ole_methods
|
39
|
+
@ole_object.ole_methods
|
40
|
+
end
|
41
|
+
|
42
|
+
# Does this OLE object respond to the given ole method?
|
43
|
+
def respond_to_ole?(symbol)
|
44
|
+
detect_ole_method?(@ole_object, symbol)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Use [idx] syntax for objects that respond to <tt>GetAt(idx)</tt>
|
48
|
+
def [](idx)
|
49
|
+
if idx.kind_of? Integer
|
50
|
+
self.class.new(@ole_object.GetAt(idx))
|
51
|
+
else
|
52
|
+
@ole_object[idx]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Called by #method_missing of other classes. Initiates the OLEWrapper#method_missing
|
57
|
+
# method which is responsible for the various method conversions.
|
58
|
+
# +sess+ argument is a QBFC::Session.
|
59
|
+
def qbfc_method_missing(sess, symbol, *params)
|
60
|
+
@sess = sess
|
61
|
+
method_missing(symbol, *params)
|
62
|
+
end
|
63
|
+
|
64
|
+
# If the method name is capitalized, send directly to ole_object; if
|
65
|
+
# a WIN32OLE is returned, wrap it.
|
66
|
+
# If the method name starts with a lower-case letter, send to +lower_method_missing+
|
67
|
+
# for conversion.
|
68
|
+
def method_missing(symbol, *params) #:nodoc:
|
69
|
+
if (('a'..'z') === symbol.to_s[0].chr)
|
70
|
+
lower_case_method_missing(symbol, *params)
|
71
|
+
else
|
72
|
+
resp = @ole_object.send(symbol, *params)
|
73
|
+
return( resp.kind_of?(WIN32OLE) ?
|
74
|
+
self.class.new(resp) :
|
75
|
+
resp )
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Decide which conversion method needs to handle this method and send.
|
82
|
+
def lower_case_method_missing(symbol, *params)
|
83
|
+
if '=' == symbol.to_s[-1].chr
|
84
|
+
set_value(symbol.to_s[0..-2], *params)
|
85
|
+
elsif symbol.to_s =~ /\A(\w+)_(full_name|id)\Z/ && ref_name($1)
|
86
|
+
get_ref_name_or_id(ref_name($1), $2)
|
87
|
+
elsif detect_ole_method?(@ole_object, ole_sym(symbol))
|
88
|
+
get_value(symbol, *params)
|
89
|
+
elsif detect_ole_method?(@ole_object, (s = symbol.to_s.singularize.camelize + "RetList"))
|
90
|
+
setup_array(s)
|
91
|
+
elsif detect_ole_method?(@ole_object, (s = "OR" + symbol.to_s.singularize.camelize + "RetList"))
|
92
|
+
setup_array(s, true)
|
93
|
+
elsif ref_name(symbol)
|
94
|
+
create_ref(ref_name(symbol), *params)
|
95
|
+
else
|
96
|
+
raise NoMethodError, symbol.to_s
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Sets a value by calling OLEMethodName.SetValue(*params)
|
101
|
+
def set_value(ole_method_name, *params)
|
102
|
+
ole_method_name = ole_sym(ole_method_name)
|
103
|
+
obj = @ole_object.send(ole_method_name)
|
104
|
+
|
105
|
+
if detect_ole_method?(obj, "SetValue")
|
106
|
+
obj.SetValue(*params)
|
107
|
+
if @setter && detect_ole_method?(@setter, ole_method_name)
|
108
|
+
@setter.send(ole_method_name).SetValue(*params)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
raise SetValueMissing, "SetValue is expected, but missing, for #{ole_method_name}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Gets a value by calling OLEMethodName.GetValue
|
116
|
+
def get_value(ole_method_name, *params)
|
117
|
+
ole_method_name = ole_sym(ole_method_name)
|
118
|
+
obj = @ole_object.send(ole_method_name, *params)
|
119
|
+
if detect_ole_method?(obj, "GetValue")
|
120
|
+
if ole_method_name =~ /date/i || ole_method_name.to_s =~ /time/i
|
121
|
+
Time.parse(obj.GetValue)
|
122
|
+
else
|
123
|
+
obj.GetValue
|
124
|
+
end
|
125
|
+
else
|
126
|
+
if obj.kind_of?(WIN32OLE)
|
127
|
+
if @setter && detect_ole_method?(@setter, ole_method_name)
|
128
|
+
self.class.new(obj, @setter.send(ole_method_name, *params))
|
129
|
+
else
|
130
|
+
self.class.new(obj)
|
131
|
+
end
|
132
|
+
else
|
133
|
+
obj
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Sets up an array to return if the return of OLEMethodName appears
|
139
|
+
# to be a list structure.
|
140
|
+
# <tt>is_OR_list</tt> indicates the list is an OR*RetList which
|
141
|
+
# is structured differently.
|
142
|
+
def setup_array(ole_method_name, is_OR_list = false)
|
143
|
+
list = @ole_object.send(ole_method_name)
|
144
|
+
ary = []
|
145
|
+
0.upto(list.Count - 1) do |i|
|
146
|
+
if is_OR_list
|
147
|
+
ary << self.class.new(list.GetAt(i)).send(ole_method_name.match(/\AOR(.*)List\Z/)[1])
|
148
|
+
else
|
149
|
+
ary << self.class.new(list.GetAt(i))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
return ary
|
153
|
+
end
|
154
|
+
|
155
|
+
def ref_name(symbol)
|
156
|
+
if detect_ole_method?(@ole_object, symbol.to_s.camelize + "Ref")
|
157
|
+
symbol.to_s.camelize + "Ref"
|
158
|
+
elsif detect_ole_method?(@ole_object, symbol.to_s.camelize + "EntityRef")
|
159
|
+
symbol.to_s.camelize + "EntityRef"
|
160
|
+
else
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Creates a QBFC::Base inherited object if the return of
|
166
|
+
# OLEMethodName appears to be a reference to such an object.
|
167
|
+
def create_ref(ref_ole_name, *options)
|
168
|
+
ref_ole_object = @ole_object.send(ref_ole_name)
|
169
|
+
if ref_ole_object
|
170
|
+
ref_ole_name =~ /EntityRef/ ?
|
171
|
+
QBFC::Entity.find_by_id(@sess, ref_ole_object.ListID.GetValue(), *options) :
|
172
|
+
QBFC::const_get(ref_ole_name.gsub(/Ref/,"")).find_by_id(@sess, ref_ole_object.ListID.GetValue(), *options)
|
173
|
+
else
|
174
|
+
return nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_ref_name_or_id(symbol, field)
|
179
|
+
field = (field == "id" ? "ListID" : "FullName")
|
180
|
+
@ole_object.send(symbol).send(field).GetValue()
|
181
|
+
end
|
182
|
+
|
183
|
+
# Check if the obj has an ole_method matching the symbol.
|
184
|
+
def detect_ole_method?(obj, symbol)
|
185
|
+
obj && obj.respond_to?(:ole_methods) && obj.ole_methods.detect{|m| m.to_s == symbol.to_s}
|
186
|
+
end
|
187
|
+
|
188
|
+
# Helper method to convert 'Ruby-ish' method name to WIN32OLE method name
|
189
|
+
def ole_sym(symbol)
|
190
|
+
symbol.to_s.camelize.gsub(/Id/, 'ID')
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|