qbfc 0.1.0-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README +41 -0
  3. data/Rakefile +82 -0
  4. data/lib/qbfc.rb +39 -0
  5. data/lib/qbfc/base.rb +82 -0
  6. data/lib/qbfc/element.rb +178 -0
  7. data/lib/qbfc/entities/generated.rb +8 -0
  8. data/lib/qbfc/entity.rb +11 -0
  9. data/lib/qbfc/info.rb +42 -0
  10. data/lib/qbfc/infos/generated.rb +9 -0
  11. data/lib/qbfc/item.rb +10 -0
  12. data/lib/qbfc/items/generated.rb +11 -0
  13. data/lib/qbfc/list.rb +84 -0
  14. data/lib/qbfc/lists/generated.rb +15 -0
  15. data/lib/qbfc/lists/qb_class.rb +25 -0
  16. data/lib/qbfc/modifiable.rb +31 -0
  17. data/lib/qbfc/ole_wrapper.rb +193 -0
  18. data/lib/qbfc/qb_collection.rb +26 -0
  19. data/lib/qbfc/qb_types.rb +34 -0
  20. data/lib/qbfc/qbfc_const.rb +14 -0
  21. data/lib/qbfc/request.rb +158 -0
  22. data/lib/qbfc/session.rb +136 -0
  23. data/lib/qbfc/terms.rb +10 -0
  24. data/lib/qbfc/terms/generated.rb +10 -0
  25. data/lib/qbfc/transaction.rb +83 -0
  26. data/lib/qbfc/transactions/generated.rb +18 -0
  27. data/lib/qbfc/voidable.rb +11 -0
  28. data/spec/rcov.opts +1 -0
  29. data/spec/spec.opts +6 -0
  30. data/spec/spec_helper.rb +62 -0
  31. data/spec/unit/base_spec.rb +138 -0
  32. data/spec/unit/element_finder_spec.rb +180 -0
  33. data/spec/unit/element_spec.rb +118 -0
  34. data/spec/unit/entities/generated_spec.rb +18 -0
  35. data/spec/unit/entity_spec.rb +18 -0
  36. data/spec/unit/info/generated_spec.rb +12 -0
  37. data/spec/unit/info_spec.rb +48 -0
  38. data/spec/unit/item_spec.rb +18 -0
  39. data/spec/unit/items/generated_spec.rb +16 -0
  40. data/spec/unit/list_finders_spec.rb +128 -0
  41. data/spec/unit/list_spec.rb +86 -0
  42. data/spec/unit/lists/generated_spec.rb +15 -0
  43. data/spec/unit/lists/qb_class_spec.rb +9 -0
  44. data/spec/unit/modifiable_spec.rb +84 -0
  45. data/spec/unit/ole_wrapper_spec.rb +293 -0
  46. data/spec/unit/qb_collection_spec.rb +13 -0
  47. data/spec/unit/qbfc_const_spec.rb +10 -0
  48. data/spec/unit/qbfc_spec.rb +10 -0
  49. data/spec/unit/request_query_survey.txt +48 -0
  50. data/spec/unit/request_spec.rb +236 -0
  51. data/spec/unit/session_spec.rb +138 -0
  52. data/spec/unit/terms/generated_spec.rb +14 -0
  53. data/spec/unit/terms_spec.rb +18 -0
  54. data/spec/unit/transaction_finders_spec.rb +124 -0
  55. data/spec/unit/transaction_spec.rb +94 -0
  56. data/spec/unit/transactions/generated_spec.rb +20 -0
  57. data/spec/unit/voidable_spec.rb +32 -0
  58. metadata +140 -0
@@ -0,0 +1,8 @@
1
+ module QBFC
2
+ # Generated Entity Types (Inherit from List)
3
+ ENTITY_TYPES = %w{Customer Employee OtherName Vendor}
4
+
5
+ # Generate Entity subclasses
6
+ # NB: All Entities are Modifiable
7
+ generate(ENTITY_TYPES, Entity, {Modifiable => ENTITY_TYPES})
8
+ end
@@ -0,0 +1,11 @@
1
+ module QBFC
2
+ # Entity objects are Customers, Employees, Vendors and OtherNames
3
+ class Entity < List
4
+ is_base_class
5
+ end
6
+ end
7
+
8
+ # Require subclass files
9
+ Dir[File.dirname(__FILE__) + '/entities/*.rb'].each do |file|
10
+ require file
11
+ end
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
@@ -0,0 +1,9 @@
1
+ module QBFC
2
+
3
+ # Generated Info types
4
+ INFO_TYPES = %w{Company CompanyActivity Host Preferences}
5
+
6
+ # Generate List subclasses
7
+ generate(INFO_TYPES, Info)
8
+
9
+ end
data/lib/qbfc/item.rb ADDED
@@ -0,0 +1,10 @@
1
+ module QBFC
2
+ class Item < List
3
+ is_base_class
4
+ end
5
+ end
6
+
7
+ # Require subclass files
8
+ Dir[File.dirname(__FILE__) + '/items/*.rb'].each do |file|
9
+ require file
10
+ end
@@ -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