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,26 @@
1
+ module QBFC
2
+
3
+ # A QBCollection object is used as an intermediate object when doing finds,
4
+ # for example, in <tt>qb_session.customers.find(:all)</tt>, +customers+
5
+ # returns a QBCollection instance which then is sent the find call.
6
+ # The instance sends the find method on to the appropriate Class.
7
+ # The reason for having this intermediate class is to be able to
8
+ # pass a reference to the Session to the find method
9
+ # (or other class method).
10
+ #
11
+ # There's probably no reason to use this class directly.
12
+ class QBCollection
13
+
14
+ # +sess+ is a QBFC::Session object, +class_name+ is the name of
15
+ # a class descended from QBFC::Base.
16
+ def initialize(sess, class_name)
17
+ @sess = sess
18
+ @klass = QBFC::const_get(class_name)
19
+ end
20
+
21
+ # Send any missing methods to the class, along with the +Session+ object
22
+ def method_missing(symbol, *params) #:nodoc:
23
+ @klass.send(symbol, @sess, *params)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ # This file sets up the classes for QuickBooks entities, transactions and reports.
2
+
3
+ # Types that allow Query and Delete only
4
+ QBFC_DELETE_ONLY = %w{PayrollItemNonWage DataEventRecoveryInfo}
5
+
6
+ # Report types return an IReportRet
7
+ QBFC_REPORT_TYPES = %w{AgingReport BudgetSummaryReport CustomDetailReport CustomSummaryReport
8
+ GeneralDetailReport GeneralSummaryReport JobReport PayrollDetailReport PayrollSummaryReport TimeReport }
9
+
10
+ # Types that allow Special adds (Pre-defined and normally added automatically by QuickBooks)
11
+ QBFC_HAS_SPECIAL_ADD = %w{Account Item}
12
+
13
+ # TODO: Here and below arrays I haven't yet formed any approach to dealing with.
14
+ # I leave them here as a reminder.
15
+ ELEMENTS_ADD_MOD = %w{ DataExt }
16
+
17
+ ELEMENTS_ADD_MOD_QUERY = %w{ DataExtDef }
18
+
19
+ # Types that have their own DelRq
20
+ ELEMENT_DEL_TYPES = %w{DataEventRecoveryInfo DataExt DataExtDef}
21
+
22
+ # Query types support Query requests only and return an itemized list of some sort;
23
+ # most of these may be integrated as special finders for their types.
24
+ QBFC_QUERY_TYPES = %w{BillToPay ListDeleted ReceivePaymentToDeposit Template TxnDeleted SalesTaxPaymentCheck}
25
+
26
+ QBFC_ANOTHER_TO_INTEGRATE_SOMEWHERE = %w{ ItemAssembliesCanBuild }
27
+
28
+
29
+ module QBFC
30
+ # Create QBElement classes
31
+ (QBFC_REPORT_TYPES + QBFC_DELETE_ONLY + %w{DataExt DataExtDef}).uniq.each do | qb_element_name |
32
+ const_set(qb_element_name, Class.new(Base))
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ # QBFC_CONST contains constants defined by QBFC6.QBSessionManager.
2
+ # For example:
3
+ #
4
+ # QBFC_CONST::DmToday # => 1 (for DateMacro)
5
+ #
6
+ # The constants defined in the SDK documents all begin with a lower case letter.
7
+ # In contrast, WIN32OLE capitalizes the constants to follow Ruby naming conventions:
8
+ #
9
+ # QBFC_CONST::DmToday instead of QBFC_CONST::dmToday
10
+
11
+ module QBFC_CONST
12
+ end
13
+
14
+ WIN32OLE.const_load(WIN32OLE.new('QBFC6.QBSessionManager'), QBFC_CONST)
@@ -0,0 +1,158 @@
1
+ module QBFC
2
+ # A QBFC::Request handles creating and sending a Request, including creating
3
+ # the RequestSet. Most often, RubyQBFC classes create and execute the Request
4
+ # internally, however, the Base.find, for example, accepts a QBFC::Request
5
+ # object as an argument if greater control is needed.
6
+ #
7
+ # The WIN32OLE Request object is wrapped in OLEWrapper, so Ruby-esque methods
8
+ # can be used.
9
+ #
10
+ # req = QBFC::Request.new(qb_session, "CustomerQuery").
11
+ # or_customer_list_query.customer_list_filter.max_returned = 2
12
+ # puts req.response
13
+ class Request
14
+
15
+ # <tt>session</tt> is a QBFC::Session object (or a Session object not created through Ruby QBFC)
16
+ # <tt>request_type</tt> is the name of the request, not including trailing 'Rq',
17
+ # e.g. 'CustomerQuery', 'CustomerMod'
18
+ def initialize(sess, request_type, country = 'US', major_version = 6, minor_version = 0)
19
+ @sess = sess
20
+
21
+ begin
22
+ @request_set = sess.CreateMsgSetRequest(country, major_version, minor_version)
23
+ rescue WIN32OLERuntimeError => error
24
+ if error.to_s =~ /error code:8004030A/
25
+ raise QBFC::QBXMLVersionError, "Unsupported qbXML version"
26
+ else
27
+ raise
28
+ end
29
+ end
30
+
31
+ begin
32
+ @request = @request_set.send("Append#{request_type}Rq")
33
+ rescue WIN32OLERuntimeError => error
34
+ if error.to_s =~ /error code:0x80020006/
35
+ raise QBFC::UnknownRequestError, "Unknown request name '#{request_type}'"
36
+ else
37
+ raise
38
+ end
39
+ end
40
+ end
41
+
42
+ # Submit the requests. This returns the full (not wrapped) response object.
43
+ def submit
44
+ @sess.DoRequests(@request_set)
45
+ end
46
+
47
+ # Submit the Request and return the response Detail, wrapped in OLEWrapper (unless nil).
48
+ # The response does not include any MsgSetResponse attributes.
49
+ def response
50
+ submit.ResponseList.GetAt(0).Detail
51
+ end
52
+
53
+ # Get the OR*Query object of the given Request
54
+ # For example, the ORListQuery
55
+ def query
56
+ query_name = @request.ole_methods.detect{|m| m.to_s =~ /Query\Z/}
57
+ return nil if query_name.nil?
58
+ @request.send(query_name.to_s.to_sym)
59
+ end
60
+
61
+ # Get the *Filter object of the given Request
62
+ # For example, the ListFilter
63
+ def filter
64
+ q = self.query
65
+ return nil if q.nil?
66
+ filter_name = q.ole_methods.detect{|m| m.to_s =~ /Filter\Z/}
67
+ return nil if filter_name.nil?
68
+ q.send(filter_name.to_s.to_sym)
69
+ end
70
+
71
+ # Returns where the filter is available for use. That is, that
72
+ # none of the query options other than filter have been used
73
+ def filter_available?
74
+ # -1 = unused, 2 = Filter used
75
+ self.query.ole_object.ortype == -1 ||
76
+ self.query.ole_object.ortype == 2
77
+ end
78
+
79
+ # Applies options from a Hash. This method is primarily experimental
80
+ # (and proof of concept) at this time.
81
+ def apply_options(options)
82
+ if options.kind_of? Hash
83
+ filters = options[:conditions]
84
+ if filters
85
+ if filters[:txn_date]
86
+ txn_date_filter = q.ORTxnQuery.TxnFilter.ORDateRangeFilter.TxnDateRangeFilter.ORTxnDateRangeFilter.TxnDateFilter
87
+ txn_date_filter.FromTxnDate.SetValue( filters[:txn_date][0] ) if filters[:txn_date][0]
88
+ txn_date_filter.ToTxnDate.SetValue( filters[:txn_date][1] ) if filters[:txn_date][1]
89
+ filters.delete(:txn_date)
90
+ end
91
+
92
+ if filters[:ref_number]
93
+ ref_num_filter = q.send("OR#{self.qb_name}Query").send("#{self.qb_name}Filter").
94
+ ORRefNumberFilter.RefNumberRangeFilter
95
+ ref_num_filter.FromRefNumber.SetValue( filters[:ref_number][0] ) if filters[:ref_number][0]
96
+ ref_num_filter.ToRefNumber.SetValue( filters[:ref_number][1] ) if filters[:ref_number][1]
97
+ filters.delete(:ref_number)
98
+ end
99
+
100
+ filters.each do |filter, value|
101
+ q.send("OR#{self.qb_name}Query").
102
+ send("#{self.qb_name}Filter").
103
+ send("#{filter}=", QBFC_CONST::PsNotPaidOnly)
104
+ end
105
+
106
+ options.delete(:conditions)
107
+ end
108
+
109
+ add_owner_ids(options.delete(:owner_id))
110
+
111
+ options.each do |key, value|
112
+ q.send(key.to_s.camelize).SetValue(value)
113
+ end
114
+ end
115
+ end
116
+
117
+
118
+ # Add one or more OwnerIDs to the Request. Used in retrieving
119
+ # custom fields (aka private data extensions).
120
+ # Argument should be a single ID or an Array of IDs.
121
+ def add_owner_ids(ids)
122
+ return if ids.nil?
123
+
124
+ ids = [ids] unless ids.respond_to?(:each)
125
+ ids.each do | id |
126
+ @request.OwnerIDList.Add(id)
127
+ end
128
+ end
129
+
130
+ # Send missing methods to @ole_object (OLEWrapper)
131
+ def method_missing(symbol, *params) #:nodoc:
132
+ @request.qbfc_method_missing(@sess, symbol, *params)
133
+ end
134
+
135
+ # Return Array of ole_methods for request WIN32OLE object.
136
+ # This is mostly useful for debugging.
137
+ def ole_methods
138
+ @request.ole_methods
139
+ end
140
+
141
+ # Return XML for the request WIN32OLE object.
142
+ # This is mostly useful for debugging.
143
+ def to_xml
144
+ @request_set.ToXMLString
145
+ end
146
+
147
+ # Submit Request and return full response as XML.
148
+ # This is mostly useful for debugging.
149
+ def response_xml
150
+ @sess.DoRequests(@request_set).ToXMLString
151
+ end
152
+
153
+ # Return actual WIN32OLE object
154
+ def ole_object
155
+ @request.ole_object
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,136 @@
1
+ module QBFC
2
+ class QuickbooksClosedError < RuntimeError #:nodoc:
3
+ end
4
+ class SetValueMissing < RuntimeError#:nodoc:
5
+ end
6
+ class QBXMLVersionError < RuntimeError#:nodoc:
7
+ end
8
+ class UnknownRequestError < RuntimeError#:nodoc:
9
+ end
10
+ class BaseClassNewError < RuntimeError#:nodoc:
11
+ end
12
+ class NotSavableError < RuntimeError#:nodoc:
13
+ end
14
+
15
+ # Encapsulates a QBFC session.
16
+ #
17
+ # QBFC::Session.open(:app_name => 'Test Application') do |qb|
18
+ # qb.customers.find(:all).each do |customer|
19
+ # puts customer.full_name
20
+ # end
21
+ # end
22
+ #
23
+ # qb = QBFC::Session.new(:app_name => 'Test Application')
24
+ # qb.customers.find(:all).each do |customer|
25
+ # puts customer.full_name
26
+ # end
27
+ # qb.close
28
+ #
29
+ # A QBFC::Session abstracts the ole_methods so that more conventional Ruby method names are used,
30
+ # e.g. <tt>full_name</tt> instead of <tt>FullName.GetValue()</tt>.
31
+ #
32
+ # This also allows a shortcut for setting up Quickbooks objects:
33
+ #
34
+ # - session.customers.find(:all) instead of QBFC::Customer.find(session, :all)
35
+ # - session.customer('CustomerFullName') instead of QBFC::Customer.find(session, 'CustomerFullName')
36
+ # - session.customer instead of QBFC::Customer.find(session, :first)
37
+ class Session
38
+ class << self
39
+
40
+ # Open a QBFC session. Takes options as a hash, and an optional block. Options are:
41
+ #
42
+ # - +app_name+: Name that the application sends to Quickbooks (used for allowing/denying access)
43
+ # (defaults to 'Ruby QBFC Application'
44
+ # - +app_id+: Per the Quickbooks SDK (QBFC Language Reference):
45
+ # 'Normally not assigned. Use an empty string for appID.' An empty string is passed by default.
46
+ # - +conn_type+: QBFC_CONST::CtUnknown, CtLocalQBD, CtRemoteQBD, CtLocalQBDLaunchUI, or CtRemoteQBOE.
47
+ # Default is QBFC_CONST::CtLocalQBD (1)
48
+ # - +filename+: The full path to the Quickbooks file; leave blank to connect to the currently
49
+ # open company file. Default is an empty string (Quickbooks should be running).
50
+ # - +open_mode+: The desired access mode. It can be one of three values:
51
+ # - QBFC_CONST::OmSingleUser (specifies single-user mode)
52
+ # - QBFC_CONST::OmMultiUser (specifies multi-user mode)
53
+ # - QBFC_CONST::OmDontCare (accept whatever mode is currently in effect, or single-user mode if no other mode is in effect)
54
+ # Default is QBFC_CONST::OmDontCare
55
+ #
56
+ # If given a block, it yields the Session object and closes the Session and Connection
57
+ # when the block closes.
58
+ #
59
+ # Otherwise, it returns the new Session object.
60
+
61
+ def open(*options, &block)
62
+ qb = new(*options)
63
+ if block_given?
64
+ begin
65
+ yield qb
66
+ ensure
67
+ qb.close
68
+ end
69
+ else
70
+ qb
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ # See Session.open for initialization options.
77
+ def initialize(options = {})
78
+ ole_object = WIN32OLE.new("QBFC6.QBSessionManager")
79
+
80
+ ole_object.OpenConnection2(options[:app_id].to_s,
81
+ (options[:app_name] || "Ruby QBFC Application"),
82
+ (options[:conn_type] || QBFC_CONST::CtLocalQBD))
83
+
84
+ begin
85
+ ole_object.BeginSession(options[:filename].to_s,
86
+ (options[:open_mode] || QBFC_CONST::OmDontCare))
87
+ rescue WIN32OLERuntimeError
88
+ ole_object.CloseConnection
89
+ ole_object = nil
90
+ raise QBFC::QuickbooksClosedError, "BeginSession failed: Quickbooks must be open or a valid filename specified."
91
+ end
92
+
93
+ @ole_object = QBFC::OLEWrapper.new(ole_object)
94
+ end
95
+
96
+ # Close the session with Quickbooks. If this is ommitted, Quickbooks will not close.
97
+ # Using a block with Session.open ensures the session is closed.
98
+ def close
99
+ @ole_object.EndSession
100
+ @ole_object.CloseConnection
101
+ @ole_object = nil
102
+ end
103
+
104
+ # The classes method allows using <tt>session.classes.find</tt> instead of
105
+ # <tt>session.q_b_classes.find</tt>, for finds on QBClass.
106
+ def classes
107
+ QBFC::QBCollection.new(self, :QBClass)
108
+ end
109
+
110
+ # Responsible for the conversion of ole_method name to more convential Ruby method names.
111
+ # This specifies the methods for setting up an Entity, such as a Customer, directly, which is
112
+ # not included in OLEWrapper (setting up entities that are children of another entity is).
113
+ # Send other missing methods on to OLE Wrapper
114
+ def method_missing(symbol, *params) #:nodoc:
115
+ if (('a'..'z') === symbol.to_s[0].chr && symbol.to_s[-1].chr != '=')
116
+ camelized_method = symbol.to_s.camelize.to_sym
117
+ single_camelized_method = symbol.to_s.singularize.camelize.to_sym
118
+ if QBFC.const_defined?(camelized_method)
119
+ if params[0]
120
+ return QBFC::const_get(camelized_method).find_by_unique_id(self, params[0])
121
+ else
122
+ return QBFC::const_get(camelized_method).find(self, :first)
123
+ end
124
+ elsif QBFC.const_defined?(single_camelized_method)
125
+ return QBFC::QBCollection.new(self, single_camelized_method)
126
+ end
127
+ end
128
+
129
+ # Don't want to pass an OLEWrapper to a WIN32OLE method.
130
+ params = params.collect{ |p| p.respond_to?(:ole_object) ? p.ole_object : p }
131
+
132
+ @ole_object.qbfc_method_missing(self, symbol, *params)
133
+ end
134
+
135
+ end
136
+ end
data/lib/qbfc/terms.rb ADDED
@@ -0,0 +1,10 @@
1
+ module QBFC
2
+ class Terms < List
3
+ is_base_class
4
+ end
5
+ end
6
+
7
+ # Require subclass files
8
+ Dir[File.dirname(__FILE__) + '/terms/*.rb'].each do |file|
9
+ require file
10
+ end
@@ -0,0 +1,10 @@
1
+ module QBFC
2
+
3
+ # Generated Terms Types (Inherit from List)
4
+ TERMS_TYPES = %w{DateDrivenTerms StandardTerms}
5
+
6
+ # Generate Terms subclasses
7
+ # NB: All Terms are Modifiable
8
+ generate(TERMS_TYPES, Terms)
9
+
10
+ end
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/voidable'
2
+
3
+ module QBFC
4
+ class Transaction < Element
5
+ is_base_class
6
+ ID_NAME = "TxnID"
7
+
8
+ class << self
9
+
10
+ # Find by Reference Number of the Transaction record.
11
+ # +options+ are the same as those for in +find+.
12
+ def find_by_ref(sess, ref, options = {})
13
+ q = create_query(sess)
14
+ q.query.RefNumberList.Add(ref)
15
+ find(sess, :first, q, options)
16
+ end
17
+
18
+ # Find by TxnID of List record.
19
+ # +options+ are the same as those for in +find+.
20
+ def find_by_id(sess, id, options = {})
21
+ q = create_query(sess)
22
+ q.query.TxnIDList.Add(id)
23
+ find(sess, :first, q, options)
24
+ end
25
+
26
+ # Find by either ref or id. Tries id first, then ref.
27
+ def find_by_ref_or_id(*args)
28
+ find_by_id(*args) || find_by_ref(*args)
29
+ end
30
+
31
+ alias_method :find_by_unique_id, :find_by_ref_or_id
32
+
33
+ end
34
+
35
+ # Alias of TxnID for this record.
36
+ def id
37
+ @ole.txn_id
38
+ end
39
+
40
+ # Delete this Transaction
41
+ def delete
42
+ req = QBFC::Request.new(@sess, "TxnDel")
43
+ req.txn_del_type = QBFC_CONST::const_get("Tdt#{qb_name}")
44
+ req.txn_id = id
45
+ req.submit
46
+ return true
47
+ end
48
+
49
+ # Change cleared status of transaction
50
+ # status can be one of:
51
+ # - QBFC::CsCleared (or true)
52
+ # - QBFC::CsNotCleared (or false)
53
+ # - QBFC::CsPending
54
+ def cleared_status=(status)
55
+ req = QBFC::Request.new(@sess, "ClearedStatusMod")
56
+ req.txn_id = id
57
+ status = QBFC_CONST::CsCleared if status === true
58
+ status = QBFC_CONST::CsNotCleared if status === false
59
+ req.cleared_status = status
60
+ req.submit
61
+ return status
62
+ end
63
+
64
+ # Display the Transaction add (for new records) or edit dialog box
65
+ def display
66
+ if new_record?
67
+ req = QBFC::Request.new(@sess, "TxnDisplayAdd")
68
+ req.txn_display_add_type = QBFC_CONST::const_get("Tdat#{qb_name}")
69
+ else
70
+ req = QBFC::Request.new(@sess, "TxnDisplayMod")
71
+ req.txn_display_mod_type = QBFC_CONST::const_get("Tdmt#{qb_name}")
72
+ req.txn_id = id
73
+ end
74
+ req.submit
75
+ return true
76
+ end
77
+ end
78
+ end
79
+
80
+ # Require subclass files
81
+ Dir[File.dirname(__FILE__) + '/transactions/*.rb'].each do |file|
82
+ require file
83
+ end