effective_qb_sync 1.0.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 (92) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +94 -0
  4. data/Rakefile +21 -0
  5. data/app/controllers/admin/qb_syncs_controller.rb +60 -0
  6. data/app/controllers/effective/qb_sync_controller.rb +40 -0
  7. data/app/models/effective/access_denied.rb +17 -0
  8. data/app/models/effective/datatables/qb_syncs.rb +30 -0
  9. data/app/models/effective/qb_log.rb +13 -0
  10. data/app/models/effective/qb_machine.rb +281 -0
  11. data/app/models/effective/qb_order_item.rb +13 -0
  12. data/app/models/effective/qb_order_items_form.rb +55 -0
  13. data/app/models/effective/qb_request.rb +262 -0
  14. data/app/models/effective/qb_ticket.rb +55 -0
  15. data/app/models/effective/qbwc_supervisor.rb +89 -0
  16. data/app/views/admin/qb_syncs/_actions.html.haml +2 -0
  17. data/app/views/admin/qb_syncs/_qb_item_names.html.haml +9 -0
  18. data/app/views/admin/qb_syncs/index.html.haml +24 -0
  19. data/app/views/admin/qb_syncs/instructions.html.haml +136 -0
  20. data/app/views/admin/qb_syncs/show.html.haml +52 -0
  21. data/app/views/effective/orders_mailer/qb_sync_error.html.haml +56 -0
  22. data/app/views/effective/qb_sync/authenticate.erb +12 -0
  23. data/app/views/effective/qb_sync/clientVersion.erb +8 -0
  24. data/app/views/effective/qb_sync/closeConnection.erb +8 -0
  25. data/app/views/effective/qb_sync/connectionError.erb +9 -0
  26. data/app/views/effective/qb_sync/getLastError.erb +9 -0
  27. data/app/views/effective/qb_sync/receiveResponseXML.erb +8 -0
  28. data/app/views/effective/qb_sync/sendRequestXML.erb +8 -0
  29. data/app/views/effective/qb_sync/serverVersion.erb +8 -0
  30. data/app/views/effective/qb_web_connector/quickbooks.qwc.erb +12 -0
  31. data/config/routes.rb +16 -0
  32. data/db/migrate/01_create_effective_qb_sync.rb.erb +68 -0
  33. data/lib/effective_qb_sync/engine.rb +42 -0
  34. data/lib/effective_qb_sync/version.rb +3 -0
  35. data/lib/effective_qb_sync.rb +42 -0
  36. data/lib/generators/effective_qb_sync/install_generator.rb +42 -0
  37. data/lib/generators/templates/effective_qb_sync.rb +61 -0
  38. data/lib/generators/templates/effective_qb_sync_mailer_preview.rb +39 -0
  39. data/spec/dummy/README.rdoc +8 -0
  40. data/spec/dummy/Rakefile +6 -0
  41. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  42. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  43. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  44. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  45. data/spec/dummy/app/models/product.rb +14 -0
  46. data/spec/dummy/app/models/product_with_float_price.rb +13 -0
  47. data/spec/dummy/app/models/user.rb +14 -0
  48. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  49. data/spec/dummy/bin/bundle +3 -0
  50. data/spec/dummy/bin/rails +4 -0
  51. data/spec/dummy/bin/rake +4 -0
  52. data/spec/dummy/config/application.rb +32 -0
  53. data/spec/dummy/config/boot.rb +5 -0
  54. data/spec/dummy/config/database.yml +25 -0
  55. data/spec/dummy/config/environment.rb +5 -0
  56. data/spec/dummy/config/environments/development.rb +37 -0
  57. data/spec/dummy/config/environments/production.rb +80 -0
  58. data/spec/dummy/config/environments/test.rb +36 -0
  59. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  61. data/spec/dummy/config/initializers/devise.rb +254 -0
  62. data/spec/dummy/config/initializers/effective_addresses.rb +15 -0
  63. data/spec/dummy/config/initializers/effective_orders.rb +154 -0
  64. data/spec/dummy/config/initializers/effective_qb_sync.rb +41 -0
  65. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  66. data/spec/dummy/config/initializers/inflections.rb +16 -0
  67. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  68. data/spec/dummy/config/initializers/session_store.rb +3 -0
  69. data/spec/dummy/config/initializers/simple_form.rb +189 -0
  70. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  71. data/spec/dummy/config/locales/en.yml +23 -0
  72. data/spec/dummy/config/routes.rb +3 -0
  73. data/spec/dummy/config/secrets.yml +22 -0
  74. data/spec/dummy/config.ru +4 -0
  75. data/spec/dummy/db/schema.rb +208 -0
  76. data/spec/dummy/db/test.sqlite3 +0 -0
  77. data/spec/dummy/log/development.log +90 -0
  78. data/spec/dummy/log/test.log +1 -0
  79. data/spec/dummy/public/404.html +67 -0
  80. data/spec/dummy/public/422.html +67 -0
  81. data/spec/dummy/public/500.html +66 -0
  82. data/spec/dummy/public/favicon.ico +0 -0
  83. data/spec/fixtures/qbxml_response_error.xml +6 -0
  84. data/spec/fixtures/qbxml_response_success.xml +621 -0
  85. data/spec/models/acts_as_purchasable_spec.rb +131 -0
  86. data/spec/models/factories_spec.rb +32 -0
  87. data/spec/models/qb_machine_spec.rb +554 -0
  88. data/spec/models/qb_request_spec.rb +327 -0
  89. data/spec/models/qb_ticket_spec.rb +62 -0
  90. data/spec/spec_helper.rb +45 -0
  91. data/spec/support/factories.rb +97 -0
  92. metadata +397 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ad8ca346bd7681f4e298eab0a873b9d18381c5a8
4
+ data.tar.gz: 052fda34af7a8a95fd6a578ea588bf3adc32acd7
5
+ SHA512:
6
+ metadata.gz: 2bf69833eb4ed99ac58b87470c693bd6435f83551e316e4fc0913507fe7ae0718f3346f6bf4ef8b13cf1f3eaa659b7e35f1ad4eb3e7126e8a17584f9d72aa385
7
+ data.tar.gz: b700597415767d5266091bf45dffbc7f5aa487ab3665994d068e583140cdd2a9702963d7fbf5523c7db7b6ba85b1c08e078946aeacb20b7fd558df59d58fa667
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Effective Quickbooks Sync
2
+
3
+ Synchronize [effective_orders](https://github.com/code-and-effect/effective_orders) with [Quickbooks Web Connector](https://developer.intuit.com/docs/quickbooks_web_connector).
4
+
5
+ ## Getting Started
6
+
7
+ Make sure [effective_orders](https://github.com/code-and-effect/effective_orders) is installed.
8
+
9
+ Ensure all your `acts_as_purchasable` objects respond to `qb_item_name`.
10
+
11
+
12
+ Add to your Gemfile:
13
+
14
+ ```ruby
15
+ gem 'effective_qb_sync'
16
+ ```
17
+
18
+ Run the bundle command to install it:
19
+
20
+ ```console
21
+ bundle install
22
+ ```
23
+
24
+ Then run the generator:
25
+
26
+ ```ruby
27
+ rails generate effective_qb_sync:install
28
+ ```
29
+
30
+ The generator will install an initializer which describes all configuration options and creates a database migration.
31
+
32
+ If you want to tweak the table name (to use something other than the default 'qb_requests', 'qb_tickets', 'qb_logs' and 'qb_order_items'), manually adjust both the configuration file and the migration now.
33
+
34
+ Then migrate the database:
35
+
36
+ ```ruby
37
+ rake db:migrate
38
+ ```
39
+
40
+ ### Admin Screen
41
+
42
+ To use the Admin screen, please also install the effective_datatables gem:
43
+
44
+ ```ruby
45
+ gem 'effective_datatables'
46
+ ```
47
+
48
+ Then visit:
49
+
50
+ ```ruby
51
+ link_to 'Quickbooks', effective_qb_sync.admin_qb_syncs_path # /admin/qb_syncs
52
+ ```
53
+
54
+ ### Permissions
55
+
56
+ The Quickbooks synchronization controller does not require a logged in user, or any other rails permissions.
57
+
58
+ The sync itself checks that the Quickbooks user performing the sync has a username/password that matches the values in the config/initializers/effective_qb_sync.rb file.
59
+
60
+ For the admin screen, a logged in user is required (devise) and the user should be able to (CanCan):
61
+
62
+ ```ruby
63
+ can :admin, :effective_qb_sync
64
+ ```
65
+
66
+ Devise is a required dependency, but CanCan is not. Please see the authorization_method in the initializer.
67
+
68
+ ## Setting up Quickbooks
69
+
70
+ Once the gem is installed, visit the /admin/qb_syncs/instructions page for setup instructions.
71
+
72
+ ## License
73
+
74
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
75
+
76
+ Code and Effect is the product arm of [AgileStyle](http://www.agilestyle.com/), an Edmonton-based shop that specializes in building custom web applications with Ruby on Rails.
77
+
78
+ ## Testing
79
+
80
+ Run tests by:
81
+
82
+ ```ruby
83
+ rspec
84
+ ```
85
+
86
+ ## Contributing
87
+
88
+ 1. Fork it
89
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
90
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
91
+ 4. Push to the branch (`git push origin my-new-feature`)
92
+ 5. Bonus points for test coverage
93
+ 6. Create new Pull Request
94
+
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ # Testing tasks
9
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
10
+ load 'rails/tasks/engine.rake'
11
+
12
+ require "bundler/vendored_thor"
13
+ Bundler::GemHelper.install_tasks
14
+
15
+ require 'rspec/core'
16
+ require 'rspec/core/rake_task'
17
+
18
+ desc "Run all specs in spec directory (excluding plugin specs)"
19
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
20
+
21
+ task :default => :spec
@@ -0,0 +1,60 @@
1
+ module Admin
2
+ class QbSyncsController < ApplicationController
3
+ before_filter :authenticate_user! # This is devise, ensure we're logged in.
4
+ before_filter :restrict_access
5
+
6
+ layout (EffectiveQbSync.layout.kind_of?(Hash) ? EffectiveQbSync.layout[:admin_qb_tickets] : EffectiveQbSync.layout)
7
+
8
+ def index
9
+ @datatable = Effective::Datatables::QbSyncs.new() if defined?(EffectiveDatatables)
10
+ @page_title = 'Quickbooks Synchronizations'
11
+ end
12
+
13
+ def show
14
+ @qb_ticket = Effective::QbTicket.includes(:qb_requests, :qb_logs).find(params[:id])
15
+ @page_title = "Quickbooks Sync ##{@qb_ticket.id}"
16
+
17
+ @qb_order_items_form = Effective::QbOrderItemsForm.new(id: @qb_ticket.id, orders: @qb_ticket.orders)
18
+ end
19
+
20
+ def update
21
+ @qb_ticket = Effective::QbTicket.includes(:qb_requests, :qb_logs).find(params[:id])
22
+ @page_title = "Quickbooks Sync ##{@qb_ticket.id}"
23
+
24
+ @qb_order_items_form = Effective::QbOrderItemsForm.new(id: @qb_ticket.id, orders: @qb_ticket.orders)
25
+ @qb_order_items_form.qb_order_items_attributes = permitted_qb_order_items_params[:qb_order_items_attributes].values
26
+
27
+ if @qb_order_items_form.save
28
+ flash[:success] = 'Successfully updated Quickbooks item names'
29
+ redirect_to effective_qb_sync.admin_qb_sync_path(@qb_ticket)
30
+ else
31
+ flash.now[:danger] = 'Unable to update Quickbooks item names'
32
+ render action: :show
33
+ end
34
+ end
35
+
36
+ def instructions
37
+ @page_title = 'Quickbooks Setup Instructions'
38
+ end
39
+
40
+ def qwc
41
+ @filename = Rails.application.class.parent_name.downcase + '.qwc'
42
+
43
+ response.headers['Content-Disposition'] = "attachment; filename=\"#{@filename}\""
44
+
45
+ render '/effective/qb_web_connector/quickbooks.qwc', layout: false
46
+ end
47
+
48
+ private
49
+
50
+ def restrict_access
51
+ EffectiveQbSync.authorized?(self, :admin, :effective_qb_sync)
52
+ end
53
+
54
+ def permitted_qb_order_items_params
55
+ params.require(:effective_qb_order_items_form).permit(:id, qb_order_items_attributes: [:name, :id, :order_item_id])
56
+ end
57
+
58
+
59
+ end
60
+ end
@@ -0,0 +1,40 @@
1
+ module Effective
2
+ class QbSyncController < ApplicationController
3
+ skip_authorization_check if defined?(CanCan)
4
+ skip_before_filter :verify_authenticity_token
5
+
6
+ def api
7
+ # respond successfully to a GET which some versions of the Web Connector send to verify the url
8
+ (render(nothing: true) and return) if request.get?
9
+
10
+ # Examine raw post and determine which API call to process
11
+ doc = Nokogiri::XML(request.raw_post)
12
+ @qbwcSupervisor = QbwcSupervisor.new
13
+
14
+ api_verb = (doc.at_xpath('//soap:Body').children.first.node_name rescue '')
15
+
16
+ case api_verb
17
+ when 'serverVersion'
18
+ @version = '1.0'
19
+ when 'clientVersion'
20
+ @version = nil
21
+ when 'authenticate'
22
+ @token, @message = @qbwcSupervisor.authenticate(doc)
23
+ when 'sendRequestXML'
24
+ @message = @qbwcSupervisor.sendRequestXML(doc)
25
+ when 'receiveResponseXML'
26
+ @message = @qbwcSupervisor.receiveResponseXML(doc)
27
+ when 'getLastError'
28
+ @message = @qbwcSupervisor.getLastError(doc)
29
+ when 'connectionError'
30
+ @message = @qbwcSupervisor.connectionError(doc)
31
+ when 'closeConnection'
32
+ @message = @qbwcSupervisor.closeConnection(doc)
33
+ else
34
+ ''
35
+ end
36
+
37
+ render(template: "/effective/qb_sync/#{api_verb}.erb", layout: false, content_type: 'text/xml')
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,17 @@
1
+ unless defined?(Effective::AccessDenied)
2
+ module Effective
3
+ class AccessDenied < StandardError
4
+ attr_reader :action, :subject
5
+
6
+ def initialize(message = nil, action = nil, subject = nil)
7
+ @message = message
8
+ @action = action
9
+ @subject = subject
10
+ end
11
+
12
+ def to_s
13
+ @message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ if defined?(EffectiveDatatables)
2
+ module Effective
3
+ module Datatables
4
+ class QbSyncs < Effective::Datatable
5
+ datatable do
6
+ default_order :created_at, :desc
7
+
8
+ table_column :created_at
9
+ table_column :state, filter: { values: QbTicket::STATES }
10
+
11
+ array_column :num_orders, visible: false do |qb_ticket|
12
+ qb_ticket.qb_requests.length
13
+ end
14
+
15
+ array_column :orders do |qb_ticket|
16
+ qb_ticket.qb_requests.select { |qb_request| qb_request.order.present? }
17
+ .map { |qb_request| link_to "##{qb_request.order.to_param}", effective_orders.admin_order_path(qb_request.order) }
18
+ .join('<br>').html_safe
19
+ end
20
+
21
+ table_column :actions, sortable: false, filter: false, partial: 'admin/qb_syncs/actions', partial_local: :qb_sync
22
+ end
23
+
24
+ def collection
25
+ Effective::QbTicket.includes(qb_requests: :order)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Effective
2
+ class QbLog < ActiveRecord::Base
3
+ belongs_to :qb_ticket
4
+
5
+ # structure do
6
+ # message :text
7
+ # timestamps
8
+ # end
9
+
10
+ validates :qb_ticket, presence: true
11
+ validates :message, presence: true
12
+ end
13
+ end
@@ -0,0 +1,281 @@
1
+ # a state controller that will help orchestrate the different state transitions in the dance with the QBWC
2
+ # Note that all methods that are prefixed with op_ should correspond directly to the QuickBooks web connector API.
3
+
4
+ module Effective
5
+ class QbMachine
6
+
7
+ attr_reader :ticket
8
+ attr_reader :last_log_message
9
+
10
+ # creates a new machine. this implicitly creates a new ticket unless one is supplied
11
+ def initialize(ticket_id = nil)
12
+ if ticket_id
13
+ @ticket = Effective::QbTicket.find_by_id(ticket_id)
14
+ else
15
+ @ticket = Effective::QbTicket.create
16
+ end
17
+ end
18
+
19
+ # authenticates a user for a particular session
20
+ #
21
+ # returns
22
+ # - 'nvu' if the user login was invalid
23
+ # - 'none' if the user login was valid but there is no work to be done
24
+ # - '' if the user login was valid and there is work to be done
25
+ def op_authenticate(username, password)
26
+ return 'nvu' unless valid?
27
+
28
+ unless authentication_valid?(username, password)
29
+ log "Authentication failed for user #{username}"
30
+ @ticket.update_attributes!(username: username, state: 'Finished', last_error: @last_log_message)
31
+ return 'nvu' # not valid user
32
+ end
33
+
34
+ if has_work?
35
+ log "Authentication successful. Reporting to QuickBooks that there is work to be done."
36
+ @ticket.update_attributes!(username: username, state: 'Authenticated')
37
+ '' # "Any other string value = use this name for company file"
38
+ else
39
+ log "Authentication successful, but there is no work to be done"
40
+ @ticket.update_attributes!(username: username, state: 'Finished')
41
+ 'none'
42
+ end
43
+ end
44
+
45
+ # processes a message from the QBWC that corresponds to
46
+ #
47
+ # string sendRequestXML(ticket, hcpresponse, company, country, major_ver, minor_ver)
48
+ #
49
+ # in the QBWC api
50
+ #
51
+ # The params input should have members that correspond to the above parameters except for ticket, since
52
+ # the ticket has already been used to build this state machine.
53
+ #
54
+ # Returns:
55
+ # - '' if there is no work to be done
56
+ # - Some qbXML if there is work to be done
57
+ def op_send_request_xml(params)
58
+ return '' unless valid?
59
+
60
+ # update the ticket with the metadata sent at the first request for XML (i.e. if not blank)
61
+ @ticket.update_attributes!(
62
+ hpc_response: (@ticket.hpc_response || params[:hcpresponse]),
63
+ company_file_name: (@ticket.company_file_name || params[:company]),
64
+ country: (@ticket.country || params[:country]),
65
+ qbxml_major_version: (@ticket.qbxml_major_version || params[:major_ver]),
66
+ qbxml_minor_version: (@ticket.qbxml_minor_version || params[:minor_ver])
67
+ )
68
+
69
+ # only process when in the Authenticated or Processing states
70
+ unless ['Authenticated', 'Processing'].include?(@ticket.state)
71
+ @ticket.request_error!(@last_log_message)
72
+ return ''
73
+ end
74
+
75
+ # either grab the current request or create a new one
76
+ request = @ticket.qb_request
77
+ unless request
78
+ request = create_request
79
+ @ticket.qb_request = request
80
+ end
81
+
82
+ # if we don't have a request, then we are done.
83
+ unless request
84
+ log "There is no more work to be done. Marking ticket state as finished"
85
+ @ticket.update_attributes!(state: 'Finished')
86
+ return ''
87
+ end
88
+
89
+ request.update_attributes!(qb_ticket: @ticket, request_sent_at: Time.zone.now)
90
+ qb_xml = request.to_qb_xml
91
+ request.update_attributes!(request_qbxml: qb_xml)
92
+
93
+ # set the ticket into a Processing state
94
+ @ticket.state = 'Processing'
95
+
96
+ # save the changes.
97
+ @ticket.save!
98
+
99
+ log "Sending request [#{request.state}] XML to QuickBooks"
100
+
101
+ qb_xml
102
+ end
103
+
104
+ # processes a message from the QBWC that corresponds to
105
+ #
106
+ # int receiveResponseXML(ticket, response, hresult, message)
107
+ #
108
+ # in the QBWC api
109
+ #
110
+ # The params input should have members that correspond to the above parameters except for ticket
111
+ #
112
+ # Returns:
113
+ # - negative value for error
114
+ # - postive value < 100 for percent complete (more work is to be done)
115
+ # - 100 if there is no more work
116
+ def op_receive_response_xml(params)
117
+ return -1 unless valid?
118
+
119
+ # only process when in the 'Processing' state
120
+ unless @ticket.state == 'Processing'
121
+ log "Ticket state #{@ticket.state} not valid for processing responses"
122
+ @ticket.request_error! @last_log_message
123
+ return -1
124
+ end
125
+
126
+ responseXML = params[:response]
127
+ log "Received response XML from QuickBooks"
128
+
129
+ # handle a connection error
130
+ unless params[:hresult].blank? and params[:message].blank?
131
+ log "Connection error with QuickBooks: #{params[:hresult]} : #{params[:message]}"
132
+
133
+ @ticket.request_error!(@last_log_message, connection_error_hresult: params[:hresult], connection_error_message: params[:message])
134
+
135
+ # also update the request if it is able to be found
136
+ request = find_outstanding_request(responseXML)
137
+ request.update_attributes!(response_qbxml: responseXML, state: 'Error') if request
138
+
139
+ return -1
140
+ end
141
+
142
+ # find the corresponding request
143
+ request = find_outstanding_request(responseXML)
144
+
145
+ unless request
146
+ log "Received response back from QuickBooks but it did not correspond to any outstanding ticket request"
147
+ @ticket.request_error! @last_log_message
148
+ return -1
149
+ end
150
+
151
+ log "Found corresponding request [#{request.state}]"
152
+
153
+ # safety check. we should always get a response back for the current request
154
+ unless request == @ticket.qb_request
155
+ log "Received response from QuickBooks but it references a request other than the current request"
156
+ @ticket.request_error! @last_log_message
157
+ return -1
158
+ end
159
+
160
+ # process the response XML now
161
+ unless request.consume_response_xml(responseXML)
162
+ # this request for some reason did not succeeed. Update the request and the ticket
163
+ log "Request [#{request.state}] could not process the QuickBooks response: #{request.error}"
164
+ request.update_attributes!(response_qbxml: responseXML, state: 'Error')
165
+ @ticket.error! @last_log_message
166
+ return -1
167
+ end
168
+
169
+ request.update_attributes!(response_qbxml: responseXML) # This was changed for effective_qb_sync
170
+
171
+ # the request has processed the response XML. if it does not have any more work to do, then detach it
172
+
173
+ if request.has_more_work?
174
+ log "Request [#{request.state}] has more work to do on the next request"
175
+ else
176
+ # detach the current request
177
+ @ticket.update_attributes!(qb_request: nil)
178
+ log "Request [#{request.state}] has completed its work"
179
+ end
180
+
181
+ work_done = @ticket.qb_requests.size
182
+ work_left = how_much_more_work
183
+ work_left = work_left + 1 if @ticket.qb_request # if there is still a current request we need to add that to the work_left
184
+
185
+ work_left == 0 ? 100 : (work_done * 100 / (work_done + work_left))
186
+ end
187
+
188
+ # processes a message from the QBWC that corresponds to
189
+ #
190
+ # string connectionError(string ticket, string hresult, string message)
191
+ #
192
+ # in the QBWC api, signfiying that the connection with QuickBooks was lost
193
+ #
194
+ # The params input should have members that correspond to the above parameters except for ticket
195
+ #
196
+ # Returns: 'done' to indicate that activity on this ticket should not be resumed
197
+ def op_connection_error(hresult,message)
198
+ if valid?
199
+ @ticket.connection_error_hresult = hresult
200
+ @ticket.connection_error_message = message
201
+ @ticket.state = 'ConnectionError'
202
+ end
203
+
204
+ 'done'
205
+ end
206
+
207
+ # processes a message from the QBWC that corresponds to
208
+ #
209
+ # string closeConnection(string ticket)
210
+ #
211
+ # in the QBWC api, signifying that this connection should be terminated.
212
+ #
213
+ # returns the connection closing result (e.g. 'OK')
214
+ def op_close_connection
215
+ return 'Close error: invalid ticket' unless valid?
216
+
217
+ @ticket.update_attributes!(state: 'Finished') unless ['ConnectionError', 'RequestError'].include?(@ticket.state)
218
+ log "Closed connection with QuickBooks"
219
+
220
+ 'OK'
221
+ end
222
+
223
+ # processes a message from the QBWC that corresponds to
224
+ #
225
+ # string getLastError(string ticket)
226
+ #
227
+ # in the QBWC api
228
+ #
229
+ # Returns: the last error recorded for that ticket
230
+ def op_last_error
231
+ return 'Invalid Ticket Id' unless valid?
232
+ @ticket.last_error || ''
233
+ end
234
+
235
+ # returns true if this machine is a valid machine
236
+ def valid?
237
+ @ticket.present?
238
+ end
239
+
240
+ # logs a message to the ticket if it exists
241
+ def log(message)
242
+ @ticket.log(message) unless message.blank?
243
+ # always save the last log message
244
+ @last_log_message = message
245
+ end
246
+
247
+ # sets the ticket as failed and records the message
248
+ def fail_unexpectedly(message)
249
+ log "An unexpected error occurred: #{message}"
250
+ @ticket.request_error! @last_log_message
251
+ end
252
+
253
+ protected
254
+
255
+ # determines if this username and password is valid
256
+ def authentication_valid?(username,password)
257
+ (username == EffectiveQbSync.quickbooks_username) && (password == EffectiveQbSync.quickbooks_password)
258
+ end
259
+
260
+ # returns how much more work is to be done. If there is no more work to be done, it will return 0, else,
261
+ # the number of requests that need to be processed.
262
+ def how_much_more_work
263
+ Effective::QbRequest.new_requests_for_unsynced_items.size
264
+ end
265
+
266
+ # returns true if there is work to be done
267
+ def has_work?
268
+ how_much_more_work > 0
269
+ end
270
+
271
+ # creates a new request object for a unit of work to be done. returns nil if no work can be found
272
+ def create_request
273
+ Effective::QbRequest.new_requests_for_unsynced_items.first
274
+ end
275
+
276
+ # returns a qb request that corresponds to the first element in the response with a requestID
277
+ def find_outstanding_request(responseXML)
278
+ Effective::QbRequest.find_using_response_qbxml(responseXML)
279
+ end
280
+ end
281
+ end