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