qbwc 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -1
  3. data/.travis.yml +6 -0
  4. data/README.md +152 -144
  5. data/Rakefile +7 -1
  6. data/lib/generators/qbwc/install/install_generator.rb +20 -4
  7. data/lib/generators/qbwc/install/templates/config/qbwc.rb +42 -26
  8. data/lib/generators/qbwc/install/templates/controllers/qbwc_controller.rb +2 -38
  9. data/lib/generators/qbwc/install/templates/db/migrate/create_qbwc_jobs.rb +15 -0
  10. data/lib/generators/qbwc/install/templates/db/migrate/create_qbwc_sessions.rb +16 -0
  11. data/lib/qbwc.rb +107 -71
  12. data/lib/qbwc/active_record.rb +6 -0
  13. data/lib/qbwc/active_record/job.rb +111 -0
  14. data/lib/qbwc/active_record/session.rb +52 -0
  15. data/lib/qbwc/controller.rb +176 -0
  16. data/lib/qbwc/job.rb +81 -18
  17. data/lib/qbwc/railtie.rb +8 -0
  18. data/lib/qbwc/request.rb +14 -23
  19. data/lib/qbwc/session.rb +100 -72
  20. data/lib/qbwc/version.rb +1 -1
  21. data/lib/qbwc/worker.rb +16 -0
  22. data/qbwc.gemspec +11 -5
  23. data/test/qbwc/controllers/controller_test.rb +157 -0
  24. data/test/qbwc/integration/job_management_test.rb +86 -0
  25. data/test/qbwc/integration/request_generation_test.rb +340 -0
  26. data/test/qbwc/integration/response_test.rb +303 -0
  27. data/test/qbwc/integration/routes_test.rb +38 -0
  28. data/test/qbwc/integration/session_test.rb +94 -0
  29. data/test/test_helper.rb +248 -0
  30. data/test/wash_out_helper.rb +76 -0
  31. metadata +133 -55
  32. data/Gemfile.lock +0 -72
  33. data/lib/qbwc/soap_wrapper.rb +0 -35
  34. data/lib/qbwc/soap_wrapper/QBWebConnectorSvc.rb +0 -69
  35. data/lib/qbwc/soap_wrapper/QBWebConnectorSvc.wsdl +0 -312
  36. data/lib/qbwc/soap_wrapper/default.rb +0 -198
  37. data/lib/qbwc/soap_wrapper/defaultMappingRegistry.rb +0 -163
  38. data/lib/qbwc/soap_wrapper/defaultServant.rb +0 -133
  39. data/spec/spec_helper.rb +0 -17
@@ -1,39 +1,3 @@
1
- class QbwcController < ApplicationController
2
- require "Quickbooks"
3
- protect_from_forgery :except => :api
4
- def qwc
5
- qwc = <<-QWC
6
- <QBWCXML>
7
- <AppName>#{Rails.application.class.parent_name} #{Rails.env}</AppName>
8
- <AppID></AppID>
9
- <AppURL>#{quickbooks_url(:protocol => 'https://', :action => 'api')}</AppURL>
10
- <AppDescription>I like to describe my awesome app</AppDescription>
11
- <AppSupport>#{QBWC.support_site_url}</AppSupport>
12
- <UserName>#{QBWC.username}</UserName>
13
- <OwnerID>#{QBWC.owner_id}</OwnerID>
14
- <FileID>{90A44FB5-33D9-4815-AC85-BC87A7E7D1EB}</FileID>
15
- <QBType>QBFS</QBType>
16
- <Style>Document</Style>
17
- <Scheduler>
18
- <RunEveryNMinutes>5</RunEveryNMinutes>
19
- </Scheduler>
20
- </QBWCXML>
21
- QWC
22
- send_data qwc, :filename => 'name_me.qwc'
23
- end
24
-
25
- def api
26
- # respond successfully to a GET which some versions of the Web Connector send to verify the url
27
-
28
- if request.get?
29
- render :nothing => true
30
- return
31
- end
32
-
33
- req = request
34
- puts "========== #{ params["Envelope"]["Body"].keys.first} =========="
35
- res = QBWC::SoapWrapper.route_request(req)
36
- render :xml => res, :content_type => 'text/xml'
37
- end
38
-
1
+ class <%= controller_name.camelize %>Controller < ApplicationController
2
+ include QBWC::Controller
39
3
  end
@@ -0,0 +1,15 @@
1
+ class CreateQbwcJobs < ActiveRecord::Migration
2
+ def change
3
+ create_table :qbwc_jobs, :force => true do |t|
4
+ t.string :name
5
+ t.string :company, :limit => 1000
6
+ t.string :worker_class, :limit => 100
7
+ t.boolean :enabled, :null => false, :default => false
8
+ t.integer :request_index, :null => false, :default => 0
9
+ t.text :requests
10
+ t.boolean :requests_provided_when_job_added, :null => false, :default => false
11
+ t.text :data
12
+ t.timestamps :null => false
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ class CreateQbwcSessions < ActiveRecord::Migration
2
+ def change
3
+ create_table :qbwc_sessions, :force => true do |t|
4
+ t.string :ticket
5
+ t.string :user
6
+ t.string :company, :limit => 1000
7
+ t.integer :progress, :null => false, :default => 0
8
+ t.string :current_job
9
+ t.string :iterator_id
10
+ t.string :error, :limit => 1000
11
+ t.string :pending_jobs, :limit => 1000, :null => false, :default => ''
12
+
13
+ t.timestamps :null => false
14
+ end
15
+ end
16
+ end
@@ -1,93 +1,129 @@
1
- $:.unshift File.dirname(File.expand_path(__FILE__))
2
- require 'qbwc/version'
3
- require 'quickbooks'
1
+ require 'qbwc/railtie'
2
+ require 'qbxml'
4
3
 
5
4
  module QBWC
5
+ autoload :ActiveRecord, 'qbwc/active_record'
6
+ autoload :Controller, 'qbwc/controller'
7
+ autoload :Version, 'qbwc/version'
8
+ autoload :Job, 'qbwc/job'
9
+ autoload :Session, 'qbwc/session'
10
+ autoload :Request, 'qbwc/request'
11
+ autoload :Worker, 'qbwc/worker'
6
12
 
7
- # Web connector login credentials
13
+ # Credentials to be entered in QuickBooks Web Connector.
8
14
  mattr_accessor :username
9
- @@username = 'foo'
15
+ @@username = nil
10
16
  mattr_accessor :password
11
- @@password = 'bar'
12
-
13
- # Full path to pompany file
17
+ @@password = nil
18
+
19
+ # Path to QuickBooks company file on the client. Empty string to use whatever file is open when the connector runs.
14
20
  mattr_accessor :company_file_path
15
21
  @@company_file_path = ""
16
-
17
- # Minimum quickbooks version required for use in qbxml requests
22
+
23
+ # Instead of using hard coded username, password, and path, use a proc
24
+ # to determine who has access to what. Useful for multiple users or
25
+ # multiple company files.
26
+ mattr_accessor :authenticator
27
+ @@authenticator = nil
28
+
29
+ # QBXML version to use. Check the "Implementation" column in the QuickBooks Onscreen Reference to see which fields are supported in which versions. Newer versions of QuickBooks are backwards compatible with older QBXML versions.
18
30
  mattr_accessor :min_version
19
- @@min_version = 3.0
20
-
21
- # Quickbooks support url provided in qwc file
31
+ @@min_version = "3.0"
32
+
33
+ # Quickbooks type (either :qb or :qbpos).
34
+ mattr_reader :api
35
+ @@api = :qb
36
+
37
+ # Storage module. Only :active_record is currently supported.
38
+ mattr_accessor :storage
39
+ @@storage = :active_record
40
+
41
+ # Support URL shown in QuickBooks Web Connector. nil will use root path of the app.
22
42
  mattr_accessor :support_site_url
23
- @@support_site_url = 'http://google.com'
24
-
25
- # Quickbooks owner id provided in qwc file
43
+ @@support_site_url = nil
44
+
45
+ # Unique user GUID. If you want access by multiple users to the same file, you will need to modify this in the generated QWC file.
26
46
  mattr_accessor :owner_id
27
47
  @@owner_id = '{57F3B9B1-86F1-4fcc-B1EE-566DE1813D20}'
28
-
29
- # Job definitions
30
- mattr_reader :jobs
31
- @@jobs = {}
32
-
48
+
49
+ # How often to run web service (in minutes) or nil to only run manually.
50
+ mattr_accessor :minutes_to_run
51
+ @@minutes_to_run = nil
52
+
53
+ # Code to execute after each session is authenticated
54
+ mattr_accessor :session_initializer
55
+ @@session_initializer = nil
56
+
57
+ # In the event of an error running requests, :stop all work or :continue with the next request?
33
58
  mattr_reader :on_error
34
59
  @@on_error = 'stopOnError'
35
- # Do processing after session termination
36
- # Enabling this option will speed up qbwc session time but will necessarily eat
37
- # up more memory since every response must be stored until it is processed.
38
- mattr_accessor :delayed_processing
39
- @@delayed_processing = false
40
-
41
- # Quickbooks Type (either :qb or :qbpos)
42
- mattr_reader :api, :parser
43
- @@api = :qb #::Quickbooks::API[:qb]
60
+
61
+ # Logger to use.
62
+ mattr_accessor :logger
63
+ @@logger = Rails.logger
44
64
 
45
- # Check Rails Cache for Parser before boot
46
- mattr_accessor :warm_boot
47
- @@warm_boot = false
65
+ # Some log lines contain sensitive information
66
+ mattr_accessor :log_requests_and_responses
67
+ @@log_requests_and_responses = Rails.env == 'production' ? false : true
48
68
 
49
- class << self
69
+ class << self
50
70
 
51
- def add_job(name, &block)
52
- @@jobs[name] = Job.new(name, &block)
53
- end
54
-
55
- def on_error=(reaction)
56
- raise 'Quickbooks type must be :qb or :qbpos' unless [:stop, :continue].include?(reaction)
57
- @@on_error = "stopOnError" if reaction == :stop
58
- @@on_error = "continueOnError" if reaction == :continue
59
- end
60
-
61
- def api=(api)
62
- raise 'Quickbooks type must be :qb or :qbpos' unless [:qb, :qbpos].include?(api)
63
- @@api = api
64
- if @@warm_boot
65
- ::Rails.logger.warn "using warm boot"
66
- @@parser = ::Rails.cache.read("qb_api_#{api}") || ::Quickbooks::API[api]
67
- ::Rails.cache.write("qb_api_#{api}", @@parser)
68
- else
69
- @@parser = ::Quickbooks::API[api]
71
+ def storage_module
72
+ const_get storage.to_s.camelize
70
73
  end
71
- end
72
74
 
73
- # Allow configuration overrides
74
- def configure
75
- yield self
76
- end
75
+ def jobs
76
+ storage_module::Job.list_jobs
77
+ end
78
+
79
+ def add_job(name, enabled = true, company = nil, klass = QBWC::Worker, requests = nil, data = nil)
80
+ storage_module::Job.add_job(name, enabled, company, klass, requests, data)
81
+ end
77
82
 
83
+ def get_job(name)
84
+ storage_module::Job.find_job_with_name(name)
85
+ end
78
86
 
79
- end
87
+ def delete_job(object_or_name)
88
+ name = (object_or_name.is_a?(Job) ? object_or_name.name : object_or_name)
89
+ storage_module::Job.delete_job_with_name(name)
90
+ end
91
+
92
+ def pending_jobs(company)
93
+ js = jobs
94
+ QBWC.logger.info "#{js.length} jobs exist, checking for pending jobs for company '#{company}'."
95
+ storage_module::Job.sort_in_time_order(js.select {|job| job.company == company && job.pending?})
96
+ end
97
+
98
+ def set_session_initializer(&block)
99
+ @@session_initializer = block
100
+ self
101
+ end
102
+
103
+ def on_error=(reaction)
104
+ raise 'Quickbooks on_error must be :stop or :continue' unless [:stop, :continue].include?(reaction)
105
+ @@on_error = "stopOnError" if reaction == :stop
106
+ @@on_error = "continueOnError" if reaction == :continue
107
+ end
108
+
109
+ def api=(api)
110
+ raise 'Quickbooks type must be :qb or :qbpos' unless [:qb, :qbpos].include?(api)
111
+ @@api = api
112
+ end
113
+
114
+ def parser
115
+ @@parser ||= Qbxml.new(api, min_version)
116
+ end
117
+
118
+ # Allow configuration overrides
119
+ def configure
120
+ yield self
121
+ end
122
+
123
+ def clear_jobs
124
+ storage_module::Job.clear_jobs
125
+ end
126
+
127
+ end
80
128
 
81
129
  end
82
-
83
- require 'fiber'
84
-
85
- #Todo Move this to Autolaod
86
- require 'qbwc/soap_wrapper/default'
87
- require 'qbwc/soap_wrapper/defaultMappingRegistry'
88
- require 'qbwc/soap_wrapper/defaultServant'
89
- require 'qbwc/soap_wrapper/QBWebConnectorSvc'
90
- require 'qbwc/soap_wrapper'
91
- require 'qbwc/session'
92
- require 'qbwc/request'
93
- require 'qbwc/job'
@@ -0,0 +1,6 @@
1
+ module QBWC
2
+ module ActiveRecord
3
+ autoload 'Job', 'qbwc/active_record/job'
4
+ autoload 'Session', 'qbwc/active_record/session'
5
+ end
6
+ end
@@ -0,0 +1,111 @@
1
+ class QBWC::ActiveRecord::Job < QBWC::Job
2
+ class QbwcJob < ActiveRecord::Base
3
+ validates :name, :uniqueness => true, :presence => true
4
+ serialize :requests, Array
5
+ serialize :data
6
+
7
+ def to_qbwc_job
8
+ QBWC::ActiveRecord::Job.new(name, enabled, company, worker_class, requests, data)
9
+ end
10
+
11
+ end
12
+
13
+ # Creates and persists a job.
14
+ def self.add_job(name, enabled, company, worker_class, requests, data)
15
+
16
+ worker_class = worker_class.to_s
17
+ ar_job = find_ar_job_with_name(name).first_or_initialize
18
+ ar_job.company = company
19
+ ar_job.enabled = enabled
20
+ ar_job.request_index = 0
21
+ ar_job.worker_class = worker_class
22
+ ar_job.save!
23
+
24
+ jb = self.new(name, enabled, company, worker_class, requests, data)
25
+ jb.requests = requests.is_a?(Array) ? requests : [requests] unless requests.nil?
26
+ jb.requests_provided_when_job_added = (! requests.nil? && ! requests.empty?)
27
+ jb.data = data
28
+
29
+ jb
30
+ end
31
+
32
+ def self.find_job_with_name(name)
33
+ j = find_ar_job_with_name(name).first
34
+ j = j.to_qbwc_job unless j.nil?
35
+ return j
36
+ end
37
+
38
+ def self.find_ar_job_with_name(name)
39
+ QbwcJob.where(:name => name)
40
+ end
41
+
42
+ def find_ar_job
43
+ self.class.find_ar_job_with_name(name)
44
+ end
45
+
46
+ def self.delete_job_with_name(name)
47
+ j = find_ar_job_with_name(name).first
48
+ j.destroy unless j.nil?
49
+ end
50
+
51
+ def enabled=(value)
52
+ find_ar_job.update_all(:enabled => value)
53
+ end
54
+
55
+ def enabled?
56
+ find_ar_job.where(:enabled => true).exists?
57
+ end
58
+
59
+ def requests
60
+ find_ar_job.pluck(:requests).first
61
+ end
62
+
63
+ def requests=(r)
64
+ find_ar_job.update_all(:requests => r)
65
+ super
66
+ end
67
+
68
+ def requests_provided_when_job_added
69
+ find_ar_job.pluck(:requests_provided_when_job_added).first
70
+ end
71
+
72
+ def requests_provided_when_job_added=(value)
73
+ find_ar_job.update_all(:requests_provided_when_job_added => value)
74
+ super
75
+ end
76
+
77
+ def data
78
+ find_ar_job.pluck(:data).first
79
+ end
80
+
81
+ def data=(r)
82
+ find_ar_job.update_all(:data => r)
83
+ super
84
+ end
85
+
86
+ def request_index
87
+ find_ar_job.pluck(:request_index).first
88
+ end
89
+
90
+ def request_index=(nr)
91
+ find_ar_job.update_all(:request_index => nr)
92
+ end
93
+
94
+ def advance_next_request
95
+ nr = request_index
96
+ self.request_index = nr + 1
97
+ end
98
+
99
+ def self.list_jobs
100
+ QbwcJob.all.map {|ar_job| ar_job.to_qbwc_job}
101
+ end
102
+
103
+ def self.clear_jobs
104
+ QbwcJob.delete_all
105
+ end
106
+
107
+ def self.sort_in_time_order(ary)
108
+ ary.sort {|a,b| a.find_ar_job.first.created_at <=> b.find_ar_job.first.created_at}
109
+ end
110
+
111
+ end
@@ -0,0 +1,52 @@
1
+ class QBWC::ActiveRecord::Session < QBWC::Session
2
+ class QbwcSession < ActiveRecord::Base
3
+ attr_accessible :company, :ticket, :user unless Rails::VERSION::MAJOR >= 4
4
+ end
5
+
6
+ def self.get(ticket)
7
+ session = QbwcSession.find_by_ticket(ticket)
8
+ self.new(session) if session
9
+ end
10
+
11
+ def initialize(session_or_user = nil, company = nil, ticket = nil)
12
+ if session_or_user.is_a? QbwcSession
13
+ @session = session_or_user
14
+ # Restore current job from saved one on QbwcSession
15
+ @current_job = QBWC.get_job(@session.current_job) if @session.current_job
16
+ # Restore pending jobs from saved list on QbwcSession
17
+ @pending_jobs = @session.pending_jobs.split(',').map { |job_name| QBWC.get_job(job_name) }.select { |job| ! job.nil? }
18
+ super(@session.user, @session.company, @session.ticket)
19
+ else
20
+ super
21
+ @session = QbwcSession.new
22
+ @session.user = self.user
23
+ @session.company = self.company
24
+ @session.ticket = self.ticket
25
+ self.save
26
+ @session
27
+ end
28
+ end
29
+
30
+ def save
31
+ @session.pending_jobs = pending_jobs.map(&:name).join(',')
32
+ @session.current_job = current_job.try(:name)
33
+ @session.save
34
+ super
35
+ end
36
+
37
+ def destroy
38
+ @session.destroy
39
+ super
40
+ end
41
+
42
+ [:error, :progress, :iterator_id].each do |method|
43
+ define_method method do
44
+ @session.send(method)
45
+ end
46
+ define_method "#{method}=" do |value|
47
+ @session.send("#{method}=", value)
48
+ end
49
+ end
50
+ protected :progress=, :iterator_id=, :iterator_id
51
+
52
+ end