148 Commits
0.0.6 ... 0.1.0

Author SHA1 Message Date
06e827fff8 Version bump 0.1.0 2016-08-01 22:30:47 -04:00
b1844689df Update qbo_controller.rb 2016-08-01 22:29:53 -04:00
a4263a92ca Update issues_show_hook_listener.rb 2016-08-01 22:20:57 -04:00
14cc251809 Update Gemfile 2016-08-01 21:56:17 -04:00
471e8f3398 Update qbo_controller.rb 2016-08-01 21:56:00 -04:00
dadbda62c6 Update qbo_controller.rb 2016-08-01 21:55:30 -04:00
df47efe816 Update qbo_controller.rb 2016-08-01 21:54:31 -04:00
03cc6943a3 Update Gemfile 2016-08-01 21:51:04 -04:00
6f0163ce7d Update qbo_controller.rb 2016-08-01 21:49:17 -04:00
91110adad5 Update qbo_controller.rb 2016-08-01 21:47:30 -04:00
c2f48d0277 Update qbo_controller.rb 2016-08-01 21:41:47 -04:00
06344b6498 Update qbo_controller.rb 2016-08-01 21:34:14 -04:00
4ff2b2bdc6 Update _settings.html.erb 2016-08-01 21:28:15 -04:00
a71dd310fe Update issues_show_hook_listener.rb 2016-08-01 21:24:22 -04:00
90da7a5d74 Update issues_show_hook_listener.rb 2016-08-01 21:23:15 -04:00
6505f54c7f Update issues_show_hook_listener.rb 2016-08-01 21:22:31 -04:00
c4a488e5a7 Update issues_show_hook_listener.rb 2016-08-01 21:21:41 -04:00
71817f5ca8 Update issues_show_hook_listener.rb 2016-08-01 21:18:31 -04:00
77c97ef2c1 Update vehicle.rb 2016-08-01 21:14:02 -04:00
875ec19e38 Update customer.rb 2016-08-01 21:09:39 -04:00
f47e77f816 Version bump 0.0.8 2016-08-01 21:08:49 -04:00
144a52f813 Add files via upload 2016-08-01 21:07:01 -04:00
f9a5269fd7 Delete plugin_issue_edit.png 2016-08-01 21:06:48 -04:00
bc1445f8bb Update qbo_employee.rb 2016-08-01 21:01:06 -04:00
01e5415074 Update qbo_controller.rb 2016-08-01 20:57:51 -04:00
697ff4f9d5 Update qbo_controller.rb 2016-08-01 20:54:42 -04:00
fe7cfc6b1d Update qbo_controller.rb 2016-08-01 20:52:24 -04:00
7a7e148719 Update README.md 2016-08-01 20:38:04 -04:00
95db8f9839 Update _list.html.erb 2016-08-01 20:31:48 -04:00
7fb91ae10b Update _list.html.erb 2016-08-01 20:29:58 -04:00
0c5c778c75 Update _list.html.erb 2016-08-01 20:28:18 -04:00
38865bd062 Update vehicle.rb 2016-08-01 19:34:28 -04:00
e201765f02 Update vehicles_controller.rb 2016-08-01 19:31:51 -04:00
d9ccffe3d6 Update vehicles_controller.rb 2016-08-01 19:30:25 -04:00
86e084574e Update _list.html.erb 2016-08-01 19:28:22 -04:00
756e60b865 Update index.html.erb 2016-08-01 19:09:41 -04:00
5c49094b40 Update index.html.erb 2016-08-01 19:08:49 -04:00
336d1c7c7b Update vehicles_controller.rb 2016-08-01 19:05:49 -04:00
632b788082 Update vehicle.rb 2016-08-01 19:04:46 -04:00
ddd00a3e9a Update show.html.erb 2016-08-01 19:02:49 -04:00
54e59fbd98 Update show.html.erb 2016-08-01 17:00:54 -04:00
3f29a024f9 Update show.html.erb 2016-08-01 17:00:25 -04:00
c5f03ed03c Update _details.html.erb 2016-08-01 16:59:35 -04:00
547880443c Update _details.html.erb 2016-08-01 16:58:51 -04:00
838733fdc3 Update README.md 2016-08-01 16:55:56 -04:00
4b068266a9 Add files via upload 2016-08-01 16:48:46 -04:00
57c78f27a7 Delete plugin_config.png 2016-08-01 16:48:31 -04:00
3af5caef4a Update index.html.erb 2016-08-01 16:41:59 -04:00
49425656ee Update index.html.erb 2016-08-01 16:40:50 -04:00
3e85216e66 Update qbo.rb 2016-08-01 16:39:58 -04:00
443d6fc47c Update customer.rb 2016-08-01 16:13:58 -04:00
4d7bc59bd3 Update customer.rb 2016-08-01 16:12:08 -04:00
4dbeee0aa1 Update index.html.erb 2016-08-01 16:09:30 -04:00
9b137fed69 Update index.html.erb 2016-08-01 16:02:05 -04:00
9c667c20da Update index.html.erb 2016-08-01 16:01:24 -04:00
e503c965c3 Update index.html.erb 2016-08-01 15:59:51 -04:00
933d1eb730 Update qbo_controller.rb 2016-08-01 15:54:40 -04:00
c99fe57074 Update qbo_controller.rb 2016-08-01 15:52:02 -04:00
77fc54dc31 Update customer.rb 2016-08-01 15:48:28 -04:00
37f6518a15 Update customer.rb 2016-08-01 15:47:10 -04:00
bcaf011166 Update customers_controller.rb 2016-08-01 15:46:37 -04:00
27807e963d Update customers_controller.rb 2016-08-01 15:45:57 -04:00
f0fabc5e10 Update customers_controller.rb 2016-08-01 15:45:10 -04:00
e7c85eac4d Update index.html.erb 2016-08-01 15:43:45 -04:00
01ea01fef6 Update index.html.erb 2016-08-01 15:42:42 -04:00
134fb776f9 Update index.html.erb 2016-08-01 15:40:17 -04:00
9cd143c5ef Update index.html.erb 2016-08-01 15:39:04 -04:00
ba18275ef8 Update index.html.erb 2016-08-01 15:37:32 -04:00
6e92648d8b Update customers_controller.rb 2016-08-01 15:36:10 -04:00
8ddc612bba Extended search 2016-08-01 15:34:17 -04:00
2324aadcd5 Extended search 2016-08-01 15:31:54 -04:00
baff3f5a1b Extended search 2016-08-01 15:30:00 -04:00
68b6ea7649 Update _list.html.erb 2016-07-28 23:33:15 -04:00
c46cab6a6f Update customers_controller.rb 2016-07-28 23:30:49 -04:00
74807c73b0 Jump to customer is there is only one result 2016-07-28 23:27:02 -04:00
a26214fef7 Search 2016-07-28 20:50:23 -04:00
ec77a004a2 Search 2016-07-28 20:49:38 -04:00
f33203b0e3 Search 2016-07-28 20:48:34 -04:00
297dd8ec4e Search 2016-07-28 20:46:49 -04:00
bbc2ae4750 Search 2016-07-28 20:45:29 -04:00
fb1a560751 Search 2016-07-28 20:44:15 -04:00
bee48c4f0f Version Bump 0.0.7! 2016-07-28 20:37:13 -04:00
0f8fbfb8df Update qbo.rb 2016-07-28 20:34:31 -04:00
d71e9a78a1 Update qbo_controller.rb 2016-07-28 20:06:06 -04:00
7ac778586c Update qbo.rb 2016-07-28 20:03:36 -04:00
2558cb69b1 Update qbo_controller.rb 2016-07-28 19:52:42 -04:00
55ac8d12a5 Update qbo_controller.rb 2016-07-28 19:51:48 -04:00
a5dc5ce921 Update qbo.rb 2016-07-28 19:42:09 -04:00
de65cc0926 Update qbo.rb 2016-07-28 19:41:02 -04:00
80d3eed224 Update qbo_controller.rb 2016-07-28 19:32:24 -04:00
76beccfb9f Update qbo_controller.rb 2016-07-28 19:31:24 -04:00
5579cd9255 Update _settings.html.erb 2016-07-28 19:27:00 -04:00
236e84f11a Update _settings.html.erb 2016-07-28 19:23:20 -04:00
ed61dc6bbf Update qbo_controller.rb 2016-07-28 19:21:40 -04:00
2b7ac05338 Update qbo.rb 2016-07-28 19:19:13 -04:00
36e63995aa Update qbo_controller.rb 2016-07-28 19:15:19 -04:00
58d16fbc7d Update qbo_controller.rb 2016-07-28 19:12:36 -04:00
aa78482c36 Update qbo_controller.rb 2016-07-28 19:11:09 -04:00
c35b6a3f6b Update qbo_controller.rb 2016-07-28 18:35:44 -04:00
8d52c46a53 Update qbo_controller.rb 2016-07-28 18:31:54 -04:00
325f124e4e Update qbo_controller.rb 2016-07-28 18:30:48 -04:00
18d71a69f8 Update qbo_controller.rb 2016-07-28 18:27:00 -04:00
7d5fd72297 Update qbo_controller.rb 2016-07-28 18:21:58 -04:00
a625f6d9fc Update qbo_controller.rb 2016-07-28 18:17:44 -04:00
ede89cc6cf Update qbo_controller.rb 2016-07-28 18:15:56 -04:00
c60f06e8ed Webhook 2016-07-28 09:18:50 -04:00
863a5efa38 Webhook 2016-07-28 09:17:21 -04:00
670b0aac67 Webhook 2016-07-28 09:16:13 -04:00
d261b156bd Webhook 2016-07-28 09:14:18 -04:00
c49bdb731a Webhook 2016-07-28 09:13:16 -04:00
dc2993bdea Webhook 2016-07-28 09:09:38 -04:00
09e1c0ad48 Webhook 2016-07-28 09:07:51 -04:00
370153bed9 Webhook 2016-07-28 09:04:17 -04:00
b115c4bf67 Webhook 2016-07-28 09:03:55 -04:00
90a7ac1267 Webhook 2016-07-28 09:02:29 -04:00
887d330ba9 Merge branch 'master' of github.com:rickbarrette/redmine_qbo 2016-07-28 09:02:04 -04:00
fe97a589d9 Update 2016-07-28 09:01:41 -04:00
37d0b2321f Webhook 2016-07-28 08:59:43 -04:00
47aa454895 Webhook 2016-07-28 08:57:28 -04:00
fecc4956b4 Webhook 2016-07-28 08:52:30 -04:00
0d5fb8d3e3 Update qbo_controller.rb 2016-07-28 08:47:39 -04:00
ca6e51911b Webhook 2016-07-28 08:41:23 -04:00
8159487631 Webhook 2016-07-28 08:33:26 -04:00
392b27563a Webhooks 2016-07-28 07:55:20 -04:00
60dced41db Webhook 2016-07-28 01:07:54 -04:00
8b02a80904 Webhooks 2016-07-28 01:04:00 -04:00
ff977cc364 Update pdf_patch.rb 2016-07-22 19:01:43 -04:00
3578908832 Update pdf_patch.rb 2016-07-22 19:00:24 -04:00
4fcde967f1 Update issues_show_hook_listener.rb 2016-07-22 19:00:03 -04:00
4e81c16617 Update pdf_patch.rb 2016-07-22 16:02:57 -04:00
8149f5ab9b Update issues_show_hook_listener.rb 2016-07-22 15:59:49 -04:00
7800e52299 Update users_show_hook_listener.rb 2016-07-13 16:33:04 -04:00
c6f8fd7561 Update issues_form_hook_listener.rb 2016-06-04 10:09:27 -04:00
e3d26cea23 Update issue_patch.rb 2016-06-02 09:47:42 -04:00
4fd35d4cb6 Update issues_form_hook_listener.rb 2016-06-02 09:45:54 -04:00
cf00331497 Update issues_form_hook_listener.rb 2016-06-02 09:45:28 -04:00
dc8ea82b61 Update issues_form_hook_listener.rb 2016-06-02 09:41:54 -04:00
aab99e3abe Update issues_form_hook_listener.rb 2016-06-02 09:41:14 -04:00
f240a5a6a4 Update issues_form_hook_listener.rb 2016-06-02 09:40:13 -04:00
05ce348d8a Update issues_form_hook_listener.rb 2016-06-02 09:36:34 -04:00
67afbff93d Update issues_form_hook_listener.rb 2016-06-02 09:35:41 -04:00
bd92ca8f2c Fixed Vehicle Drop down 2016-06-02 09:25:52 -04:00
7ef3e31465 New Issue Button 2016-06-02 09:20:44 -04:00
f59aa18be8 Fixed Formatting 2016-06-02 09:02:06 -04:00
2699b37e4f Edmunds API Key 2016-06-02 08:56:13 -04:00
22f8138422 Edmunds API Key Setting 2016-06-02 08:54:37 -04:00
0727257d72 Update issues_save_hook_listener.rb 2016-06-01 12:01:51 -04:00
f8a9ffbe15 Setter for email 2016-05-31 11:16:41 -04:00
28 changed files with 328 additions and 125 deletions

View File

@@ -20,6 +20,8 @@ The goal of this project is to allow redmine to connect with Quickbooks Online t
* Sign up to become a developer for Intuit https://developer.intuit.com/
* Create your own aplication to obtain your API keys
* Set up webhook service to https://redmine.yourdomain.com/qbo/webhook
- See https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/webhooks
##The Install
@@ -47,9 +49,7 @@ The goal of this project is to allow redmine to connect with Quickbooks Online t
![Alt plugin_issue-edit](/Screenshots/plugin_issue_edit.png)
Note: Customers, Employees, and Service Items with automaticly update during normal usage of redmine i.e. a page refresh. You can also manualy force redmine to sync its database with QBO clicking the sync link in the Quickbooks top menu page
![Alt plugin_top_menu](/Screenshots/plugin_top_menu.png)
Note: After the inital synchronization, this plugin will recieve push notifications via Intuit's webhook service.
## TODO
* Abiltiy to add line items to a ticket in a dynamic table so they can be added to the invoice upon closing of the issue

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -16,9 +16,16 @@ class CustomersController < ApplicationController
before_filter :require_user
default_search_scope :names
# display a list of all customers
def index
@customers = Customer.paginate(:page => params[:page])
if params[:search]
@customers = Customer.search(params[:search]).paginate(:page => params[:page])
if only_one_non_zero?(@customers)
redirect_to @customers.first
end
end
end
def new
@@ -81,5 +88,18 @@ class CustomersController < ApplicationController
render_404
end
end
private
def only_one_non_zero?( array )
found_non_zero = false
array.each do |val|
if val!=0
return false if found_non_zero
found_non_zero = true
end
end
found_non_zero
end
end

View File

@@ -11,9 +11,12 @@
class QboController < ApplicationController
unloadable
require 'openssl'
include AuthHelper
before_filter :require_user
before_filter :require_user, :except => :qbo_webhook
skip_before_filter :verify_authenticity_token, :check_if_login_required
#
# Called when the QBO Top Menu us shown
@@ -31,9 +34,9 @@ class QboController < ApplicationController
# Called when the user requests that Redmine to connect to QBO
#
def authenticate
callback = request.base_url + qbo_oauth_callback_path
callback = qbo_oauth_callback_url
token = Qbo.get_oauth_consumer.get_request_token(:oauth_callback => callback)
session[:qb_request_token] = token
session[:qb_request_token] = Marshal.dump(token)
redirect_to("https://appcenter.intuit.com/Connect/Begin?oauth_token=#{token.token}") and return
end
@@ -41,7 +44,7 @@ class QboController < ApplicationController
# Called by QBO after authentication has been processed
#
def oauth_callback
at = session[:qb_request_token].get_access_token(:oauth_verifier => params[:oauth_verifier])
at = Marshal.load(session[:qb_request_token]).get_access_token(:oauth_verifier => params[:oauth_verifier])
#There can only be one...
Qbo.destroy_all
@@ -54,11 +57,61 @@ class QboController < ApplicationController
qbo.reconnect_token_at = 5.months.from_now.utc
qbo.company_id = params['realmId']
if qbo.save!
redirect_to qbo_sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
redirect_to qbo_path, :flash => { :notice => "Successfully connected to Quickbooks" }
else
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
end
end
# Quickbooks Webhook Callback
def qbo_webhook
# check the payload
signature = request.headers['intuit-signature']
key = Setting.plugin_redmine_qbo['settingsWebhookToken']
data = request.body.read
hash = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), key, data)).strip()
# proceed if the request is good
if hash.eql? signature
if request.headers['content-type'] == 'application/json'
data = JSON.parse(data)
else
# application/x-www-form-urlencoded
data = params.as_json
end
# Process the information
entities = data['eventNotifications'][0]['dataChangeEvent']['entities']
entities.each do |entity|
id = entity['id'].to_i
name = entity['name']
# TODO rename all other models!
name.prepend("Qbo") if not name.eql? "Customer"
# Magicly initialize the correct class
obj = name.constantize
# for merge events
obj.destroy(entity['deletedId']) if entity['deletedId']
#Check to see if we are deleting a record
if entity['operation'].eql? "Delete"
obj.destroy(id)
#if not then update!
else
obj.sync_by_id(id)
end
end
# Record that last time we updated
Qbo.update_time_stamp
# The webhook doesn't require a response but let's make sure we don't send anything
render :nothing => true
else
render nothing: true, status: 400
end
end
#
@@ -71,7 +124,6 @@ class QboController < ApplicationController
QboEmployee.sync
QboEstimate.sync
QboInvoice.sync
#QboPurchase.sync
# Record the last sync time
Qbo.update_time_stamp

View File

@@ -18,7 +18,12 @@ class VehiclesController < ApplicationController
# display a list of all vehicles
def index
@vehicles = Vehicle.paginate(:page => params[:page])
if params[:search]
@vehicles = Vehicle.search(params[:search]).paginate(:page => params[:page])
if only_one_non_zero?(@vehicles)
redirect_to @vehicles.first
end
end
end
# return an HTML form for creating a new vehicle
@@ -86,5 +91,18 @@ class VehiclesController < ApplicationController
render_404
end
end
private
def only_one_non_zero?( array )
found_non_zero = false
array.each do |val|
if val!=0
return false if found_non_zero
found_non_zero = true
end
end
found_non_zero
end
end

View File

@@ -36,6 +36,13 @@ class Customer < ActiveRecord::Base
end
end
# Convenience Method
# Sets the email
def email=(s)
pull unless @details
@details.email_address = s
end
# Convenience Method
# returns the customer's primary phone
def primary_phone
@@ -108,14 +115,14 @@ class Customer < ActiveRecord::Base
service = Qbo.get_base(:customer).service
# Sync ALL customers if the database is empty
if count == 0
#if count == 0
customers = service.all
else
last = Qbo.first.last_sync
query = "Select Id, DisplayName From Customer"
query << " Where Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
customers = service.query(query)
end
#else
# last = Qbo.first.last_sync
# query = "Select Id, DisplayName From Customer"
# query << " Where Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
# customers = service.query(query)
#end
customers.each do |customer|
qbo_customer = Customer.find_or_create_by(id: customer.id)
@@ -133,6 +140,31 @@ class Customer < ActiveRecord::Base
end
end
# Searchs the database for a customer by name
def self.search(search)
where("name LIKE ?", "%#{search}%").order(:name)
end
# proforms a bruteforce sync operation
# This needs to be simplified
def self.sync_by_id(id)
service = Qbo.get_base(:customer).service
customer = service.fetch_by_id(id)
qbo_customer = Customer.find_or_create_by(id: customer.id)
if customer.active?
if not qbo_customer.name.eql? customer.display_name
qbo_customer.name = customer.display_name
qbo_customer.id = customer.id
qbo_customer.save_without_push
end
else
if not qbo_customer.new_record?
qbo_customer.delete
end
end
end
# Push the updates
def save_with_push
begin

View File

@@ -11,18 +11,17 @@
class Qbo < ActiveRecord::Base
unloadable
validates_presence_of :qb_token, :qb_secret, :company_id, :token_expires_at, :reconnect_token_at
QB_KEY = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
QB_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
# Quickbooks Config Info
$qb_oauth_consumer = OAuth::Consumer.new(QB_KEY, QB_SECRET, {
:site => "https://oauth.intuit.com",
:request_token_path => "/oauth/v1/get_request_token",
:authorize_url => "https://appcenter.intuit.com/Connect/Begin",
:access_token_path => "/oauth/v1/get_access_token"
})
OAUTH_CONSUMER_KEY = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
OAUTH_CONSUMER_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
$qb_oauth_consumer = OAuth::Consumer.new(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET, {
:site => "https://oauth.intuit.com",
:request_token_path => "/oauth/v1/get_request_token",
:authorize_url => "https://appcenter.intuit.com/Connect/Begin",
:access_token_path => "/oauth/v1/get_access_token"
})
# Configure quickbooks-ruby-base to access our database
Quickbooks::Base.configure do |c|
c.persistent_token = 'qb_token'
@@ -31,6 +30,7 @@ class Qbo < ActiveRecord::Base
end
def self.get_oauth_consumer
# Quickbooks Config Info
return $qb_oauth_consumer
end
@@ -51,4 +51,8 @@ class Qbo < ActiveRecord::Base
qbo.last_sync = DateTime.now
qbo.save
end
def self.last_sync
format_time(Qbo.first.last_sync)
end
end

View File

@@ -30,8 +30,13 @@ class QboEmployee < ActiveRecord::Base
qbo_employee.save!
}
end
#remove deleted employees
where.not(employees.map(&:id)).destroy_all
end
def self.sync_by_id(id)
employee = get_base.service.fetch_by_id(id)
qbo_employee = find_or_create_by(id: employee.id)
qbo_employee.name = employee.display_name
qbo_employee.id = employee.id
qbo_employee.save!
end
end

View File

@@ -35,6 +35,14 @@ class QboEstimate < ActiveRecord::Base
where.not(estimates.map(&:id)).destroy_all
end
def self.sync_by_id(id)
estimate = get_base.service.fetch_by_id(id)
qbo_estimate = QboEstimate.find_or_create_by(id: estimate.id)
qbo_estimate.doc_number = estimate.doc_number
qbo_estimate.id = estimate.id
qbo_estimate.save!
end
def self.update(id)
# Update the item table
estimate = get_base.service.fetch_by_id(id)

View File

@@ -45,6 +45,14 @@ class QboInvoice < ActiveRecord::Base
#where.not(invoices.map(&:id)).destroy_all
end
def self.sync_by_id(id)
invoice = get_base.service.fetch_by_id(id)
qbo_invoice = find_or_create_by(id: invoice.id)
qbo_invoice.doc_number = invoice.doc_number
qbo_invoice.id = invoice.id
qbo_invoice.save!
end
def self.update(id)
# Update the item table
invoice = get_base.service.fetch_by_id(id)

View File

@@ -12,6 +12,8 @@ class Vehicle < ActiveRecord::Base
unloadable
API_KEY = Setting.plugin_redmine_qbo['settingsEdmundsAPIKey']
belongs_to :customer
has_many :issues, :foreign_key => 'vehicles_id'
@@ -19,7 +21,7 @@ class Vehicle < ActiveRecord::Base
validates_presence_of :customer
validates :vin, uniqueness: true
validates :year, numericality: { only_integer: true }
#validates :year, numericality: { only_integer: true }
before_save :decode_vin
after_initialize :get_details
@@ -73,6 +75,11 @@ class Vehicle < ActiveRecord::Base
write_attribute(:vin, val.to_s.upcase)
end
# search for a vin
def self.search(search)
where("vin LIKE ?", "%#{search}%")
end
private
# init method to pull JSON details from Edmunds
@@ -91,7 +98,7 @@ class Vehicle < ActiveRecord::Base
# returns the Edmunds decoder service
def get_decoder
#TODO API Code via Settings
return decoder = Edmunds::Vin.new('2dheutzvhxs28dzukx5tgu47')
return decoder = Edmunds::Vin.new(API_KEY)
end
# decodes a vin and updates self
@@ -119,4 +126,5 @@ class Vehicle < ActiveRecord::Base
v = self.vin[0,11]
return v.slice(0,8) + v.slice(9,11)
end
end

View File

@@ -1,19 +1,30 @@
<h1>Customers</h1>
<br/>
<%= form_tag(customers_path, :method => "get", id: "search-form") do %>
<%= text_field_tag :search, params[:search], placeholder: "Search Customers" %>
<%= submit_tag "Search" %>
<%= button_to "New Customer", new_customer_path, method: :get %>
<% end %>
<br/>
<% if @customers.present? %>
<br/>
<% @customers.each do |c| %>
<div class="row">
<div class="span6 columns">
<fieldset>
<%= c.name %>
<%= button_to "Show", customer_path(c.id), method: :get %>
</fieldset>
<%= link_to c, customer_path(c.id) %>
</div>
</div>
<% end %>
<br/>
<div class="actions">
<%= will_paginate @customers %>
<%= button_to "New", new_customer_path, method: :get %>
</div>
<% else %>
<p>There are no customers containing the term(s) <%= params[:search] %>.</p>
<% end %>
<div>
<b>Last Sync: </b> <%= Qbo.last_sync %>
</div>

View File

@@ -1,4 +1,4 @@
<h1>Customer Detail</h1>
<h1>Customer #<%= @customer.id %></h1>
<br/>
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
<br/>

View File

@@ -10,10 +10,27 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<!-- somewhere in your document include the Javascript -->
<script type="text/javascript" src="https://appcenter.intuit.com/Content/IA/intuit.ipp.anywhere.js"></script>
<!-- configure the Intuit object: 'grantUrl' is a URL in your application which kicks off the flow, see below -->
<script>
intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '<%= qbo_authenticate_url %>'});
</script>
<table >
<tbody>
<tr>
<th>OAuth Consumer Key</th>
<th>Edmunds API Key</th>
<td>
<input type="text" style="width:350px" id="settingsEdmundsAPIKey"
value="<%= settings['settingsEdmundsAPIKey'] %>"
name="settings[settingsEdmundsAPIKey]" >
</td>
</tr>
<tr>
<th>Intuit QBO OAuth Consumer Key</th>
<td>
<input type="text" style="width:350px" id="settingsOAuthConsumerKey"
value="<%= settings['settingsOAuthConsumerKey'] %>"
@@ -22,21 +39,22 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
</tr>
<tr>
<th>OAuth Consumer Secret</th>
<th>Intuit QBO OAuth Consumer Secret</th>
<td>
<input type="text" style="width:350px" id="settingsOAuthConsumerSecret"
value="<%= settings['settingsOAuthConsumerSecret'] %>"
name="settings[settingsOAuthConsumerSecret]" >
</td>
</tr>
</tbody>
</table>
<br/>
<table>
<tbody>
<tr>
<th>Intuit QBO Webhook Token</th>
<td>
<input type="text" style="width:350px" id="settingsWebhookToken"
value="<%= settings['settingsWebhookToken'] %>"
name="settings[settingsWebhookToken]" >
</td>
</tr>
<tr>
<th>Token Expires At</th>
@@ -54,5 +72,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
<br/>
Note: You need to authenticate after saving your key and secret above
<br/>
<%= link_to "Authenticate", qbo_authenticate_path, :method => :get %>
<!-- this will display a button that the user clicks to start the flow -->
<ipp:connectToIntuit></ipp:connectToIntuit>

View File

@@ -12,44 +12,31 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
<body>
<h1> Redmine Quickbooks</h1>
<%= form_for @qbo do |f|%>
<div>
<%= f.label "Customer Count:"+@customer_count.to_s%>
<br/>
<%= f.select :customer_id, Customer.all.pluck(:name, :id).sort, :selected => @selected_customer, include_blank: true %>
</div>
<br/>
<div>
<%= f.label "Item Count: "+@qbo_item_count.to_s %>
<br/>
<%= f.select :qbo_item_id, QboItem.all.pluck(:name, :id).sort.reverse, :selected => @selected_item, include_blank: true %>
</div>
<br/>
<div>
<%= f.label "Employee Count: "+@qbo_employee_count.to_s %>
<br/>
<%= f.select :qbo_employee_id, QboEmployee.all.pluck(:name, :id).sort, :selected => @selected_employee, include_blank: true %>
</div>
<p>
<%= f.label "Invoice Count: "+@qbo_invoice_count.to_s %>
<br/>
<%=f.select :qbo_invoice_id, QboInvoice.all.pluck(:doc_number, :id).sort! {|x, y| y <=> x}, :selected => @selected_invoice, include_blank: true%>
</p>
<p>
<%= f.label "Estimate Count: "+@qbo_estimate_count.to_s %>
<br/>
<%=f.select :qbo_estimate_id, QboEstimate.all.pluck(:doc_number, :id).sort! {|x, y| y <=> x}, :selected => @selected_estimate, include_blank: true%>
</p>
<div>
<b>Customer Count:</b> <%= @customer_count.to_s%>
</div>
<div>
<b>Item Count:</b> <%= @qbo_item_count.to_s %>
</div>
<div>
<b>Employee Count:</b> <%= @qbo_employee_count.to_s %>
</div>
<div>
<b>Invoice Count:</b> <%= @qbo_invoice_count.to_s %>
</div>
<div>
<b>Estimate Count:</b> <%= @qbo_estimate_count.to_s %>
</div>
<% end %>
<br/>
<br/>
<%= link_to "Sync", qbo_sync_path %>
<div>
<b>Last Sync: </b> <%= Qbo.last_sync %>
</div>
</body>

View File

@@ -44,6 +44,8 @@
<tr>
<td/>
<td>
<%= button_to "New Issue", new_issue_path(:vehicle_id => vehicle.id, :customer_id => vehicle.customer.id), method: :get%>
<%= button_to "Edit", edit_vehicle_path(vehicle), method: :get%>
<%= button_to "Delete", vehicle, method: :delete, data: {confirm: "You sure?"} %>
</td>

View File

@@ -1,20 +1,28 @@
<% @vehicles.each do |vehicle| %>
<div class="row">
<div class="span6 columns">
<fieldset>
<% if @vehicles.present? %>
<% @vehicles.each do |vehicle| %>
<div class="row">
<div>
<b><%= link_to "##{vehicle.id}", vehicle_path(vehicle) %> </b>
</div>
<div>
<%= vehicle.to_s %>
<br/>
<div style="float: right;" >
<%= button_to "More", vehicle_path(vehicle), method: :get %>
</div>
</fieldset>
<%= vehicle.customer %>
<br/>
<%= vehicle.vin %>
</div>
</div>
<br/>
<% end %>
<br/>
<div class="actions">
<%= will_paginate @vehicles %>
</div>
<% else %>
<p>There are no vehicles containing the term(s) <%= params[:search] %>.</p>
<% end %>
<br/>
<div class="actions">
<%= will_paginate @vehicles %>
<%= button_to "New Vehicle", new_vehicle_path, method: :get %>
</div>

View File

@@ -1,3 +1,10 @@
<h1>Customer Vehicles</h1>
<br/>
<%= form_tag(vehicles_path, :method => "get", id: "search-form") do %>
<%= text_field_tag :search, params[:search], placeholder: "Search Vehicles by VIN" %>
<%= submit_tag "Search" %>
<%= button_to "New Vehicle", new_vehicle_path, method: :get %>
<% end %>
<%= render :partial => 'vehicles/list' %>

View File

@@ -1,4 +1,4 @@
<h1>Customer Vehicle</h1>
<h1>Vehicle #<%=@vehicle.id%> </h1>
<br/>
<div style="text-align: left; width:90%;">

View File

@@ -17,5 +17,8 @@ get 'qbo/oauth_callback', :to => 'qbo#oauth_callback'
get 'qbo/sync', :to => 'qbo#sync'
get 'qbo/estimate/:id', :to => 'estimate#show', as: :estimate
get 'qbo/invoice/:id', :to => 'invoice#show', as: :invoice
post 'qbo/webhook', :to => 'qbo#qbo_webhook'
resources :vehicles
resources :customers

View File

@@ -25,7 +25,7 @@ Redmine::Plugin.register :redmine_qbo do
name 'Redmine Quickbooks Online plugin'
author 'Rick Barrette'
description 'This is a plugin for Redmine to intergrate with Quickbooks Online to allow for seamless intergration CRM and invoicing of completed issues'
version '0.0.6'
version '0.1.0'
url 'https://github.com/rickbarrette/redmine_qbo'
author_url 'http://rickbarrette.org'
settings :default => {'empty' => true}, :partial => 'qbo/settings'
@@ -49,4 +49,5 @@ Redmine::Plugin.register :redmine_qbo do
menu :top_menu, :qbo, { :controller => :qbo, :action => :index }, :caption => 'Quickbooks', :if => Proc.new { User.current.admin? }
menu :top_menu, :vehicles, { :controller => :vehicles, :action => :index }, :caption => 'Vehicles', :if => Proc.new { User.current.logged? }
menu :top_menu, :customers, { :controller => :customers, :action => :index }, :caption => 'Customers', :if => Proc.new { User.current.logged? }
end

View File

@@ -26,7 +26,7 @@ module IssuePatch
belongs_to :qbo_item, primary_key: :id
belongs_to :qbo_estimate, primary_key: :id
belongs_to :qbo_invoice, primary_key: :id
belongs_to :vehicle
belongs_to :vehicle, primary_key: :id
end
end

View File

@@ -16,14 +16,15 @@ class IssuesFormHookListener < Redmine::Hook::ViewListener
f = context[:form]
# Check to see if there is a quickbooks user attached to the issue
@selected_customer = context[:issue].customer ? context[:issue].customer.id : nil
selected_customer = context[:issue].customer ? context[:issue].customer.id : nil
selected_item = context[:issue].qbo_item ? context[:issue].qbo_item.id : nil
selected_invoice = context[:issue].qbo_invoice ? context[:issue].qbo_invoice.id : nil
selected_estimate = context[:issue].qbo_estimate ? context[:issue].qbo_estimate.id : nil
selected_vehicle = context[:issue].vehicles_id ? context[:issue].vehicles_id : nil
# Load customer information without callbacks
@customer = Customer.find_by_id(@selected_customer) if @selected_customer
@select_customer = f.select :customer_id, Customer.all.pluck(:name, :id).sort, :selected => @selected_customer, include_blank: true
customer = Customer.find_by_id(selected_customer) if selected_customer
select_customer = f.select :customer_id, Customer.all.pluck(:name, :id).sort, :selected => selected_customer, include_blank: true
# Generate the drop down list of quickbooks items
select_item = f.select :qbo_item_id, QboItem.all.pluck(:name, :id).sort, :selected => selected_item, include_blank: true
@@ -34,10 +35,14 @@ class IssuesFormHookListener < Redmine::Hook::ViewListener
# Generate the drop down list of quickbooks extimates
select_estimate = f.select :qbo_estimate_id, QboEstimate.all.pluck(:doc_number, :id).sort! {|x, y| y <=> x}, :selected => selected_estimate, include_blank: true
vehicles = @customer.vehicles.pluck(:name, :id).sort! if context[:issue].customer
vehicles = Vehicle.all.order(:name) if not vehicles
vehicle = f.select :vehicles_id, vehicles, include_blank: true, :selected => vehicle
if context[:issue].customer
vehicles = customer.vehicles.pluck(:name, :id).sort!
else
vehicles = Vehicle.all.order(:name).pluck(:name, :id)
end
return "<p>#{@select_customer}</p> <p>#{select_item}</p> <p>#{select_invoice}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
vehicle = f.select :vehicles_id, vehicles, :selected => selected_vehicle, include_blank: true
return "<p>#{select_customer}</p> <p>#{select_item}</p> <p>#{select_invoice}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
end
end

View File

@@ -55,11 +55,14 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
# Called After Issue Saved
def controller_issues_edit_after_save(context={})
issue = context[:issue]
employee_id = issue.assigned_to.qbo_employee_id
if issue.assigned_to
employee_id = issue.assigned_to.qbo_employee_id
# Check to see if we have registered with QBO and if the issue is closed.
# If so then we need to create a new billable time activity for the customer
bill_time(issue, employee_id) if Qbo.first && issue.customer && issue.qbo_item && employee_id && issue.status.is_closed?
# Check to see if we have registered with QBO and if the issue is closed.
# If so then we need to create a new billable time activity for the customer
bill_time(issue, employee_id) if Qbo.first && issue.customer && issue.qbo_item && employee_id && issue.status.is_closed?
end
end
# Create billable time entries

View File

@@ -50,6 +50,8 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
#do nothing
end
split_vin = vin.scan(/.{1,9}/) if vin
return "
<div class=\"attributes\">
@@ -82,7 +84,7 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
<div class=\"vehicle_vin attribute\">
<div class=\"label\"><span>VIN</span>:</div>
<div class=\"value\">#{vin}</div>
<div class=\"value\">#{split_vin[0] if split_vin}<b>#{split_vin[1] if split_vin}</b></div>
</div>
<div class=\"vehicle_notes attribute\">

View File

@@ -51,7 +51,7 @@ module IssuesPdfHelperPatch
vin = v ? v.vin : nil
notes = v ? v.notes : nil
left << [l(:field_vehicles), vehicle]
left << [l(:field_vin), vin]
left << [l(:field_vin), vin.gsub(/(.{9})/, '\1 ')]
#left << [l(:field_notes), notes]
right = []

View File

@@ -14,7 +14,7 @@ class UsersShowHookListener < Redmine::Hook::ViewListener
def view_users_form(context={})
# Update the users
QboEmployee.update_all
#QboEmployee.update_all
# Check to see if there is a quickbooks user attached to the issue
@selected = context[:user].qbo_employee.id if context[:user].qbo_employee