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.
- 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
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,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
|