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
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Jared E Morgan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,41 @@
1
+ ==QBFC-Ruby
2
+
3
+ QBFC-Ruby provides a wrapper around QuickBooks' QBFC COM object, while
4
+ allowing more or less direct access to the actual COM object.
5
+
6
+ Obviously, test before using on your production data...
7
+
8
+ ==Examples
9
+
10
+ # A very simple example, finding a single Customer by name
11
+ QBFC::session do | qb |
12
+ puts qb.customer('Customer Name').full_name
13
+ end
14
+
15
+ # Find all Customer, then return the first in the Array
16
+ # Next, find the first Customer only
17
+ QBFC::session do | qb |
18
+ customers = qb.customers.find(:all)
19
+ puts customers[0].full_name
20
+ puts qb.customers.find(:first).full_name
21
+ end
22
+
23
+ # Same as previous, but not using a block
24
+ sess = QBFC::Session.new
25
+ customers = QBFC::Customer.find(sess, :all)
26
+ puts customers[0].full_name
27
+ puts QBFC::Customer.find(sess, :first).full_name
28
+ sess.close
29
+
30
+ # Use a QBFC::Session object, but access the COM object
31
+ # more directly.
32
+ QBFC::session do | qb |
33
+ request_set = qb.CreateMsgSetRequest("US", 6, 0)
34
+ customer_query = request_set.AppendCustomerQueryRq
35
+ response = qb.DoRequests(request_set)
36
+ customer_set = response.ResponseList[0]
37
+ first_customer = customer_set.Detail[0]
38
+ puts first_customer.full_name
39
+ end
40
+
41
+ Copyright (c) 2008 Jared E. Morgan, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,82 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ gem 'rspec'
5
+ require 'spec/rake/spectask'
6
+
7
+ QBFC_ROOT = File.dirname(__FILE__)
8
+
9
+ task :default => :spec
10
+
11
+ desc "Run all specs in spec/unit directory"
12
+ Spec::Rake::SpecTask.new(:spec) do |t|
13
+ t.spec_opts = ['--options', "\"#{QBFC_ROOT}/spec/spec.opts\""]
14
+ t.spec_files = FileList['spec/unit/**/*_spec.rb']
15
+ end
16
+
17
+ namespace :spec do
18
+ desc "Run all specs in spec/unit directory with RCov"
19
+ Spec::Rake::SpecTask.new(:rcov) do |t|
20
+ t.spec_opts = ['--options', "\"#{QBFC_ROOT}/spec/spec.opts\""]
21
+ t.spec_files = FileList['spec/unit/**/*_spec.rb']
22
+ t.rcov = true
23
+ t.rcov_opts = lambda do
24
+ IO.readlines("#{QBFC_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
25
+ end
26
+ end
27
+
28
+ desc "Run all specs in spec/integration directory"
29
+ Spec::Rake::SpecTask.new(:integration) do |t|
30
+ t.spec_opts = ['--options', "\"#{QBFC_ROOT}/spec/spec.opts\""]
31
+ t.spec_files = FileList['spec/integration/**/*_spec.rb']
32
+ end
33
+
34
+ desc "Run all specs in spec/integration directory with RCov"
35
+ Spec::Rake::SpecTask.new(:integration_rcov) do |t|
36
+ t.spec_opts = ['--options', "\"#{QBFC_ROOT}/spec/spec.opts\""]
37
+ t.spec_files = FileList['spec/integration/**/*_spec.rb']
38
+ t.rcov = true
39
+ t.rcov_opts = lambda do
40
+ IO.readlines("#{QBFC_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
41
+ end
42
+ end
43
+
44
+ desc "Print Specdoc for all specs"
45
+ Spec::Rake::SpecTask.new(:doc) do |t|
46
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
47
+ t.spec_files = FileList['spec/**/*_spec.rb']
48
+ end
49
+ end
50
+
51
+ desc 'Generate documentation for the qbfc library.'
52
+ Rake::RDocTask.new(:rdoc) do |rdoc|
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = 'QBFC-Ruby'
55
+ rdoc.options << '--line-numbers' << '--inline-source'
56
+ rdoc.rdoc_files.include('README')
57
+ rdoc.rdoc_files.include('lib/**/*.rb')
58
+ end
59
+
60
+ require 'rubygems'
61
+ Gem::manage_gems
62
+ require 'rake/gempackagetask'
63
+
64
+ spec = Gem::Specification.new do |s|
65
+ s.name = "qbfc"
66
+ s.version = "0.1.0"
67
+ s.author = "Jared Morgan"
68
+ s.email = "jmorgan@morgancreative.net"
69
+ s.homepage = "http://rubyforge.org/projects/qbfc/"
70
+ s.rubyforge_project = "qbfc"
71
+ s.platform = Gem::Platform::CURRENT
72
+ s.summary = "A wrapper around the QBFC COM object of the Quickbooks SDK"
73
+ s.files = FileList['lib/**/*.rb', 'bin/*', '[A-Z]*', 'spec/*.opts', 'spec/*.rb', 'spec/unit/**/*'].to_a
74
+ s.require_path = "lib"
75
+ s.test_files = Dir.glob('spec/unit/**/*_spec.rb')
76
+ s.has_rdoc = true
77
+ s.extra_rdoc_files = ["README"]
78
+ end
79
+
80
+ Rake::GemPackageTask.new(spec) do |pkg|
81
+ pkg.need_tar = true
82
+ end
data/lib/qbfc.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'win32ole'
2
+ require 'time'
3
+
4
+ # ActiveSupport is used for camelize, singularize and similar String inflectors.
5
+ unless Object.const_defined?(:ActiveSupport)
6
+ gem 'activesupport'
7
+ require 'active_support'
8
+ end
9
+
10
+ module QBFC
11
+ class << self
12
+
13
+ # Opens and yields a QBFC::Session
14
+ def session(*args, &block)
15
+ QBFC::Session::open(*args, &block)
16
+ end
17
+
18
+ # Generate classes.
19
+ # - +names+: Array of class names.
20
+ # - +superclass+: Superclass of classes to be generated.
21
+ # - +includes+: hash of Module => names of classes to include this module.
22
+ def generate(names, superclass, include_modules = {})
23
+ names.each do | class_name |
24
+ const_set(class_name, Class.new(superclass))
25
+ end
26
+
27
+ include_modules.each do | mod, classes |
28
+ classes.each do | class_name |
29
+ const_get(class_name).__send__(:include, mod)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+
37
+ %w{ ole_wrapper qbfc_const session request base element info qb_collection qb_types }.each do |file|
38
+ require File.dirname(__FILE__) + '/qbfc/' + file
39
+ end
data/lib/qbfc/base.rb ADDED
@@ -0,0 +1,82 @@
1
+ module QBFC
2
+ # Base is the...um..."base" class from which Element, Info, and
3
+ # Report inherit. It defines methods that the three share.
4
+ class Base
5
+ class << self
6
+
7
+ # is_base_class? is used by Element and subclasses. It is included
8
+ # in Base because some Base methods may check for it.
9
+ def is_base_class? #:nodoc:
10
+ false
11
+ end
12
+
13
+ # Element::find and Info::get receive optional arguments which can include
14
+ # a Request object and/or an options Hash. <tt>parse_find_args</tt>
15
+ # gets these arguments into a set that is easier to deal with.
16
+ def parse_find_args(*args)
17
+ request = args[0].kind_of?(QBFC::Request) ? args[0] : nil
18
+ options = args[-1].kind_of?(Hash) ? args[-1] : {}
19
+
20
+ # base classes will need to pass a subset of options to
21
+ # the ChildClass.find . Also, the actually options to the
22
+ # BaseClass.find Request cannot include owner_id.
23
+ if is_base_class?
24
+ base_options = options.dup
25
+ base_options.delete(:conditions)
26
+ options.delete(:owner_id)
27
+ else
28
+ base_options = nil
29
+ end
30
+
31
+ return request, options, base_options
32
+ end
33
+
34
+ # A convenience method for creating and returning
35
+ # a Query Request for this class.
36
+ def create_query(sess)
37
+ QBFC::Request.new(sess, "#{qb_name}Query")
38
+ end
39
+
40
+ protected :parse_find_args, :create_query
41
+
42
+ # The QuickBooks name for this Element or Report.
43
+ # It typically matches the last part of class name.
44
+ # Used in determining names of Requests and other
45
+ # OLE methods.
46
+ def qb_name
47
+ self.name.split('::').last
48
+ end
49
+ end
50
+
51
+ # Create an instance of this Element or Report.
52
+ # - <tt>sess</tt>: An open QBFC::Session object that will recieve all requests
53
+ # - <tt>ole</tt>: An optional QBFC::OLEWrapper object representing
54
+ # a response to a QueryRq. It is unlikely that this will be used directly.
55
+ def initialize(sess, ole = nil)
56
+ @sess, @ole = sess, ole
57
+ @ole = QBFC::OLEWrapper.new(@ole) if @ole.kind_of?(WIN32OLE)
58
+ end
59
+
60
+ # List the methods of the OLE object
61
+ def ole_methods
62
+ @ole.ole_methods
63
+ end
64
+
65
+ # Check if the OLE object responds to a given method
66
+ def respond_to_ole?(symbol)
67
+ @ole.respond_to_ole?(symbol)
68
+ end
69
+
70
+ # Pass missing methods to OLEWrapper#qbfc_method_missing
71
+ # to handle checking if there is a related OLE method to run.
72
+ def method_missing(symbol, *params)
73
+ @ole.qbfc_method_missing(@sess, symbol, *params)
74
+ end
75
+
76
+ # Name of the QuickBooks Element or Query represented by this class.
77
+ def qb_name
78
+ self.class.qb_name
79
+ end
80
+ end
81
+ end
82
+
@@ -0,0 +1,178 @@
1
+ require File.dirname(__FILE__) + '/modifiable'
2
+
3
+ module QBFC
4
+
5
+ # An Element is a Transaction or a List; that is any QuickBooks objects that can
6
+ # be created, edited (possibly), deleted and read. Contrast to a Report or Info
7
+ # which are read-only.
8
+ #
9
+ # Inheritance from Element implies a set of shared methods, such as find, but the
10
+ # only shared implementation defined here is #custom, for getting custom field information.
11
+ class Element < Base
12
+
13
+ class << self
14
+ # Set that this is a "base class", one which is inherited,
15
+ # such as List, Transaction, Entity, or Terms.
16
+ # Base classes do not accept Add Requests, and their finders
17
+ # will return an instance of an inherited class.
18
+ def is_base_class
19
+ @is_base_class = true
20
+ end
21
+
22
+ # Check if this is a "base class" (see is_base_class)
23
+ def is_base_class?
24
+ @is_base_class ? true : false
25
+ end
26
+
27
+ # Find a QuickBooks record for the given session.
28
+ #
29
+ # session.[qb_name] aliases this functionality. The following
30
+ # pairs are equivalent:
31
+ #
32
+ # QBFC::Vendor.find(session, :first) <-> session.vendor
33
+ # QBFC::Vendor.find(session, "Supply Shop") <-> session.vendor("Supply Shop")
34
+ # QBFC::Vendor.find(session, :all) <-> session.vendors.find(:all)
35
+ #
36
+ # Find requires a +session+ (representing a QBFC::Session) and
37
+ # a +what+ argument. +what+ can be one of:
38
+ # - <tt>:first</tt> - finds the first record fitting any given conditions.
39
+ # - <tt>:finds</tt> - finds all records fitting any given conditions.
40
+ # - An unique identifier, such as a ListID, FullName, RefNumber or TxnID
41
+ #
42
+ # .find can also receive a optional Request object followed
43
+ # by an optional options Hash. Valid argument sets are:
44
+ #
45
+ # QBFC::Vendor.find(session, :first)
46
+ # QBFC::Vendor.find(session, :first, request)
47
+ # QBFC::Vendor.find(session, :first, request, options)
48
+ # QBFC::Vendor.find(session, :first, options)
49
+ #
50
+ # The options hash accepts the following:
51
+ # - <tt>:owner_id</tt>: One or more OwnerIDs, used in accessing
52
+ # custom fields (aka private data extensions).
53
+ #
54
+ # Additional options are planned, but not really supported in this version.
55
+ # Passing a Request object is the current recommended way of applying
56
+ # Filters or other options to the Query Request.
57
+ def find(sess, what, *args)
58
+
59
+ if what.kind_of?(String) # Single FullName or ListID
60
+ return find_by_unique_id(sess, what, *args)
61
+ end
62
+
63
+ # Setup q, options and base_options arguments
64
+ q, options, base_options = parse_find_args(*args)
65
+ q ||= create_query(sess)
66
+ q.apply_options(options)
67
+
68
+ # QuickBooks only needs to return one record if .find is
69
+ # only returning a single record.
70
+ if what == :first && q.filter_available?
71
+ q.filter.max_returned = 1
72
+ end
73
+
74
+ # Send the query so far to base_class_find if this is
75
+ # a base class to handle special base class functionality.
76
+ return base_class_find(sess, what, q, base_options) if is_base_class?
77
+
78
+ # Get response and return an Array, a QBFC::Element-inherited
79
+ # object, or nil, depending on <tt>what</tt> argument and whether
80
+ # the query returned any records.
81
+ list = q.response
82
+
83
+ if list.nil?
84
+ (what == :all) ? [] : nil
85
+ elsif what == :all
86
+ (0..(list.Count - 1)).collect { |i|
87
+ new(sess, list.GetAt(i))
88
+ }
89
+ else
90
+ new(sess, list.GetAt(0))
91
+ end
92
+ end
93
+
94
+ # Base classes can be used to find members of any of their
95
+ # inherited classes. Since QuickBooks has limited options
96
+ # for these type of queries (in particular, no custom fields
97
+ # are returned), an initial query is run which retrieves
98
+ # only IDs and the class type and then individual subsequent
99
+ # queries are run for each returned object.
100
+ def base_class_find(sess, what, q, options)
101
+ q.IncludeRetElementList.Add(self::ID_NAME)
102
+ list = q.response
103
+
104
+ if list.nil?
105
+ (what == :all) ? [] : nil
106
+ else
107
+ ary = (0..(list.Count - 1)).collect { |i|
108
+ element = list.GetAt(i)
109
+ ret_name = element.ole_methods.detect{ |m| m.to_s =~ /(.*)Ret\Z/ }.to_s
110
+ ret_class = QBFC::const_get($1)
111
+ ret_class.find(sess, element.send(ret_name).send(ret_class::ID_NAME), options.dup)
112
+ }
113
+
114
+ if what == :all
115
+ ary
116
+ else
117
+ ary[0]
118
+ end
119
+ end
120
+ end
121
+
122
+ private :base_class_find
123
+
124
+ end
125
+
126
+ is_base_class
127
+
128
+ # Extends Base#initialize to allow for an Add Request if
129
+ # .new is called directly (instead of by .find)
130
+ def initialize(sess, ole_object = nil)
131
+ if self.class.is_base_class?
132
+ raise BaseClassNewError, "This is a base class which doesn't allow object initialization"
133
+ end
134
+
135
+ super
136
+
137
+ if @ole.nil?
138
+ add_rq = QBFC::Request.new(sess, "#{qb_name}Add")
139
+ @ole = QBFC::OLEWrapper.new(add_rq.ole_object)
140
+ @new_record = true
141
+ @setter = add_rq
142
+ end
143
+ end
144
+
145
+ # Is this a new record, i.e. are we doing an Add Request?
146
+ def new_record?
147
+ @new_record ? true : false
148
+ end
149
+
150
+ # Access information from a custom field.
151
+ def custom(field_name, owner_id = 0)
152
+ if @ole.DataExtRetList
153
+ @ole.data_ext.each do |field|
154
+ if field.data_ext_name == field_name && field.owner_id == owner_id
155
+ return field.data_ext_value
156
+ end
157
+ end
158
+ end
159
+
160
+ return nil
161
+ end
162
+
163
+ # Save (create or update) this record
164
+ def save
165
+ if @setter
166
+ @setter.submit
167
+ else
168
+ raise NotSavableError, "This record cannot be saved (Probably because it does not support Mod Requests)."
169
+ end
170
+ end
171
+
172
+ end
173
+ end
174
+
175
+ # Require subclass files
176
+ %w{ list transaction }.each do |file|
177
+ require File.dirname(__FILE__) + '/' + file
178
+ end