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