qbwc 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -1
- data/.travis.yml +6 -0
- data/README.md +152 -144
- data/Rakefile +7 -1
- data/lib/generators/qbwc/install/install_generator.rb +20 -4
- data/lib/generators/qbwc/install/templates/config/qbwc.rb +42 -26
- data/lib/generators/qbwc/install/templates/controllers/qbwc_controller.rb +2 -38
- data/lib/generators/qbwc/install/templates/db/migrate/create_qbwc_jobs.rb +15 -0
- data/lib/generators/qbwc/install/templates/db/migrate/create_qbwc_sessions.rb +16 -0
- data/lib/qbwc.rb +107 -71
- data/lib/qbwc/active_record.rb +6 -0
- data/lib/qbwc/active_record/job.rb +111 -0
- data/lib/qbwc/active_record/session.rb +52 -0
- data/lib/qbwc/controller.rb +176 -0
- data/lib/qbwc/job.rb +81 -18
- data/lib/qbwc/railtie.rb +8 -0
- data/lib/qbwc/request.rb +14 -23
- data/lib/qbwc/session.rb +100 -72
- data/lib/qbwc/version.rb +1 -1
- data/lib/qbwc/worker.rb +16 -0
- data/qbwc.gemspec +11 -5
- data/test/qbwc/controllers/controller_test.rb +157 -0
- data/test/qbwc/integration/job_management_test.rb +86 -0
- data/test/qbwc/integration/request_generation_test.rb +340 -0
- data/test/qbwc/integration/response_test.rb +303 -0
- data/test/qbwc/integration/routes_test.rb +38 -0
- data/test/qbwc/integration/session_test.rb +94 -0
- data/test/test_helper.rb +248 -0
- data/test/wash_out_helper.rb +76 -0
- metadata +133 -55
- data/Gemfile.lock +0 -72
- data/lib/qbwc/soap_wrapper.rb +0 -35
- data/lib/qbwc/soap_wrapper/QBWebConnectorSvc.rb +0 -69
- data/lib/qbwc/soap_wrapper/QBWebConnectorSvc.wsdl +0 -312
- data/lib/qbwc/soap_wrapper/default.rb +0 -198
- data/lib/qbwc/soap_wrapper/defaultMappingRegistry.rb +0 -163
- data/lib/qbwc/soap_wrapper/defaultServant.rb +0 -133
- data/spec/spec_helper.rb +0 -17
@@ -1,39 +1,3 @@
|
|
1
|
-
class
|
2
|
-
|
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
|
data/lib/qbwc.rb
CHANGED
@@ -1,93 +1,129 @@
|
|
1
|
-
|
2
|
-
require '
|
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
|
-
#
|
13
|
+
# Credentials to be entered in QuickBooks Web Connector.
|
8
14
|
mattr_accessor :username
|
9
|
-
@@username =
|
15
|
+
@@username = nil
|
10
16
|
mattr_accessor :password
|
11
|
-
@@password =
|
12
|
-
|
13
|
-
#
|
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
|
-
#
|
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
|
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 =
|
24
|
-
|
25
|
-
#
|
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
|
-
#
|
30
|
-
|
31
|
-
@@
|
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
|
-
|
36
|
-
#
|
37
|
-
|
38
|
-
|
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
|
-
#
|
46
|
-
mattr_accessor :
|
47
|
-
@@
|
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
|
-
|
52
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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,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
|