effective_qb_sync 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +94 -0
- data/Rakefile +21 -0
- data/app/controllers/admin/qb_syncs_controller.rb +60 -0
- data/app/controllers/effective/qb_sync_controller.rb +40 -0
- data/app/models/effective/access_denied.rb +17 -0
- data/app/models/effective/datatables/qb_syncs.rb +30 -0
- data/app/models/effective/qb_log.rb +13 -0
- data/app/models/effective/qb_machine.rb +281 -0
- data/app/models/effective/qb_order_item.rb +13 -0
- data/app/models/effective/qb_order_items_form.rb +55 -0
- data/app/models/effective/qb_request.rb +262 -0
- data/app/models/effective/qb_ticket.rb +55 -0
- data/app/models/effective/qbwc_supervisor.rb +89 -0
- data/app/views/admin/qb_syncs/_actions.html.haml +2 -0
- data/app/views/admin/qb_syncs/_qb_item_names.html.haml +9 -0
- data/app/views/admin/qb_syncs/index.html.haml +24 -0
- data/app/views/admin/qb_syncs/instructions.html.haml +136 -0
- data/app/views/admin/qb_syncs/show.html.haml +52 -0
- data/app/views/effective/orders_mailer/qb_sync_error.html.haml +56 -0
- data/app/views/effective/qb_sync/authenticate.erb +12 -0
- data/app/views/effective/qb_sync/clientVersion.erb +8 -0
- data/app/views/effective/qb_sync/closeConnection.erb +8 -0
- data/app/views/effective/qb_sync/connectionError.erb +9 -0
- data/app/views/effective/qb_sync/getLastError.erb +9 -0
- data/app/views/effective/qb_sync/receiveResponseXML.erb +8 -0
- data/app/views/effective/qb_sync/sendRequestXML.erb +8 -0
- data/app/views/effective/qb_sync/serverVersion.erb +8 -0
- data/app/views/effective/qb_web_connector/quickbooks.qwc.erb +12 -0
- data/config/routes.rb +16 -0
- data/db/migrate/01_create_effective_qb_sync.rb.erb +68 -0
- data/lib/effective_qb_sync/engine.rb +42 -0
- data/lib/effective_qb_sync/version.rb +3 -0
- data/lib/effective_qb_sync.rb +42 -0
- data/lib/generators/effective_qb_sync/install_generator.rb +42 -0
- data/lib/generators/templates/effective_qb_sync.rb +61 -0
- data/lib/generators/templates/effective_qb_sync_mailer_preview.rb +39 -0
- data/spec/dummy/README.rdoc +8 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/product.rb +14 -0
- data/spec/dummy/app/models/product_with_float_price.rb +13 -0
- data/spec/dummy/app/models/user.rb +14 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +32 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/devise.rb +254 -0
- data/spec/dummy/config/initializers/effective_addresses.rb +15 -0
- data/spec/dummy/config/initializers/effective_orders.rb +154 -0
- data/spec/dummy/config/initializers/effective_qb_sync.rb +41 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/simple_form.rb +189 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/schema.rb +208 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +90 -0
- data/spec/dummy/log/test.log +1 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/fixtures/qbxml_response_error.xml +6 -0
- data/spec/fixtures/qbxml_response_success.xml +621 -0
- data/spec/models/acts_as_purchasable_spec.rb +131 -0
- data/spec/models/factories_spec.rb +32 -0
- data/spec/models/qb_machine_spec.rb +554 -0
- data/spec/models/qb_request_spec.rb +327 -0
- data/spec/models/qb_ticket_spec.rb +62 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/support/factories.rb +97 -0
- metadata +397 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
# This is a form object for the admin_qb_syncs#update action
|
2
|
+
|
3
|
+
module Effective
|
4
|
+
class QbOrderItemsForm
|
5
|
+
include ActiveModel::Model
|
6
|
+
|
7
|
+
attr_accessor :id, :orders
|
8
|
+
|
9
|
+
def initialize(id:, orders:)
|
10
|
+
@id = id
|
11
|
+
@orders = Array(orders)
|
12
|
+
end
|
13
|
+
|
14
|
+
def qb_order_items
|
15
|
+
@qb_order_items ||= orders.flat_map { |order| order.order_items.map { |oi| oi.qb_item_name; oi.qb_order_item } }
|
16
|
+
end
|
17
|
+
|
18
|
+
# This is required by SimpleForm and Rails for non-ActiveRecord nested attributes
|
19
|
+
def qb_order_items_attributes=(qb_order_item_atts)
|
20
|
+
qb_order_item_atts.each do |attributes|
|
21
|
+
qb_order_item = qb_order_items.find { |qb_order_item| qb_order_item.order_item_id.to_s == attributes[:order_item_id] }
|
22
|
+
raise "unable to find qb_order_item with order_item_id #{attributes[:order_item_id]}" unless qb_order_item.present?
|
23
|
+
|
24
|
+
qb_order_item.attributes = attributes.except(:id, :order_item_id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def save
|
29
|
+
qb_order_items.each { |qb_order_item| qb_order_item.valid? }
|
30
|
+
return false unless qb_order_items.all? { |qb_order_item| qb_order_item.valid? }
|
31
|
+
|
32
|
+
success = false
|
33
|
+
|
34
|
+
Effective::QbOrderItem.transaction do
|
35
|
+
begin
|
36
|
+
qb_order_items.each { |qb_order_item| qb_order_item.save! }
|
37
|
+
success = true
|
38
|
+
rescue => e
|
39
|
+
raise ActiveRecord::Rollback
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
success
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_param
|
47
|
+
id
|
48
|
+
end
|
49
|
+
|
50
|
+
def persisted?
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
module Effective
|
2
|
+
class QbRequest < ActiveRecord::Base
|
3
|
+
belongs_to :qb_ticket
|
4
|
+
belongs_to :order
|
5
|
+
|
6
|
+
# NOTE: Anything that is 'raise' here finds its way to qb_ticket#error!
|
7
|
+
|
8
|
+
# these are the states that signal a request is finished
|
9
|
+
COMPLETED_STATES = ['Finished', 'Error']
|
10
|
+
PROCESSING_STATES = ['Processing', 'CustomerQuery', 'CreateCustomer', 'OrderSync']
|
11
|
+
|
12
|
+
# structure do
|
13
|
+
# request_qbxml :text
|
14
|
+
# response_qbxml :text
|
15
|
+
|
16
|
+
# request_sent_at :datetime
|
17
|
+
# response_received_at :datetime
|
18
|
+
|
19
|
+
# state :string, :validates => [:presence, :inclusion => { :in => COMPLETED_STATES + PROCESSING_STATES}]
|
20
|
+
# error :text
|
21
|
+
|
22
|
+
# site_id :integer # ActsAsSiteSpecific
|
23
|
+
|
24
|
+
# timestamps
|
25
|
+
# end
|
26
|
+
|
27
|
+
validates :state, inclusion: { in: COMPLETED_STATES + PROCESSING_STATES }
|
28
|
+
validates :qb_ticket, presence: true
|
29
|
+
validates :order, presence: true
|
30
|
+
|
31
|
+
# creates (does not persist) QbRequests for outstanding orders. The caller may choose to
|
32
|
+
# persist a request when that request starts communicating with QuickBooks
|
33
|
+
def self.new_requests_for_unsynced_items
|
34
|
+
finished_order_ids = Effective::QbRequest.where(state: 'Finished').pluck(:order_id)
|
35
|
+
Effective::Order.purchased.includes(order_items: [:purchasable, :qb_order_item])
|
36
|
+
.where.not(id: finished_order_ids).map { |order| Effective::QbRequest.new(order: order) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Finds a QbRequest using response qb_xml. If the response could not be parsed, or if there was no
|
40
|
+
# corresponding record, nil will be returned.
|
41
|
+
def self.find_using_response_qbxml(xml)
|
42
|
+
return nil if xml.blank?
|
43
|
+
element = Effective::QbRequest.find_first_response_having_a_request_id(xml)
|
44
|
+
|
45
|
+
Effective::QbRequest.find_by_id(element.attr('requestID').to_i) if element
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_more_work?
|
49
|
+
PROCESSING_STATES.include?(state)
|
50
|
+
end
|
51
|
+
|
52
|
+
def state
|
53
|
+
self[:state] || 'Processing'
|
54
|
+
end
|
55
|
+
|
56
|
+
# searches the XML and returns the first element having a requestID attribute. Since the
|
57
|
+
# application does not bundle requests (yet), this should work.
|
58
|
+
def self.find_first_response_having_a_request_id(xml)
|
59
|
+
doc = Nokogiri::XML(xml)
|
60
|
+
doc.xpath('//*[@requestID]')[0]
|
61
|
+
end
|
62
|
+
|
63
|
+
# parses the response XML and processes it.
|
64
|
+
# returns true if the responseXML indicates success, false otherwise
|
65
|
+
def consume_response_xml(xml)
|
66
|
+
update_attributes!(response_qbxml: xml)
|
67
|
+
handle_response_xml(xml)
|
68
|
+
end
|
69
|
+
|
70
|
+
# handle response xml
|
71
|
+
def handle_response_xml(xml)
|
72
|
+
case state
|
73
|
+
when 'CustomerQuery'
|
74
|
+
handle_customer_query_response_xml(xml)
|
75
|
+
when 'CreateCustomer'
|
76
|
+
handle_create_customer_response_xml(xml)
|
77
|
+
when 'OrderSync'
|
78
|
+
handle_order_sync_response_xml(xml)
|
79
|
+
else
|
80
|
+
raise "Request in state #{state} was not expecting a response from the server"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# outputs this request in qb_xml_format
|
85
|
+
def to_qb_xml
|
86
|
+
if state == 'Processing'
|
87
|
+
# this is a dummy state -- we need to transition to the CustomerQuery state before any XML goes out.
|
88
|
+
transition_state 'CustomerQuery'
|
89
|
+
end
|
90
|
+
|
91
|
+
xml = generate_request_xml
|
92
|
+
wrap_qbxml_request(xml)
|
93
|
+
end
|
94
|
+
|
95
|
+
# generates the actual request XML that will be wrapped in a qbxml_request
|
96
|
+
def generate_request_xml
|
97
|
+
# safety checks to make sure we are linked in to the order
|
98
|
+
raise 'Missing Order' unless order
|
99
|
+
|
100
|
+
if order.order_items.any? { |order_item| order_item.qb_item_name.blank? }
|
101
|
+
raise 'expected .qb_item_name() to be present on Effective::OrderItem'
|
102
|
+
end
|
103
|
+
|
104
|
+
case self.state
|
105
|
+
when 'CustomerQuery'
|
106
|
+
generate_customer_query_request_xml
|
107
|
+
when 'OrderSync'
|
108
|
+
generate_order_sync_request_xml
|
109
|
+
when 'CreateCustomer'
|
110
|
+
generate_create_customer_request_xml
|
111
|
+
else
|
112
|
+
raise "Unsupported state for generating request XML: #{state}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# transitions the request state and also outputs a log statement
|
117
|
+
def transition_state(state)
|
118
|
+
old_state = self.state
|
119
|
+
update_attributes!(state: state)
|
120
|
+
log "Transitioned request state from [#{old_state}] to [#{state}]"
|
121
|
+
end
|
122
|
+
|
123
|
+
def transition_to_finished
|
124
|
+
# We create one QbOrderItem for each OrderItem here.
|
125
|
+
order.order_items.each do |order_item|
|
126
|
+
order_item.qb_item_name
|
127
|
+
order_item.qb_order_item.save
|
128
|
+
end
|
129
|
+
|
130
|
+
transition_state('Finished')
|
131
|
+
end
|
132
|
+
|
133
|
+
# This should be private too, but test needs it
|
134
|
+
def handle_create_customer_response_xml(xml)
|
135
|
+
queryResponse = Nokogiri::XML(xml).xpath('//CustomerAddRs').first['statusCode']
|
136
|
+
statusMessage = Nokogiri::XML(xml).xpath('//CustomerAddRs').first['statusMessage']
|
137
|
+
|
138
|
+
if '0' == queryResponse
|
139
|
+
# the customer was created
|
140
|
+
log "Customer #{order.billing_name} created successfully"
|
141
|
+
transition_state 'OrderSync'
|
142
|
+
else
|
143
|
+
raise "[Order ##{order.id}] Customer #{order.billing_name} could not be created in QuickBooks: #{statusMessage}"
|
144
|
+
end
|
145
|
+
|
146
|
+
true # indicate success
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
# ensures that the total amount includes two decimal places
|
152
|
+
def qb_amount(amount)
|
153
|
+
raise 'amount should be an Integer representing the price in number of cents' unless amount.kind_of?(Integer)
|
154
|
+
sprintf('%0.2f', (amount / 100.0))
|
155
|
+
end
|
156
|
+
|
157
|
+
def truncate(str, max_chars)
|
158
|
+
str[(0...max_chars)] rescue ''
|
159
|
+
end
|
160
|
+
|
161
|
+
def generate_create_customer_request_xml
|
162
|
+
Nokogiri::XML::Builder.new do |xml|
|
163
|
+
xml.CustomerAddRq(requestID: id) {
|
164
|
+
xml.CustomerAdd {
|
165
|
+
xml.Name(truncate(order.billing_name, 41))
|
166
|
+
xml.FirstName(truncate(order.billing_name.split(' ').first, 25))
|
167
|
+
xml.LastName(truncate(order.billing_name.split(' ')[1..-1].join(' '), 25))
|
168
|
+
xml.BillAddress {
|
169
|
+
xml.Addr1(truncate(order.billing_name, 41))
|
170
|
+
xml.Addr2(truncate(order.billing_address.address1, 41))
|
171
|
+
xml.Addr3(truncate(order.billing_address.address2, 41))
|
172
|
+
xml.City(truncate(order.billing_address.city, 31))
|
173
|
+
xml.PostalCode(truncate(order.billing_address.postal_code, 13))
|
174
|
+
}
|
175
|
+
xml.Phone(truncate((order.user.try(:phone) || order.user.try(:cell_phone)), 21))
|
176
|
+
xml.Email(truncate(order.user.try(:email), 1023))
|
177
|
+
}
|
178
|
+
}
|
179
|
+
end.doc.root.to_s
|
180
|
+
end
|
181
|
+
|
182
|
+
def generate_customer_query_request_xml
|
183
|
+
Nokogiri::XML::Builder.new do |xml|
|
184
|
+
xml.CustomerQueryRq(requestID: id) {
|
185
|
+
xml.FullName(truncate(order.billing_name, 209))
|
186
|
+
}
|
187
|
+
end.doc.root.to_s
|
188
|
+
end
|
189
|
+
|
190
|
+
def handle_customer_query_response_xml(xml)
|
191
|
+
queryResponse = Nokogiri::XML(xml).xpath('//CustomerQueryRs').first['statusCode']
|
192
|
+
|
193
|
+
if '500' == queryResponse # the user was not found.
|
194
|
+
log "Customer #{order.billing_name} was not found"
|
195
|
+
transition_state 'CreateCustomer'
|
196
|
+
else # the user was found
|
197
|
+
log "Customer #{order.billing_name} exists"
|
198
|
+
transition_state 'OrderSync'
|
199
|
+
end
|
200
|
+
|
201
|
+
true # indicate success
|
202
|
+
end
|
203
|
+
|
204
|
+
# delegates logging to the ticket
|
205
|
+
def log(message)
|
206
|
+
qb_ticket.log("Request: #{message}") if qb_ticket
|
207
|
+
end
|
208
|
+
|
209
|
+
def generate_order_sync_request_xml
|
210
|
+
Nokogiri::XML::Builder.new do |xml|
|
211
|
+
xml.SalesReceiptAddRq(requestID: id) {
|
212
|
+
xml.SalesReceiptAdd {
|
213
|
+
xml.CustomerRef { xml.FullName(truncate(order.billing_name, 209)) }
|
214
|
+
xml.TxnDate(order.purchased_at.strftime("%Y-%m-%d"))
|
215
|
+
xml.Memo("Order ##{order.to_param} from website")
|
216
|
+
xml.IsToBePrinted('false')
|
217
|
+
xml.IsToBeEmailed('false')
|
218
|
+
|
219
|
+
order.order_items.each do |order_item|
|
220
|
+
xml.SalesReceiptLineAdd {
|
221
|
+
xml.ItemRef { xml.FullName(order_item.qb_item_name) }
|
222
|
+
xml.Desc(order_item.title)
|
223
|
+
xml.Amount(qb_amount(order_item.subtotal))
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
if EffectiveQbSync.quickbooks_tax_name.present?
|
228
|
+
xml.SalesReceiptLineAdd {
|
229
|
+
xml.ItemRef { xml.FullName(EffectiveQbSync.quickbooks_tax_name) }
|
230
|
+
xml.Desc(EffectiveQbSync.quickbooks_tax_name)
|
231
|
+
xml.Amount(qb_amount(order.tax))
|
232
|
+
}
|
233
|
+
end
|
234
|
+
}
|
235
|
+
}
|
236
|
+
end.doc.root.to_s
|
237
|
+
end
|
238
|
+
|
239
|
+
def handle_order_sync_response_xml(xml)
|
240
|
+
queryResponse = Nokogiri::XML(xml).xpath('//SalesReceiptAddRs').first['statusCode']
|
241
|
+
statusMessage = Nokogiri::XML(xml).xpath('//SalesReceiptAddRs').first['statusMessage']
|
242
|
+
|
243
|
+
if '0' == queryResponse
|
244
|
+
log "Order #{order.to_param} successfully syncronized"
|
245
|
+
transition_to_finished
|
246
|
+
elsif '3180' == queryResponse
|
247
|
+
log "Order #{order.to_param} was not recorded by quickbooks because it was an empty transaction"
|
248
|
+
transition_to_finished
|
249
|
+
else
|
250
|
+
raise "[Order ##{order.to_param}] could not be synchronized with QuickBooks: #{statusMessage}"
|
251
|
+
end
|
252
|
+
|
253
|
+
true # indicate success
|
254
|
+
end
|
255
|
+
|
256
|
+
# Simple wrapping helper
|
257
|
+
|
258
|
+
def wrap_qbxml_request(body)
|
259
|
+
'<?xml version="1.0" ?><?qbxml version="6.0" ?><QBXML><QBXMLMsgsRq onError="continueOnError">' + (body || '') + '</QBXMLMsgsRq></QBXML>'
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Effective
|
2
|
+
class QbTicket < ActiveRecord::Base
|
3
|
+
belongs_to :qb_request # the current request
|
4
|
+
has_many :qb_requests
|
5
|
+
has_many :orders, through: :qb_requests
|
6
|
+
has_many :qb_logs
|
7
|
+
|
8
|
+
STATES = ['Ready', 'Authenticated', 'Processing', 'Finished', 'ConnectionError', 'RequestError']
|
9
|
+
|
10
|
+
# structure do
|
11
|
+
# username :string
|
12
|
+
# company_file_name :string
|
13
|
+
# country :string
|
14
|
+
|
15
|
+
# qbxml_major_version :string
|
16
|
+
# qbxml_minor_version :string
|
17
|
+
|
18
|
+
# state :string, :default => 'Ready', :validates => [:presence, :inclusion => { :in => STATES}]
|
19
|
+
# percent :integer, :default => 0
|
20
|
+
|
21
|
+
# hpc_response :text
|
22
|
+
# connection_error_hresult :text
|
23
|
+
# connection_error_message :text
|
24
|
+
# last_error :text
|
25
|
+
|
26
|
+
# site_id :integer # ActsAsSiteSpecific
|
27
|
+
|
28
|
+
# timestamps
|
29
|
+
# end
|
30
|
+
|
31
|
+
validates :state, inclusion: { in: STATES }
|
32
|
+
|
33
|
+
def request_error!(error, atts={})
|
34
|
+
self.error!(error, atts.reverse_merge({state: 'RequestError'}))
|
35
|
+
end
|
36
|
+
|
37
|
+
# This is the entry point for a standard error.
|
38
|
+
def error!(error, atts={})
|
39
|
+
Effective::OrdersMailer.order_error(
|
40
|
+
order: qb_request.try(:order),
|
41
|
+
error: error,
|
42
|
+
to: EffectiveQbSync.error_email,
|
43
|
+
subject: "Quickbooks failed to synchronize order ##{qb_request.try(:order).try(:to_param) || 'unknown'}",
|
44
|
+
template: 'qb_sync_error'
|
45
|
+
).try(:deliver_now).try(:deliver)
|
46
|
+
|
47
|
+
self.update_attributes!(atts.reverse_merge({last_error: error}))
|
48
|
+
end
|
49
|
+
|
50
|
+
# persists a new log message to this ticket
|
51
|
+
def log(message)
|
52
|
+
qb_logs.create(message: message, qb_ticket: self)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Effective
|
2
|
+
class QbwcSupervisor
|
3
|
+
QBXML = 'http://developer.intuit.com/'
|
4
|
+
|
5
|
+
def authenticate(doc)
|
6
|
+
username = doc.at_xpath('//qbxml:strUserName', 'qbxml' => QBXML).content
|
7
|
+
password = doc.at_xpath('//qbxml:strPassword', 'qbxml' => QBXML).content
|
8
|
+
|
9
|
+
attempt do |m|
|
10
|
+
return [m.ticket.id.to_s, m.op_authenticate(username, password)]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def sendRequestXML(doc)
|
15
|
+
ticket = doc.at_xpath('//qbxml:ticket', 'qbxml' => QBXML).content
|
16
|
+
strHCPResponse = doc.at_xpath('//qbxml:strHCPResponse', 'qbxml' => QBXML).content
|
17
|
+
strCompanyFileName = doc.at_xpath('//qbxml:strCompanyFileName', 'qbxml' => QBXML).content
|
18
|
+
qbXMLCountry = doc.at_xpath('//qbxml:qbXMLCountry', 'qbxml' => QBXML).content
|
19
|
+
qbXMLMajorVers = doc.at_xpath('//qbxml:qbXMLMajorVers', 'qbxml' => QBXML).content
|
20
|
+
qbXMLMinorVers = doc.at_xpath('//qbxml:qbXMLMinorVers', 'qbxml' => QBXML).content
|
21
|
+
|
22
|
+
params = {
|
23
|
+
ticket: ticket,
|
24
|
+
hcpresponse: strHCPResponse,
|
25
|
+
company: strCompanyFileName,
|
26
|
+
country: qbXMLCountry,
|
27
|
+
major_ver: qbXMLMajorVers,
|
28
|
+
minor_ver: qbXMLMinorVers
|
29
|
+
}
|
30
|
+
|
31
|
+
attempt(ticket) do |m|
|
32
|
+
return m.op_send_request_xml(params)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def receiveResponseXML(doc)
|
37
|
+
ticket = doc.at_xpath('//qbxml:ticket', 'qbxml' => QBXML).content
|
38
|
+
response = doc.at_xpath('//qbxml:response', 'qbxml' => QBXML).content
|
39
|
+
hresult = doc.at_xpath('//qbxml:hresult', 'qbxml' => QBXML).content
|
40
|
+
message = doc.at_xpath('//qbxml:message', 'qbxml' => QBXML).content
|
41
|
+
|
42
|
+
params = { ticket: ticket, response: response, hresult: hresult, message: message }
|
43
|
+
|
44
|
+
attempt(ticket) do |m|
|
45
|
+
return m.op_receive_response_xml(params)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def connectionError(doc)
|
50
|
+
ticket = doc.at_xpath('//qbxml:ticket', 'qbxml' => QBXML).content
|
51
|
+
hresult = doc.at_xpath('//qbxml:hresult', 'qbxml' => QBXML).content
|
52
|
+
message = doc.at_xpath('//qbxml:message', 'qbxml' => QBXML).content
|
53
|
+
|
54
|
+
attempt(ticket) do |m|
|
55
|
+
return m.op_connection_error(hresult, message)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def closeConnection(doc)
|
60
|
+
ticket = doc.at_xpath('//qbxml:ticket', 'qbxml' => QBXML).content
|
61
|
+
|
62
|
+
attempt(ticket) do |m|
|
63
|
+
return m.op_close_connection
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def getLastError(doc)
|
68
|
+
ticket = doc.at_xpath('//qbxml:ticket', 'qbxml' => QBXML).content
|
69
|
+
|
70
|
+
attempt(ticket) do |m|
|
71
|
+
return m.op_last_error
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# executes an operation on a machine safely, recording the failure if one occurs
|
78
|
+
def attempt(ticket=nil)
|
79
|
+
@qb_machine = Effective::QbMachine.new(ticket)
|
80
|
+
begin
|
81
|
+
return yield(@qb_machine)
|
82
|
+
rescue
|
83
|
+
@qb_machine.fail_unexpectedly($!)
|
84
|
+
end
|
85
|
+
false
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|