jm81-qbfc 0.3.0

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 (91) hide show
  1. data/.gitignore +11 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +85 -0
  4. data/Rakefile +81 -0
  5. data/VERSION +1 -0
  6. data/init.rb +3 -0
  7. data/install.rb +1 -0
  8. data/lib/qbfc.rb +41 -0
  9. data/lib/qbfc/base.rb +82 -0
  10. data/lib/qbfc/element.rb +243 -0
  11. data/lib/qbfc/entities/generated.rb +8 -0
  12. data/lib/qbfc/entity.rb +11 -0
  13. data/lib/qbfc/info.rb +42 -0
  14. data/lib/qbfc/infos/generated.rb +9 -0
  15. data/lib/qbfc/item.rb +29 -0
  16. data/lib/qbfc/items/generated.rb +11 -0
  17. data/lib/qbfc/list.rb +84 -0
  18. data/lib/qbfc/lists/account.rb +24 -0
  19. data/lib/qbfc/lists/generated.rb +15 -0
  20. data/lib/qbfc/lists/qb_class.rb +25 -0
  21. data/lib/qbfc/modifiable.rb +31 -0
  22. data/lib/qbfc/ole_wrapper.rb +201 -0
  23. data/lib/qbfc/qb_collection.rb +26 -0
  24. data/lib/qbfc/qb_types.rb +18 -0
  25. data/lib/qbfc/qbfc_const.rb +14 -0
  26. data/lib/qbfc/report.rb +95 -0
  27. data/lib/qbfc/reports/aging.rb +13 -0
  28. data/lib/qbfc/reports/budget_summary.rb +13 -0
  29. data/lib/qbfc/reports/custom_detail.rb +9 -0
  30. data/lib/qbfc/reports/custom_summary.rb +9 -0
  31. data/lib/qbfc/reports/general_detail.rb +44 -0
  32. data/lib/qbfc/reports/general_summary.rb +33 -0
  33. data/lib/qbfc/reports/job.rb +14 -0
  34. data/lib/qbfc/reports/payroll_detail.rb +13 -0
  35. data/lib/qbfc/reports/payroll_summary.rb +13 -0
  36. data/lib/qbfc/reports/rows.rb +51 -0
  37. data/lib/qbfc/reports/time.rb +12 -0
  38. data/lib/qbfc/request.rb +295 -0
  39. data/lib/qbfc/session.rb +147 -0
  40. data/lib/qbfc/terms.rb +10 -0
  41. data/lib/qbfc/terms/generated.rb +10 -0
  42. data/lib/qbfc/transaction.rb +110 -0
  43. data/lib/qbfc/transactions/generated.rb +25 -0
  44. data/lib/qbfc/voidable.rb +11 -0
  45. data/spec/fixtures/test.lgb +0 -0
  46. data/spec/fixtures/test.qbw +0 -0
  47. data/spec/fixtures/test.qbw.TLG +0 -0
  48. data/spec/integration/add_spec.rb +31 -0
  49. data/spec/integration/base_spec.rb +18 -0
  50. data/spec/integration/belongs_to_spec.rb +64 -0
  51. data/spec/integration/company_spec.rb +30 -0
  52. data/spec/integration/conditions_spec.rb +59 -0
  53. data/spec/integration/customer_spec.rb +46 -0
  54. data/spec/integration/element_finders_spec.rb +20 -0
  55. data/spec/integration/quick_test.rb +31 -0
  56. data/spec/integration/request_options_spec.rb +68 -0
  57. data/spec/rcov.opts +1 -0
  58. data/spec/spec.opts +6 -0
  59. data/spec/spec_helper.rb +62 -0
  60. data/spec/unit/base_spec.rb +138 -0
  61. data/spec/unit/element_finder_spec.rb +185 -0
  62. data/spec/unit/element_spec.rb +108 -0
  63. data/spec/unit/entities/generated_spec.rb +18 -0
  64. data/spec/unit/entity_spec.rb +18 -0
  65. data/spec/unit/info/generated_spec.rb +12 -0
  66. data/spec/unit/info_spec.rb +48 -0
  67. data/spec/unit/item_spec.rb +33 -0
  68. data/spec/unit/items/generated_spec.rb +16 -0
  69. data/spec/unit/list_finders_spec.rb +129 -0
  70. data/spec/unit/list_spec.rb +86 -0
  71. data/spec/unit/lists/account_spec.rb +20 -0
  72. data/spec/unit/lists/generated_spec.rb +15 -0
  73. data/spec/unit/lists/qb_class_spec.rb +9 -0
  74. data/spec/unit/modifiable_spec.rb +84 -0
  75. data/spec/unit/ole_wrapper_spec.rb +337 -0
  76. data/spec/unit/qb_collection_spec.rb +13 -0
  77. data/spec/unit/qbfc_const_spec.rb +10 -0
  78. data/spec/unit/qbfc_spec.rb +10 -0
  79. data/spec/unit/report_spec.rb +12 -0
  80. data/spec/unit/request_query_survey.txt +48 -0
  81. data/spec/unit/request_spec.rb +486 -0
  82. data/spec/unit/session_spec.rb +144 -0
  83. data/spec/unit/terms/generated_spec.rb +14 -0
  84. data/spec/unit/terms_spec.rb +18 -0
  85. data/spec/unit/transaction_finders_spec.rb +125 -0
  86. data/spec/unit/transaction_spec.rb +94 -0
  87. data/spec/unit/transactions/generated_spec.rb +20 -0
  88. data/spec/unit/voidable_spec.rb +32 -0
  89. data/tasks/qbfc_tasks.rake +4 -0
  90. data/uninstall.rb +1 -0
  91. metadata +180 -0
@@ -0,0 +1,147 @@
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,
91
+ "BeginSession failed: Quickbooks must be open or a valid filename specified.\n\n#{$!}")
92
+ end
93
+
94
+ @ole_object = QBFC::OLEWrapper.new(ole_object)
95
+ end
96
+
97
+ # Close the session with Quickbooks. If this is ommitted, Quickbooks will not close.
98
+ # Using a block with Session.open ensures the session is closed.
99
+ def close
100
+ @ole_object.EndSession
101
+ @ole_object.CloseConnection
102
+ @ole_object = nil
103
+ end
104
+
105
+ # Generate a report with the given +name+ and +args+.
106
+ # (See QBFC::Report.get for details).
107
+ def report(name, *args)
108
+ Report.get(self, name, *args)
109
+ end
110
+
111
+ # The classes method allows using <tt>session.classes.find</tt> instead of
112
+ # <tt>session.q_b_classes.find</tt>, for finds on QBClass.
113
+ def classes
114
+ QBFC::QBCollection.new(self, :QBClass)
115
+ end
116
+
117
+ # Responsible for the conversion of ole_method name to more convential Ruby method names.
118
+ # This specifies the methods for setting up an Entity, such as a Customer, directly, which is
119
+ # not included in OLEWrapper (setting up entities that are children of another entity is).
120
+ # Send other missing methods on to OLE Wrapper
121
+ def method_missing(symbol, *params) #:nodoc:
122
+ if (('a'..'z') === symbol.to_s[0].chr && symbol.to_s[-1].chr != '=')
123
+ camelized_method = symbol.to_s.camelize.to_sym
124
+ if camelized_method.to_s =~ /Terms\Z/
125
+ single_camelized_method = camelized_method
126
+ else
127
+ single_camelized_method = symbol.to_s.singularize.camelize.to_sym
128
+ end
129
+ if QBFC.const_defined?(camelized_method) && camelized_method.to_s !~ /Terms\Z/
130
+ if params[0]
131
+ return QBFC::const_get(camelized_method).find_by_unique_id(self, params[0])
132
+ else
133
+ return QBFC::const_get(camelized_method).find(self, :first)
134
+ end
135
+ elsif QBFC.const_defined?(single_camelized_method)
136
+ return QBFC::QBCollection.new(self, single_camelized_method)
137
+ end
138
+ end
139
+
140
+ # Don't want to pass an OLEWrapper to a WIN32OLE method.
141
+ params = params.collect{ |p| p.respond_to?(:ole_object) ? p.ole_object : p }
142
+
143
+ @ole_object.qbfc_method_missing(self, symbol, *params)
144
+ end
145
+
146
+ end
147
+ 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.new(File.dirname(__FILE__) + '/terms').each do |file|
9
+ require('qbfc/terms/' + File.basename(file)) if File.extname(file) == ".rb"
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,110 @@
1
+ require 'qbfc/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
+ def base_class_find(sess, what, q, options)
34
+ q.IncludeRetElementList.Add(self::ID_NAME)
35
+ q.IncludeRetElementList.Add('TxnType')
36
+ list = q.response
37
+
38
+ if list.nil?
39
+ (what == :all) ? [] : nil
40
+ else
41
+ ary = (0..(list.Count - 1)).collect { |i|
42
+ element = list.GetAt(i)
43
+ ret_class_name = element.TxnType.GetAsString
44
+ if QBFC::const_defined?(ret_class_name)
45
+ ret_class = QBFC::const_get(ret_class_name)
46
+ ret_class.find(sess, element.send(ret_class::ID_NAME).GetValue, options.dup)
47
+ else
48
+ find(sess, element.send(Transaction::ID_NAME).GetValue, options.dup.merge(:ignore_base_class => true))
49
+ end
50
+ }
51
+
52
+ if what == :all
53
+ ary
54
+ else
55
+ ary[0]
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ # Alias of TxnID for this record.
63
+ def id
64
+ @ole.txn_id
65
+ end
66
+
67
+ # Delete this Transaction
68
+ def delete
69
+ req = QBFC::Request.new(@sess, "TxnDel")
70
+ req.txn_del_type = QBFC_CONST::const_get("Tdt#{qb_name}")
71
+ req.txn_id = id
72
+ req.submit
73
+ return true
74
+ end
75
+
76
+ # Change cleared status of transaction
77
+ # status can be one of:
78
+ # - QBFC::CsCleared (or true)
79
+ # - QBFC::CsNotCleared (or false)
80
+ # - QBFC::CsPending
81
+ def cleared_status=(status)
82
+ req = QBFC::Request.new(@sess, "ClearedStatusMod")
83
+ req.txn_id = id
84
+ status = QBFC_CONST::CsCleared if status === true
85
+ status = QBFC_CONST::CsNotCleared if status === false
86
+ req.cleared_status = status
87
+ req.submit
88
+ return status
89
+ end
90
+
91
+ # Display the Transaction add (for new records) or edit dialog box
92
+ def display
93
+ if new_record?
94
+ req = QBFC::Request.new(@sess, "TxnDisplayAdd")
95
+ req.txn_display_add_type = QBFC_CONST::const_get("Tdat#{qb_name}")
96
+ else
97
+ req = QBFC::Request.new(@sess, "TxnDisplayMod")
98
+ req.txn_display_mod_type = QBFC_CONST::const_get("Tdmt#{qb_name}")
99
+ req.txn_id = id
100
+ end
101
+ req.submit
102
+ return true
103
+ end
104
+ end
105
+ end
106
+
107
+ # Require subclass files
108
+ Dir.new(File.dirname(__FILE__) + '/transactions').each do |file|
109
+ require('qbfc/transactions/' + File.basename(file)) if File.extname(file) == ".rb"
110
+ end
@@ -0,0 +1,25 @@
1
+ module QBFC
2
+
3
+ # Generated Transaction types
4
+ TXN_TYPES = %w{ARRefundCreditCard Bill BillPaymentCheck BillPaymentCreditCard
5
+ BuildAssembly Charge Check CreditCardCharge CreditCardCredit CreditMemo
6
+ Deposit Estimate InventoryAdjustment Invoice ItemReceipt JournalEntry
7
+ PurchaseOrder ReceivePayment SalesOrder SalesReceipt SalesTaxPaymentCheck
8
+ TimeTracking VehicleMileage VendorCredit}
9
+
10
+ # Generated Transaction types that support TxnVoid Request
11
+ TXN_VOIDABLE_TYPES = %w{ARRefundCreditCard Bill BillPaymentCheck
12
+ BillPaymentCreditCard Charge Check CreditCardCharge CreditCardCredit
13
+ CreditMemo Deposit InventoryAdjustment Invoice ItemReceipt JournalEntry
14
+ SalesReceipt VendorCredit}
15
+
16
+ # Generated Transaction types that don't support Mod Requests
17
+ TXN_NO_MOD_TYPES = %w{ARRefundCreditCard BillPaymentCreditCard Deposit
18
+ InventoryAdjustment VehicleMileage VendorCredit }
19
+
20
+ # Generate Transaction subclasses
21
+ generate(TXN_TYPES, Transaction,
22
+ { Modifiable => (TXN_TYPES - TXN_NO_MOD_TYPES),
23
+ Voidable => TXN_VOIDABLE_TYPES })
24
+
25
+ end
@@ -0,0 +1,11 @@
1
+ module QBFC
2
+ module Voidable
3
+ def void
4
+ req = QBFC::Request.new(@sess, "TxnVoid")
5
+ req.txn_void_type = QBFC_CONST::const_get("Tvt#{qb_name}")
6
+ req.txn_id = id
7
+ req.submit
8
+ return true
9
+ end
10
+ end
11
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "QBFC::Customer.new" do
4
+
5
+ before(:each) do
6
+ @integration = QBFC::Integration.new
7
+ @sess = @integration.session
8
+ end
9
+
10
+ after(:each) do
11
+ @integration.close
12
+ end
13
+
14
+ it "should create a new customer" do
15
+ old_count = @sess.customers.find(:all).length
16
+
17
+ c = @sess.customers.new
18
+ c.name = "Cranky Customer"
19
+ c.is_active = true
20
+ c.last_name = "McCustomer"
21
+ c.save
22
+
23
+ n = @sess.customers.find("Cranky Customer")
24
+ n.name.should == "Cranky Customer"
25
+ n.is_active.should be(true)
26
+ n.last_name.should == "McCustomer"
27
+
28
+ @sess.customers.find(:all).length.should == old_count + 1
29
+ end
30
+
31
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe QBFC::Base do
4
+
5
+ before(:each) do
6
+ @integration = QBFC::Integration::reader
7
+ @sess = @integration.session
8
+ end
9
+
10
+ after(:each) do
11
+ @integration.close
12
+ end
13
+
14
+ it "should do nothing" do
15
+ true.should be_true
16
+ end
17
+
18
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ # This spec describes "belong to" style relationships,
4
+ # that is where QuickBooks specifies a Ref to another object.
5
+
6
+ describe "belongs to: " do
7
+
8
+ before(:each) do
9
+ @integration = QBFC::Integration::reader
10
+ @sess = @integration.session
11
+ end
12
+
13
+ after(:each) do
14
+ @integration.close
15
+ end
16
+
17
+ describe "Bob Customer" do
18
+ before(:each) do
19
+ @customer = @sess.customers.find("Bob Customer")
20
+ end
21
+
22
+ it "has terms" do
23
+ @customer.terms.should be_kind_of(QBFC::Terms)
24
+ @customer.terms.id.should == @sess.terms.find("Net 30").id
25
+ end
26
+
27
+ it "should not have a sales rep" do
28
+ @customer.sales_rep.should be_nil
29
+ end
30
+ end
31
+
32
+ describe "Check to ABC Supplies" do
33
+ before(:each) do
34
+ @check = @sess.checks.find_by_ref("1000")
35
+ end
36
+
37
+ it "has a payee" do
38
+ @check.payee.should be_kind_of(QBFC::Vendor)
39
+ @check.payee.id.should == @sess.vendors.find("ABC Supplies").id
40
+ end
41
+
42
+ it "has an account" do
43
+ @check.account.should be_kind_of(QBFC::Account)
44
+ @check.account.id.should == @sess.accounts.find("Checking").id
45
+ end
46
+ end
47
+
48
+ describe "Invoice to Customer Bob" do
49
+ before(:each) do
50
+ @invoice = @sess.invoices.find_by_ref("1")
51
+ end
52
+
53
+ it "has a template" do
54
+ @invoice.template.should be_kind_of(QBFC::Template)
55
+ @invoice.template.id.should == @sess.templates.find("Intuit Service Invoice").id
56
+ end
57
+
58
+ it "has a Customer" do
59
+ @invoice.customer.should be_kind_of(QBFC::Customer)
60
+ @invoice.customer.id.should == @sess.customers.find("Bob Customer").id
61
+ end
62
+ end
63
+
64
+ end