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