mirror of
https://github.com/rickbarrette/redmine_qbo.git
synced 2025-11-08 08:54:23 -05:00
Compare commits
364 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aac442ab55 | |||
| b7f0bf6049 | |||
| b7b9177edf | |||
| f5d7e00ec6 | |||
| 4f8c57032c | |||
| 96fcb81d9e | |||
| 1b5663fa99 | |||
| a2588040ff | |||
| 947f56899d | |||
| 731e709174 | |||
| c3cce531d6 | |||
| ca63852b4f | |||
| 9342ef146c | |||
| 00b6939571 | |||
| 0376233ace | |||
| 70533ae3c3 | |||
| d7613bd5ba | |||
| 06bd0dfcce | |||
| 78708733be | |||
| e7ae654066 | |||
| 5c0c0cc657 | |||
| 9d043e4bc8 | |||
| 08b539dc1e | |||
| e5147b3893 | |||
| 01a2b2a1b1 | |||
| e89f2d17e7 | |||
| 7e0888d375 | |||
| f435f7f3c8 | |||
| 0d2be843f5 | |||
| 6e7ec44188 | |||
| ba152e69d2 | |||
| 42212a073c | |||
| 469f8c739c | |||
| 53505857d6 | |||
| 89a02d85fe | |||
| 6249b057af | |||
| f760ee057d | |||
| 291bff0813 | |||
| 79edbed9ce | |||
| 54c797c114 | |||
| 201a577588 | |||
| 4dfbdbfdda | |||
| 3e5008aeb2 | |||
| bfb6d3bc4e | |||
| d829c2f83a | |||
| ec1063f80f | |||
| 3b20c29d0a | |||
| f2dc4b03ef | |||
| 22e4aba1cf | |||
| ece22c73ae | |||
| ed7e5d04ec | |||
| 6a595ab30c | |||
| 821606ee5e | |||
| 61440a2aca | |||
| 01eefe0a04 | |||
| 9057ef3edb | |||
| 4f333c9b94 | |||
| b64d91108e | |||
| fe063a029e | |||
| b6504333c0 | |||
| d0c3fefa77 | |||
| a81e6b9b66 | |||
| a760ddc7c1 | |||
| 3e0b327ea7 | |||
| c5296e7c5f | |||
| fec454f43e | |||
| bd18574bba | |||
| c22272143d | |||
| c3e8fb22c1 | |||
| 75defce2dc | |||
| 327d59d380 | |||
| 10ddd6731e | |||
| 181da3fef1 | |||
| fbcc3eba9d | |||
| d265d765d1 | |||
| 948c5d0146 | |||
| d0adb4ed4e | |||
| 86cf0b2edd | |||
| 4ac76a62a2 | |||
| 2995060ec6 | |||
| 6e092922ac | |||
| 07b72c9cb1 | |||
| a4904c80a0 | |||
| 181b928d19 | |||
| fd8ca2d44d | |||
| c72d375aea | |||
| 68e3a7cc7c | |||
| 69d047e687 | |||
| 12166839b2 | |||
| dfb59025f6 | |||
| 684e7a45aa | |||
| 7503c68820 | |||
| dcfe88b447 | |||
| b5eb3e2568 | |||
| 8cf39301bd | |||
| 0bb97a4e37 | |||
| 1fe10d53dd | |||
| c6b234bde1 | |||
| 68169de61d | |||
| e82d09c027 | |||
| 65a89407ff | |||
| 76778ece82 | |||
| dfbf5ab1af | |||
| b771199e3d | |||
| a715434f42 | |||
| 7009387a00 | |||
| 5117e7a479 | |||
| cae904b5a8 | |||
| 9876ebf6ed | |||
| c665f5e27b | |||
| 77e5022708 | |||
| a8a97ce748 | |||
| bb9d9e5650 | |||
| c5a461f12a | |||
| 4c659f9aca | |||
| 7bd74df165 | |||
| 5d04a8b246 | |||
| 4c847a5435 | |||
| dd79571272 | |||
| b931e2967f | |||
| 4ec8ceca1b | |||
| 798aea6a8b | |||
| e3fcc8c9be | |||
| 30255f1db8 | |||
| 24f4e9ecf2 | |||
| 454613c1ba | |||
| eeccece2a7 | |||
| c9c49c9425 | |||
| d345365e0b | |||
| 897f56eb23 | |||
| 4db96a1792 | |||
| ae572557a1 | |||
| 6605bd9467 | |||
| 3f352f1f19 | |||
| 939cd63d41 | |||
| 42c5eb797e | |||
| 16bb039917 | |||
| 2db29e1eec | |||
| 843c355a64 | |||
| 8b7cc3ffb6 | |||
| a1135115bc | |||
| 7c37cda14e | |||
| b1614941af | |||
| f19daef1d7 | |||
| 9d5d6d6c26 | |||
| 72d9bbebd0 | |||
| fc6ab6bb4e | |||
| 1360424d34 | |||
| 9838b831ec | |||
| 402f6f1097 | |||
| a64dc10471 | |||
| a9dcb183fd | |||
| 1259abc6c7 | |||
| 77438191fe | |||
| 0c2b6a8134 | |||
| b2c3e31671 | |||
| 8e62740d4d | |||
| fc93862528 | |||
| a68c7d5803 | |||
| 263030fd93 | |||
| 12a6bb575c | |||
| 0c2a5c0297 | |||
| 64b38aedea | |||
| ad1bd78afe | |||
| 74c3535335 | |||
| d8798547b1 | |||
| d48df86a49 | |||
| cd4e21daf4 | |||
| eed8daf87c | |||
| b4c79b08a2 | |||
| cec63b48ef | |||
| 2ebeef3f42 | |||
| bac9778203 | |||
| 0c4b8a24d9 | |||
| 476a194410 | |||
| 10ec2a1b1b | |||
| 936a48f205 | |||
| aba1cdf6d9 | |||
| c80e90b2d4 | |||
| b2e1d649ff | |||
| eaf2d9785c | |||
| 3925337df0 | |||
| b35b9cf478 | |||
| 65577b57dd | |||
| 8120296154 | |||
| 7a1d769581 | |||
| 5c9cd88279 | |||
| ec56b59d57 | |||
| ee088c65f2 | |||
| d7c2ef388f | |||
| 5a9aaa801a | |||
| 3f06e790f0 | |||
| 1d475185fb | |||
| a611afb475 | |||
| b65da8e2d9 | |||
| d9be3323f0 | |||
| f04e8e8ab2 | |||
| d13609ff4a | |||
| 6305dc73ac | |||
| 4e61d363ee | |||
| 9fe2119d5a | |||
| 1500493108 | |||
| 6896c94457 | |||
| 13dfca39d3 | |||
| 7570376f1b | |||
| fc626d4619 | |||
| b77c4eaf37 | |||
| c1ab2848df | |||
| 6318c2f7e0 | |||
| 1d5bf9f1c9 | |||
| 8447de006a | |||
| 82bab537b5 | |||
| c74c40f08d | |||
| e2d8f2f16c | |||
| 7f2a918f9e | |||
| 0b2fbba330 | |||
| 4df82ea8e1 | |||
| 3af5e28c9e | |||
| 319df60b51 | |||
| cf30ff3fe1 | |||
| 767f42c326 | |||
| 51c90f5fef | |||
| b82849938d | |||
| 1adfaa1fdb | |||
| 1542697ca1 | |||
| 9c63ffc08a | |||
| 9e331b83d3 | |||
| e3d4be434b | |||
| f919ae9b00 | |||
| 8fdcf7fb43 | |||
| 3fcb49a56b | |||
| 53629c59c3 | |||
| 7976c033ad | |||
| 0627722806 | |||
| 903ae789d2 | |||
| 8d5154a456 | |||
| b002730a95 | |||
| 20d3d61d1d | |||
| 8e6c5f81a1 | |||
| a70561b5bd | |||
| 5d6ff72ef7 | |||
| 039e9f97c6 | |||
| 3896560184 | |||
| 3d9c7f8ad9 | |||
| d70ce7b156 | |||
| a939804dd5 | |||
| b6ee0bb482 | |||
| a0b0c3762a | |||
| 6134906fdd | |||
| 1af02ec3de | |||
| 757ddf87ac | |||
| 5fb42e8cd8 | |||
| 836653fc38 | |||
| 604dd0830e | |||
| 3427b00e25 | |||
| ecaa0cda6a | |||
| 822dc4b705 | |||
| 259a4fdc58 | |||
| 29ea58db36 | |||
| b217126316 | |||
| 5dd7de79ab | |||
| dd280ddf51 | |||
| 2210b11a57 | |||
| 70cd5a5cdd | |||
| 23d4debc3a | |||
| 1a708a8a56 | |||
| 2bb87b74da | |||
| ccff5a5e8a | |||
| 573502ee77 | |||
| a020231224 | |||
| a1530fb952 | |||
| 98bbad8471 | |||
| 3de06cad11 | |||
| 6f5b903e5d | |||
| 5b6043a5de | |||
| 0d6240c089 | |||
| 56a3b2cffb | |||
| ca2992e334 | |||
| 9def9eb2c3 | |||
| 81b0902378 | |||
| d4a747e3a8 | |||
| a24f9b78b5 | |||
| 5d2acdac08 | |||
| f0b6c237cc | |||
| aea9bf5f40 | |||
| 97c65b703f | |||
| 23b06ced47 | |||
| c5164684ac | |||
| 0304acd2b2 | |||
| 86327723b8 | |||
| 40f4e6e00e | |||
| 49a42d2374 | |||
| 75f5cd2619 | |||
| be95de7066 | |||
| 3189d227fc | |||
| a4a043a2d9 | |||
| 19f1ffa89c | |||
| f51fa5c7d0 | |||
| 813a74d1af | |||
| a55f3e03de | |||
| 89e3e50a64 | |||
| 6e60b11be1 | |||
| c9225e028c | |||
| 16ed209cc9 | |||
| 47f6a0fa79 | |||
| e935f514c1 | |||
| 55913d3b5a | |||
| 781a1bfe61 | |||
| b6f0b35bf0 | |||
| 7d3c21771f | |||
| e26b593702 | |||
| 28324eb6ff | |||
| fea0979030 | |||
| 346fddad27 | |||
| a66adfc6d1 | |||
| 53a35e0fc3 | |||
| 325e8cd71c | |||
| 8c6d5acea1 | |||
| 832b948f80 | |||
| cba21fa1b6 | |||
| 1934ec9cfc | |||
| 783b63c9a0 | |||
| f9a31fd3f2 | |||
| 090fbba6f5 | |||
| 92baf905cd | |||
| 22241a3be4 | |||
| ed27fc559d | |||
| cb07f397e0 | |||
| 916a9bdb70 | |||
| 9750ef2f3b | |||
| ab6851ff2b | |||
| ebf0177dd7 | |||
| a8ce05d8a6 | |||
| 43876fb61d | |||
| 5b87733b54 | |||
| 0497220c68 | |||
| 9d3cbfa2e0 | |||
| 240b5de58b | |||
| 0a98e7cb74 | |||
| 04f64eadc7 | |||
| b5b30b2b03 | |||
| f96f01784a | |||
| fa09a50e84 | |||
| 923d09328b | |||
| 5a7cfbd2a6 | |||
| d2d61b5e87 | |||
| 253c566e90 | |||
| 39d2e172bc | |||
| 1bddea1e8f | |||
| e928ad58dc | |||
| 002e62f723 | |||
| d809e90627 | |||
| 150e18a2d0 | |||
| 4155547fb9 | |||
| 108ed72560 | |||
| 701112311d | |||
| 5f2c6d2c20 | |||
| e70adfd335 | |||
| a9ebc4107f | |||
| 76ffda9cad | |||
| b5d42fcd7b | |||
| 11e215189b | |||
| 1e426a8295 | |||
| 8aa3240c7e |
3
Gemfile
3
Gemfile
@@ -2,9 +2,8 @@ source 'https://rubygems.org'
|
||||
|
||||
gem 'quickbooks-ruby'
|
||||
gem 'quickbooks-ruby-base'
|
||||
gem 'oauth-plugin'#, '~> 0.5.1'
|
||||
gem 'oauth-plugin'
|
||||
gem 'oauth'
|
||||
gem 'roxml'
|
||||
gem 'nokogiri'
|
||||
gem 'edmunds_vin'
|
||||
gem 'will_paginate', '~> 3.1.0'
|
||||
|
||||
@@ -53,9 +53,9 @@ Note: Customers, Employees, and Service Items with automaticly update during nor
|
||||
|
||||
## 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
|
||||
* Customer ~~Creation~~, ~~Update~~, Deletion
|
||||
* Email Customer updates, provding a link that would: bypass the login page, go directly to the issue directing them to, and allow them to view only that issue.
|
||||
* Add a rake file to create required Trackers or statuses required
|
||||
* Add link Invoice PDF to issue after creation.
|
||||
* Clean up view hook code, possibly use a controller hook and reder partial views
|
||||
* Add Setting for Sandbox Mode
|
||||
|
||||
##License
|
||||
|
||||
@@ -18,46 +18,68 @@ class CustomersController < ApplicationController
|
||||
|
||||
# display a list of all customers
|
||||
def index
|
||||
@customers = QboCustomer.paginate(:page => params[:page])
|
||||
@customers = Customer.paginate(:page => params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
|
||||
@customer = Customer.new
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@customer = Customer.new(params[:customer])
|
||||
if @customer.save
|
||||
flash[:notice] = "New Customer Created"
|
||||
redirect_to @customer
|
||||
else
|
||||
flash[:error] = @customer.errors.full_messages.to_sentence
|
||||
redirect_to new_customer_path
|
||||
end
|
||||
end
|
||||
|
||||
# display a specific customer
|
||||
def show
|
||||
@customer = QboCustomer.find_by_id(params[:id])
|
||||
if @customer
|
||||
begin
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
@vehicles = @customer.vehicles.paginate(:page => params[:page])
|
||||
else
|
||||
flash[:error] = "Customer Not Found"
|
||||
render :index
|
||||
@issues = @customer.issues
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# return an HTML form for editing a customer
|
||||
def edit
|
||||
@customer = QboCustomer.find_by_id(params[:id])
|
||||
begin
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# update a specific customer
|
||||
def update
|
||||
@customer = QboCustomer.find_by_id(params[:id])
|
||||
if @customer.update_attributes(params[:customer])
|
||||
flash[:success] = "Customer updated"
|
||||
redirect_to @customer
|
||||
else
|
||||
render :edit
|
||||
begin
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
if @customer.update_attributes(params[:customer])
|
||||
flash[:notice] = "Customer updated"
|
||||
redirect_to @customer
|
||||
else
|
||||
redirect_to edit_customer_path
|
||||
flash[:error] = @customer.errors.full_messages.to_sentence if @customer.errors
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
begin
|
||||
Customer.find_by_id(params[:id]).destroy
|
||||
flash[:notice] = "Customer deleted successfully"
|
||||
redirect_to action: :index
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@ class QboController < ApplicationController
|
||||
#
|
||||
def index
|
||||
@qbo = Qbo.first
|
||||
@qbo_customer_count = QboCustomer.count
|
||||
@customer_count = Customer.count
|
||||
@qbo_item_count = QboItem.count
|
||||
@qbo_employee_count = QboEmployee.count
|
||||
@qbo_invoice_count = QboInvoice.count
|
||||
@@ -42,20 +42,17 @@ class QboController < ApplicationController
|
||||
#
|
||||
def oauth_callback
|
||||
at = session[:qb_request_token].get_access_token(:oauth_verifier => params[:oauth_verifier])
|
||||
token = at.token
|
||||
secret = at.secret
|
||||
realm_id = params['realmId']
|
||||
|
||||
#There can only be one...
|
||||
Qbo.destroy_all
|
||||
|
||||
# Save the authentication information
|
||||
qbo = Qbo.new
|
||||
qbo.token = token
|
||||
qbo.secret = secret
|
||||
qbo.qb_token = at.token
|
||||
qbo.qb_secret = at.secret
|
||||
qbo.token_expires_at = 6.months.from_now.utc
|
||||
qbo.reconnect_token_at = 5.months.from_now.utc
|
||||
qbo.realmId = realm_id
|
||||
qbo.company_id = params['realmId']
|
||||
if qbo.save!
|
||||
redirect_to qbo_sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
|
||||
else
|
||||
@@ -69,12 +66,15 @@ class QboController < ApplicationController
|
||||
#
|
||||
def sync
|
||||
if Qbo.exists?
|
||||
QboCustomer.update_all
|
||||
QboItem.update_all
|
||||
QboEmployee.update_all
|
||||
QboEstimate.update_all
|
||||
QboInvoice.update_all
|
||||
QboPurchase.update_all
|
||||
Customer.sync
|
||||
QboItem.sync
|
||||
QboEmployee.sync
|
||||
QboEstimate.sync
|
||||
QboInvoice.sync
|
||||
#QboPurchase.sync
|
||||
|
||||
# Record the last sync time
|
||||
Qbo.update_time_stamp
|
||||
end
|
||||
|
||||
redirect_to qbo_path(:redmine_qbo), :flash => { :notice => "Successfully synced to Quickbooks" }
|
||||
|
||||
@@ -24,53 +24,67 @@ class VehiclesController < ApplicationController
|
||||
# return an HTML form for creating a new vehicle
|
||||
def new
|
||||
@vehicle = Vehicle.new
|
||||
@customers = Customer.all.order(:name)
|
||||
end
|
||||
|
||||
# create a new vehicle
|
||||
def create
|
||||
@vehicle = Vehicle.new(params[:vehicle])
|
||||
@vehicle.save!
|
||||
redirect_to @vehicle
|
||||
if @vehicle.save
|
||||
flash[:notice] = "New Vehicle Created"
|
||||
redirect_to @vehicle
|
||||
else
|
||||
flash[:error] = @vehicle.errors.full_messages.to_sentence
|
||||
redirect_to new_vehicle_path
|
||||
end
|
||||
end
|
||||
|
||||
# display a specific vehicle
|
||||
def show
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
if @vehicle
|
||||
@customer = @vehicle.qbo_customer.name if @vehicle.qbo_customer
|
||||
else
|
||||
flash[:error] = "Vehicle Not Found"
|
||||
render :index
|
||||
begin
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# return an HTML form for editing a vehicle
|
||||
def edit
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
@customer = @vehicle.qbo_customer.id if @vehicle.qbo_customer
|
||||
begin
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
@customer = @vehicle.customer.id
|
||||
@customers = Customer.all.order(:name)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# update a specific vehicle
|
||||
def update
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
if @vehicle.update_attributes(params[:vehicle])
|
||||
flash[:success] = "Vehicle updated"
|
||||
redirect_to @vehicle
|
||||
else
|
||||
render :edit
|
||||
@customer = params[:customer]
|
||||
begin
|
||||
@vehicle = Vehicle.find_by_id(params[:id])
|
||||
if @vehicle.update_attributes(params[:vehicle])
|
||||
flash[:notice] = "Vehicle updated"
|
||||
redirect_to @vehicle
|
||||
else
|
||||
flash[:error] = @vehicle.errors.full_messages.to_sentence if @vehicle.errors
|
||||
redirect_to edit_vehicle_path
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
# delete a specific vehicle
|
||||
def destroy
|
||||
v = Vehicle.find_by_id(params[:id])
|
||||
if v != nil
|
||||
v.destroy
|
||||
flash.now[:notice] = "Vehicle deleted successfully"
|
||||
else
|
||||
flash.now[:error] = "No Vehicle Found"
|
||||
def destroy
|
||||
begin
|
||||
Vehicle.find_by_id(params[:id]).destroy
|
||||
flash[:notice] = "Vehicle deleted successfully"
|
||||
redirect_to action: :index
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
redirect_to action: :index
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
module EstimateHelper
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
module InvoiceHelper
|
||||
end
|
||||
@@ -8,11 +8,10 @@
|
||||
#
|
||||
#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.
|
||||
|
||||
module QboHelper
|
||||
|
||||
def qbo_customer_dropdown
|
||||
select = context[:form].select :qbo_customer_id, QboCustomers.all.pluck(:name, :id), :selected => selected, include_blank: true
|
||||
return "<p>#{select}</p>"
|
||||
end
|
||||
|
||||
module ActiveSupport::Callbacks::ClassMethods
|
||||
def without_callback(*args, &block)
|
||||
skip_callback(*args)
|
||||
yield
|
||||
set_callback(*args)
|
||||
end
|
||||
end
|
||||
163
app/models/customer.rb
Normal file
163
app/models/customer.rb
Normal file
@@ -0,0 +1,163 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2016 rick barrette
|
||||
#
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
#
|
||||
#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.
|
||||
|
||||
class Customer < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
has_many :issues
|
||||
has_many :qbo_purchases
|
||||
has_many :vehicles
|
||||
|
||||
attr_accessible :name, :notes, :email, :primary_phone, :mobile_phone
|
||||
validates_presence_of :id, :name
|
||||
|
||||
self.primary_key = :id
|
||||
|
||||
# returns a human readable string
|
||||
def to_s
|
||||
return name
|
||||
end
|
||||
|
||||
# Convenience Method
|
||||
# returns the customer's email
|
||||
def email
|
||||
pull unless @details
|
||||
begin
|
||||
return @details.email_address.address
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# Convenience Method
|
||||
# returns the customer's primary phone
|
||||
def primary_phone
|
||||
pull unless @details
|
||||
begin
|
||||
return @details.primary_phone.free_form_number
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# Convenience Method
|
||||
# Updates the customer's primary phone number
|
||||
def primary_phone=(n)
|
||||
pull unless @details
|
||||
pn = Quickbooks::Model::TelephoneNumber.new
|
||||
pn.free_form_number = n
|
||||
@details.primary_phone = pn
|
||||
end
|
||||
|
||||
# Convenience Method
|
||||
# returns the customer's mobile phone
|
||||
def mobile_phone
|
||||
pull unless @details
|
||||
begin
|
||||
return @details.mobile_phone.free_form_number
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# Convenience Method
|
||||
# Updates the custome's mobile phone number
|
||||
def mobile_phone=(n)
|
||||
pull unless @details
|
||||
pn = Quickbooks::Model::TelephoneNumber.new
|
||||
pn.free_form_number = n
|
||||
@details.mobile_phone = pn
|
||||
end
|
||||
|
||||
# Convenience Method
|
||||
# Updates Both local DB name & QBO display_name
|
||||
def name=(s)
|
||||
pull unless @details
|
||||
@details.display_name = s
|
||||
super
|
||||
end
|
||||
|
||||
# Magic Method
|
||||
# Maps Get/Set methods to QBO customer object
|
||||
def method_missing(sym, *arguments)
|
||||
# Check to see if the method exists
|
||||
if Quickbooks::Model::Customer.method_defined?(sym)
|
||||
# download details if required
|
||||
pull unless @details
|
||||
method_name = sym.to_s
|
||||
# Setter
|
||||
if method_name[-1, 1] == "="
|
||||
@details.method(method_name).call(arguments[0])
|
||||
# Getter
|
||||
else
|
||||
return @details.method(method_name).call
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# proforms a bruteforce sync operation
|
||||
# This needs to be simplified
|
||||
def self.sync
|
||||
service = Qbo.get_base(:customer).service
|
||||
|
||||
# Sync ALL customers if the database is empty
|
||||
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
|
||||
|
||||
customers.each do |customer|
|
||||
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
|
||||
end
|
||||
|
||||
# Push the updates
|
||||
def save_with_push
|
||||
begin
|
||||
@details = Qbo.get_base(:customer).service.update(@details)
|
||||
#raise "QBO Fault" if @details.fault?
|
||||
self.id = @details.id
|
||||
rescue Exception => e
|
||||
errors.add(e.message)
|
||||
end
|
||||
save_without_push
|
||||
end
|
||||
|
||||
alias_method :save_without_push, :save
|
||||
alias_method :save, :save_with_push
|
||||
|
||||
private
|
||||
|
||||
# pull the details
|
||||
def pull
|
||||
begin
|
||||
raise Exception unless self.id
|
||||
@details = Qbo.get_base(:customer).find_by_id(self.id)
|
||||
rescue Exception => e
|
||||
@details = Quickbooks::Model::Customer.new
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -25,10 +25,14 @@ class Qbo < ActiveRecord::Base
|
||||
|
||||
# Configure quickbooks-ruby-base to access our database
|
||||
Quickbooks::Base.configure do |c|
|
||||
c.persistent_token = 'qb_token'
|
||||
c.persistent_secret = 'qb_secret'
|
||||
c.persistent_company_id = 'company_id'
|
||||
end
|
||||
c.persistent_token = 'qb_token'
|
||||
c.persistent_secret = 'qb_secret'
|
||||
c.persistent_company_id = 'company_id'
|
||||
end
|
||||
|
||||
def self.get_oauth_consumer
|
||||
return $qb_oauth_consumer
|
||||
end
|
||||
|
||||
# Get a quickbooks base object for type
|
||||
# @params type of base
|
||||
@@ -40,4 +44,11 @@ class Qbo < ActiveRecord::Base
|
||||
def self.get_account
|
||||
first
|
||||
end
|
||||
|
||||
# Updates last sync time stamp
|
||||
def self.update_time_stamp
|
||||
qbo = Qbo.first
|
||||
qbo.last_sync = DateTime.now
|
||||
qbo.save
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2016 rick barrette
|
||||
#
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
#
|
||||
#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.
|
||||
|
||||
class QboCustomer < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
has_many :issues
|
||||
has_many :qbo_purchases
|
||||
has_many :vehicles
|
||||
|
||||
attr_accessible :name
|
||||
validates_presence_of :id, :name
|
||||
|
||||
after_initialize :get_details
|
||||
|
||||
self.primary_key = :id
|
||||
|
||||
# returns true if the customer is active
|
||||
def active?
|
||||
return @details.active? if @details
|
||||
end
|
||||
|
||||
# returns a human readable string
|
||||
def to_s
|
||||
return name
|
||||
end
|
||||
|
||||
# returns the customer's email
|
||||
def email
|
||||
begin
|
||||
return @details.email_address.address
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# returns the customer's primary phone
|
||||
def primary_phone
|
||||
begin
|
||||
return @details.primary_phone.free_form_number
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# returns the customer's mobile phone
|
||||
def mobile_phone
|
||||
begin
|
||||
return @details.mobile_phone.free_form_number
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# returns the customer's notes
|
||||
def notes
|
||||
return @details.notes if @details
|
||||
end
|
||||
|
||||
# updates the customer's notes in QBO
|
||||
def notes=(s)
|
||||
customer = get_customer(self.id)
|
||||
customer.notes = s
|
||||
get_base.update(customer)
|
||||
end
|
||||
|
||||
# returns the bases QBO service for customers
|
||||
def get_base
|
||||
Qbo.get_base(:customer)
|
||||
end
|
||||
|
||||
# returns the QBO customer
|
||||
def get_customer (id)
|
||||
get_base.find_by_id(id)
|
||||
end
|
||||
|
||||
# proforms a bruteforce sync operation
|
||||
# This needs to be simplified
|
||||
def update_all
|
||||
customers = get_base.service.all
|
||||
|
||||
transaction do
|
||||
# Update the customer table
|
||||
customers.each { |customer|
|
||||
qbo_customer = QboCustomer.find_or_create_by(id: customer.id)
|
||||
# only update if diffrent
|
||||
if not qbo_customer.name == customer.display_name
|
||||
qbo_customer.name = customer.display_name
|
||||
qbo_customer.id = customer.id
|
||||
qbo_customer.save!
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# remove deleted customers
|
||||
where.not(customers.map(&:id)).destroy_all
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# init details
|
||||
def get_details
|
||||
if self.id
|
||||
@details = get_customer(self.id)
|
||||
update
|
||||
end
|
||||
end
|
||||
|
||||
# update's the customers name if updated
|
||||
def update
|
||||
begin
|
||||
if not self.name == @detils.display_name
|
||||
self.name = @details.display_name
|
||||
self.save
|
||||
end
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -18,7 +18,7 @@ class QboEmployee < ActiveRecord::Base
|
||||
Qbo.get_base(:employee)
|
||||
end
|
||||
|
||||
def self.update_all
|
||||
def self.sync
|
||||
employees = get_base.service.all
|
||||
|
||||
transaction do
|
||||
|
||||
@@ -18,7 +18,7 @@ class QboEstimate < ActiveRecord::Base
|
||||
Qbo.get_base(:estimate)
|
||||
end
|
||||
|
||||
def self.update_all
|
||||
def self.sync
|
||||
estimates = get_base.service.all
|
||||
|
||||
# Update the item table
|
||||
|
||||
@@ -18,22 +18,31 @@ class QboInvoice < ActiveRecord::Base
|
||||
Qbo.get_base(:invoice)
|
||||
end
|
||||
|
||||
def self.update_all
|
||||
def self.sync
|
||||
#Pull the invoices from the quickbooks server
|
||||
invoices = get_base.service.all
|
||||
#invoices = get_base.service.all
|
||||
|
||||
last = Qbo.first.last_sync
|
||||
|
||||
query = "SELECT Id, DocNumber FROM Invoice"
|
||||
query << " WHERE Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
|
||||
|
||||
if count == 0
|
||||
invoices = get_base.service.all
|
||||
else
|
||||
invoices = get_base.service.query()
|
||||
end
|
||||
|
||||
# Update the invoice table
|
||||
transaction do
|
||||
invoices.each { | invoice |
|
||||
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
|
||||
invoices.each { | invoice |
|
||||
qbo_invoice = find_or_create_by(id: invoice.id)
|
||||
qbo_invoice.doc_number = invoice.doc_number
|
||||
qbo_invoice.id = invoice.id
|
||||
qbo_invoice.save!
|
||||
}
|
||||
|
||||
#remove deleted invoices
|
||||
where.not(invoices.map(&:id)).destroy_all
|
||||
#where.not(invoices.map(&:id)).destroy_all
|
||||
end
|
||||
|
||||
def self.update(id)
|
||||
|
||||
@@ -13,25 +13,35 @@ class QboItem < ActiveRecord::Base
|
||||
has_many :issues
|
||||
attr_accessible :name
|
||||
validates_presence_of :id, :name
|
||||
|
||||
|
||||
self.primary_key = :id
|
||||
|
||||
def self.get_base
|
||||
Qbo.get_base(:item)
|
||||
end
|
||||
|
||||
def self.update_all
|
||||
items = get_base.service.find_by(:type, "Service")
|
||||
|
||||
transaction do
|
||||
# Update the item table
|
||||
items.each { |item|
|
||||
qbo_item = QboItem.find_or_create_by(id: item.id)
|
||||
qbo_item.name = item.name
|
||||
qbo_item.id = item.id
|
||||
qbo_item.save!
|
||||
def self.sync
|
||||
last = Qbo.first.last_sync
|
||||
|
||||
query = "SELECT Id, Name FROM Item WHERE Type = 'Service' "
|
||||
query << " AND Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
|
||||
|
||||
if count == 0
|
||||
items = get_base.service.all
|
||||
else
|
||||
items = get_base.service.query(query)
|
||||
end
|
||||
|
||||
unless items.count = 0
|
||||
items.find_by(:type, "Service").each { |i|
|
||||
qbo_item = QboItem.find_or_create_by(id: i.id)
|
||||
qbo_item.name = i.name
|
||||
qbo_item.id = i.id
|
||||
qbo_item.save
|
||||
}
|
||||
end
|
||||
|
||||
#remove deleted items
|
||||
where.not(items.map(&:id)).destroy_all
|
||||
|
||||
# QboItem.where.not(items.map(&:id)).destroy_all
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -19,11 +19,11 @@ class QboPurchase < ActiveRecord::Base
|
||||
Qbo.get_base(:purchase)
|
||||
end
|
||||
|
||||
def self.get_purchase(id)
|
||||
def get_purchase(id)
|
||||
get_base.service.find_by_id(id)
|
||||
end
|
||||
|
||||
def self.update_all
|
||||
def self.sync
|
||||
QboPurchase.get_base.service.all.each { |purchase|
|
||||
|
||||
purchase.line_items.all? { |line_item|
|
||||
|
||||
@@ -11,17 +11,24 @@
|
||||
class Vehicle < ActiveRecord::Base
|
||||
|
||||
unloadable
|
||||
belongs_to :qbo_customer
|
||||
|
||||
attr_accessible :year, :make, :model, :qbo_customer_id, :notes, :vin
|
||||
validates_presence_of :year, :make, :model, :qbo_customer_id
|
||||
belongs_to :customer
|
||||
has_many :issues, :foreign_key => 'vehicles_id'
|
||||
|
||||
before_validation :decode_vin
|
||||
attr_accessible :year, :make, :model, :customer_id, :notes, :vin
|
||||
|
||||
validates_presence_of :customer
|
||||
validates :vin, uniqueness: true
|
||||
validates :year, numericality: { only_integer: true }
|
||||
|
||||
before_save :decode_vin
|
||||
after_initialize :get_details
|
||||
|
||||
self.primary_key = :id
|
||||
|
||||
# returns a human readable string
|
||||
def to_s
|
||||
return "#{self.year} #{self.make} #{self.model}"
|
||||
return "#{year} #{make} #{model}"
|
||||
end
|
||||
|
||||
# returns the raw JSON details from EMUNDS
|
||||
@@ -40,7 +47,7 @@ class Vehicle < ActiveRecord::Base
|
||||
|
||||
# returns the drive of the vehicle i.e. 2 wheel, 4 wheel, ect.
|
||||
def drive
|
||||
return @details['drivenWheels'] if @details
|
||||
return @details['drivenWheels'].to_s.upcase if @details
|
||||
end
|
||||
|
||||
# returns the number of doors of the vehicle
|
||||
@@ -48,12 +55,36 @@ class Vehicle < ActiveRecord::Base
|
||||
return @details['numOfDoors'] if @details
|
||||
end
|
||||
|
||||
# Force Upper Case for VIN numbers
|
||||
def make=(val)
|
||||
# The to_s is in case you get nil/non-string
|
||||
write_attribute(:make, val.to_s.titleize)
|
||||
end
|
||||
|
||||
# Force Upper Case for VIN numbers
|
||||
def model=(val)
|
||||
# The to_s is in case you get nil/non-string
|
||||
write_attribute(:model, val.to_s.titleize)
|
||||
end
|
||||
|
||||
# Force Upper Case for VIN numbers
|
||||
def vin=(val)
|
||||
# The to_s is in case you get nil/non-string
|
||||
write_attribute(:vin, val.to_s.upcase)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# init method to pull JSON details from Edmunds
|
||||
def get_details
|
||||
if self.vin?
|
||||
@details = JSON.parse get_decoder.full(self.vin)
|
||||
begin
|
||||
@details = JSON.parse get_decoder.full(self.vin)
|
||||
raise @details['message'] if @details['status'] == "NOT_FOUND"
|
||||
raise @details['message'] if @details['status'] == "BAD_REQUEST"
|
||||
rescue Exception => e
|
||||
errors.add(:vin, e.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,11 +97,14 @@ class Vehicle < ActiveRecord::Base
|
||||
# decodes a vin and updates self
|
||||
def decode_vin
|
||||
get_details
|
||||
if self.vin?
|
||||
details
|
||||
self.year = @details['years'][0]['year']
|
||||
self.make = @details['make']['name']
|
||||
self.model = @details['model']['name']
|
||||
if @details
|
||||
begin
|
||||
self.year = @details['years'][0]['year']
|
||||
self.make = @details['make']['name']
|
||||
self.model = @details['model']['name']
|
||||
rescue Exception => e
|
||||
errors.add(:vin, e.message)
|
||||
end
|
||||
end
|
||||
self.name = to_s
|
||||
end
|
||||
|
||||
@@ -2,33 +2,38 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Customer</th>
|
||||
<td><%= @customer.name %></td>
|
||||
<td><%= customer.name %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Email</th>
|
||||
<td><%= @customer.email %></td>
|
||||
<td><%= customer.email %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Primary Phone</th>
|
||||
<td><%= @customer.primary_phone %></td>
|
||||
<td><%= customer.primary_phone %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Mobile Phone</th>
|
||||
<td><%= @customer.mobile_phone %></td>
|
||||
<td><%= customer.mobile_phone %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Notes</th>
|
||||
<td><%= @customer.notes %></td>
|
||||
<td><%= customer.notes %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Issues</th>
|
||||
<td><%= customer.issues.count %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td/>
|
||||
<td>
|
||||
<%= button_to "Edit", edit_customer_path(@customer), method: :get%>
|
||||
<%= button_to "Edit Customer", edit_customer_path(customer), method: :get%>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
60
app/views/customers/_form.html.erb
Normal file
60
app/views/customers/_form.html.erb
Normal file
@@ -0,0 +1,60 @@
|
||||
<div class="row">
|
||||
<div class="span6 columns">
|
||||
<fieldset>
|
||||
|
||||
<%= form_for @customer do |f| %>
|
||||
|
||||
<div class="clearfix">
|
||||
Display Name:
|
||||
<div class="input">
|
||||
<%= f.text_field :name, :required => true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
Phone Number:
|
||||
<div class="input">
|
||||
<%= f.telephone_field :primary_phone %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
Mobile Phone Number:
|
||||
<div class="input">
|
||||
<%= f.telephone_field :mobile_phone %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
Email:
|
||||
<div class="input">
|
||||
<%= f.email_field :email %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
Notes:
|
||||
<div class="input">
|
||||
<p>
|
||||
<%= link_to_function content_tag(:span, l(:button_edit), :class => 'icon icon-edit'), '$(this).hide(); $("#issue_description_and_toolbar").show()' unless @customer.new_record? %>
|
||||
<%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@customer.new_record? ? nil : 'display:none') do %>
|
||||
<%= f.text_area :notes,
|
||||
:cols => 60,
|
||||
:rows => 10,
|
||||
:accesskey => accesskey(:edit),
|
||||
:class => 'wiki-edit',
|
||||
:no_label => true %>
|
||||
<% end %>
|
||||
</p>
|
||||
<%= wikitoolbar_for 'issue_description' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
3
app/views/customers/edit.html.erb
Normal file
3
app/views/customers/edit.html.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1>Edit Customer</h1>
|
||||
<br/>
|
||||
<%= render :partial => 'customers/form' %>
|
||||
@@ -4,8 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="span6 columns">
|
||||
<fieldset>
|
||||
<% @customer = c %>
|
||||
<%= render :partial => 'customers/details' %>
|
||||
<%= c.name %>
|
||||
<%= button_to "Show", customer_path(c.id), method: :get %>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
3
app/views/customers/new.html.erb
Normal file
3
app/views/customers/new.html.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1>New Customer</h1>
|
||||
<br/>
|
||||
<%= render :partial => 'customers/form' %>
|
||||
@@ -1,5 +1,7 @@
|
||||
<h1>Customer Detail</h1>
|
||||
<br/>
|
||||
<%= render :partial => 'customers/details' %>
|
||||
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
|
||||
<br/>
|
||||
<%= render :partial => 'vehicles/list' %>
|
||||
<br/>
|
||||
<%= render :partial => 'issues/list_simple', locals: {issues: @issues} %>
|
||||
|
||||
@@ -14,9 +14,9 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
<h1> Redmine Quickbooks</h1>
|
||||
<%= form_for @qbo do |f|%>
|
||||
<div>
|
||||
<%= f.label "Customer Count:"+@qbo_customer_count.to_s%>
|
||||
<%= f.label "Customer Count:"+@customer_count.to_s%>
|
||||
<br/>
|
||||
<%= f.select :qbo_customer_id, QboCustomer.all.pluck(:name, :id).sort, :selected => @selected_customer, include_blank: true %>
|
||||
<%= f.select :customer_id, Customer.all.pluck(:name, :id).sort, :selected => @selected_customer, include_blank: true %>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -1,45 +1,51 @@
|
||||
<table>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<th>Customer</th>
|
||||
<td><%= @customer %></td>
|
||||
<td><%= vehicle.customer.name %></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<th>Vehicle</th>
|
||||
<td><%= @vehicle.to_s %></td>
|
||||
<td><%= vehicle.to_s %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>VIN</th>
|
||||
<td><%= @vehicle.vin %></td>
|
||||
<td><%= vehicle.vin %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Style</th>
|
||||
<td><%= @vehicle.style %></td>
|
||||
<td><%= vehicle.style %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Drive</th>
|
||||
<td><%= @vehicle.drive %></td>
|
||||
<td><%= vehicle.drive %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Doors</th>
|
||||
<td><%= @vehicle.doors %></td>
|
||||
<td><%= vehicle.doors %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Notes</th>
|
||||
<td><%= @vehicle.notes %></td>
|
||||
<td><%= vehicle.notes %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Issues</th>
|
||||
<td><%= vehicle.issues.count %></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td/>
|
||||
<td>
|
||||
<%= button_to "Edit", edit_vehicle_path(@vehicle), method: :get%>
|
||||
<%= button_to "Delete", @vehicle, method: :delete, data: {confirm: "You sure?"} %>
|
||||
<%= button_to "Edit", edit_vehicle_path(vehicle), method: :get%>
|
||||
<%= button_to "Delete", vehicle, method: :delete, data: {confirm: "You sure?"} %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -6,28 +6,28 @@
|
||||
<div class="clearfix">
|
||||
Customer:
|
||||
<div class="input">
|
||||
<%= f.collection_select :qbo_customer_id, QboCustomer.order(:name), :id, :name, include_blank: true, :selected => @customer, :required => true%>
|
||||
<%= f.collection_select :customer_id, @customers, :id, :name, include_blank: true, :selected => @customer, :required => true%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
Year:
|
||||
<div class="input">
|
||||
<%= f.text_field :year, :required => true %>
|
||||
<%= f.number_field :year %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
Make:
|
||||
<div class="input">
|
||||
<%= f.text_field :make, :required => true %>
|
||||
<%= f.text_field :make %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
Model:
|
||||
<div class="input">
|
||||
<%= f.text_field :model, :required => true %>
|
||||
<%= f.text_field :model %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,18 @@
|
||||
<div class="clearfix">
|
||||
Notes:
|
||||
<div class="input">
|
||||
<%= f.text_area :notes, :rows => 6, :class => 'wiki-edit', :accesskey => accesskey(:edit), :cols => 60%>
|
||||
<p>
|
||||
<%= link_to_function content_tag(:span, l(:button_edit), :class => 'icon icon-edit'), '$(this).hide(); $("#issue_description_and_toolbar").show()' unless @vehicle.new_record? %>
|
||||
<%= content_tag 'span', :id => "issue_description_and_toolbar", :style => (@vehicle.new_record? ? nil : 'display:none') do %>
|
||||
<%= f.text_area :notes,
|
||||
:cols => 60,
|
||||
:rows => 10,
|
||||
:accesskey => accesskey(:edit),
|
||||
:class => 'wiki-edit',
|
||||
:no_label => true %>
|
||||
<% end %>
|
||||
</p>
|
||||
<%= wikitoolbar_for 'issue_description' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
<div class="row">
|
||||
<div class="span6 columns">
|
||||
<fieldset>
|
||||
<% @customer = vehicle.qbo_customer.name if vehicle.qbo_customer %>
|
||||
<% @vehicle = vehicle %>
|
||||
<%= render :partial => 'vehicles/details' %>
|
||||
<%= vehicle.to_s %>
|
||||
<br/>
|
||||
<div style="float: right;" >
|
||||
<%= button_to "More", vehicle_path(vehicle), method: :get %>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
@@ -14,5 +16,5 @@
|
||||
|
||||
<div class="actions">
|
||||
<%= will_paginate @vehicles %>
|
||||
<%= button_to "New", new_vehicle_path, method: :get %>
|
||||
<%= button_to "New Vehicle", new_vehicle_path, method: :get %>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<h1>Customer Vehicle</h1>
|
||||
<br/>
|
||||
<%= render :partial => 'vehicles/details' %>
|
||||
|
||||
<div style="text-align: left; width:90%;">
|
||||
<%= render :partial => 'vehicles/details', locals: {vehicle: @vehicle} %>
|
||||
|
||||
<%= render :partial => 'issues/list_simple', locals: {issues: @vehicle.issues} %>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# English strings go here for Rails i18n
|
||||
en:
|
||||
# my_label: "My label"
|
||||
field_qbo_customer: "Customer"
|
||||
field_customer: "Customer"
|
||||
field_qbo_item: "Item"
|
||||
field_qbo_employee: "Employee"
|
||||
field_qbo_invoice: "Invoice"
|
||||
|
||||
17
db/migrate/019_qbocustomers_to_customers.rb
Normal file
17
db/migrate/019_qbocustomers_to_customers.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
#The MIT License (MIT)
|
||||
#
|
||||
#Copyright (c) 2016 rick barrette
|
||||
#
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
#
|
||||
#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.
|
||||
|
||||
class QbocustomersToCustomers< ActiveRecord::Migration
|
||||
def change
|
||||
rename_table :qbo_customers, :customers
|
||||
rename_column :issues, :qbo_customer_id, :customer_id
|
||||
rename_column :vehicles, :qbo_customer_id, :customer_id
|
||||
end
|
||||
end
|
||||
@@ -8,6 +8,8 @@
|
||||
#
|
||||
#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.
|
||||
|
||||
module VehicleHelper
|
||||
|
||||
class UpdateQbosTimeStamp < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :qbos, :last_sync, :datetime
|
||||
end
|
||||
end
|
||||
4
init.rb
4
init.rb
@@ -25,13 +25,13 @@ 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.5'
|
||||
version '0.0.6'
|
||||
url 'https://github.com/rickbarrette/redmine_qbo'
|
||||
author_url 'http://rickbarrette.org'
|
||||
settings :default => {'empty' => true}, :partial => 'qbo/settings'
|
||||
|
||||
# Add safe attributes
|
||||
Issue.safe_attributes 'qbo_customer_id'
|
||||
Issue.safe_attributes 'customer_id'
|
||||
Issue.safe_attributes 'qbo_item_id'
|
||||
Issue.safe_attributes 'qbo_estimate_id'
|
||||
Issue.safe_attributes 'qbo_invoice_id'
|
||||
|
||||
@@ -22,11 +22,11 @@ module IssuePatch
|
||||
# Same as typing in the class
|
||||
base.class_eval do
|
||||
unloadable # Send unloadable so it will not be unloaded in development
|
||||
belongs_to :qbo_customer, primary_key: :id
|
||||
belongs_to :customer, primary_key: :id
|
||||
belongs_to :qbo_item, primary_key: :id
|
||||
belongs_to :qbo_estimate, primary_key: :id
|
||||
belongs_to :qbo_invoice, primary_key: :id
|
||||
belongs_to :vehicle, primary_key: :id
|
||||
belongs_to :vehicle
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -16,17 +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].qbo_customer ? context[:issue].qbo_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
|
||||
|
||||
customer = QboCustomer.find_by_id(selected_customer) if selected_customer
|
||||
vehicle = context[:issue].vehicles_id
|
||||
# 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
|
||||
|
||||
# Generate the drop down list of quickbooks customers
|
||||
select_customer = f.select :qbo_customer_id, QboCustomer.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
|
||||
|
||||
@@ -36,13 +34,10 @@ 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
|
||||
|
||||
#@estimates_link = link_to qbo_update_estimates_path
|
||||
|
||||
#render_on :view_issues_form_details_bottom, :partial => 'hooks/redmine_qbo/vehicles/dropdown'
|
||||
vehicles = customer.vehicles.pluck(:name, :id).sort if customer
|
||||
vehicles = Vehicle.all if not vehicles
|
||||
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
|
||||
|
||||
return "<p>#{select_customer}</p> <p>#{select_item}</p> <p>#{select_invoice}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
|
||||
return "<p>#{@select_customer}</p> <p>#{select_item}</p> <p>#{select_invoice}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
||||
issue = context[:issue]
|
||||
|
||||
# Check to see if we have registered with QBO
|
||||
if Qbo.first && issue.qbo_customer && issue.qbo_item
|
||||
if Qbo.first && issue.customer && issue.qbo_item
|
||||
|
||||
# if this is a quote, lets create a new estimate based off estimated hours
|
||||
if issue.tracker.name = "Quote" && issue.status.name = "New" && issue.qbo_estimate
|
||||
@@ -26,7 +26,7 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
||||
|
||||
# Create the estimate
|
||||
estimate = estimate_base.qr_model(:estimate)
|
||||
estimate.customer_id = issue.qbo_customer_id
|
||||
estimate.customer_id = issue.customer_id
|
||||
estimate.txn_date = Date.today
|
||||
|
||||
# Create the line item for labor
|
||||
@@ -59,7 +59,7 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
||||
|
||||
# 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.qbo_customer && issue.qbo_item && employee_id && issue.status.is_closed?
|
||||
bill_time(issue, employee_id) if Qbo.first && issue.customer && issue.qbo_item && employee_id && issue.status.is_closed?
|
||||
end
|
||||
|
||||
# Create billable time entries
|
||||
@@ -90,7 +90,7 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
||||
item = item_service.fetch_by_id issue.qbo_item_id
|
||||
time_entry.description = "#{issue.tracker} ##{issue.id}: #{issue.subject}"
|
||||
time_entry.employee_id = employee_id
|
||||
time_entry.customer_id = issue.qbo_customer_id
|
||||
time_entry.customer_id = issue.customer_id
|
||||
time_entry.billable_status = "Billable"
|
||||
time_entry.hours = hours
|
||||
time_entry.minutes = minutes
|
||||
|
||||
@@ -21,7 +21,9 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
|
||||
issue = context[:issue]
|
||||
|
||||
# Check to see if there is a quickbooks user attached to the issue
|
||||
customer = issue.qbo_customer ? issue.qbo_customer.name : nil
|
||||
if issue.customer
|
||||
customer = link_to issue.customer.name, "#{Redmine::Utils::relative_url_root}/customers/#{issue.customer.id}"
|
||||
end
|
||||
|
||||
# Check to see if there is a quickbooks item attached to the issue
|
||||
item = issue.qbo_item ? issue.qbo_item.name : nil
|
||||
@@ -39,17 +41,19 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
|
||||
end
|
||||
|
||||
|
||||
if issue.vehicles_id
|
||||
v = Vehicle.find_by_id(issue.vehicles_id)
|
||||
begin
|
||||
v = Vehicle.find(issue.vehicles_id)
|
||||
vehicle = link_to v.to_s, "#{Redmine::Utils::relative_url_root}/vehicles/#{v.id}"
|
||||
vin = v.vin
|
||||
notes = v.notes
|
||||
rescue
|
||||
#do nothing
|
||||
end
|
||||
|
||||
return "
|
||||
<div class=\"attributes\">
|
||||
|
||||
<div class=\"qbo_customer_id attribute\">
|
||||
<div class=\"customer_id attribute\">
|
||||
<div class=\"label\"><span>Customer</span>:</div>
|
||||
<div class=\"value\">#{customer}</div>
|
||||
</div>
|
||||
|
||||
@@ -37,10 +37,11 @@ module IssuesPdfHelperPatch
|
||||
pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
|
||||
pdf.ln
|
||||
|
||||
customer = issue.customer.name if issue.customer
|
||||
left = []
|
||||
left << [l(:field_status), issue.status]
|
||||
left << [l(:field_priority), issue.priority]
|
||||
left << [l(:field_qbo_customer), issue.qbo_customer.name]
|
||||
left << [l(:field_customer), customer]
|
||||
left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
|
||||
#left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
|
||||
#left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
|
||||
@@ -51,7 +52,7 @@ module IssuesPdfHelperPatch
|
||||
notes = v ? v.notes : nil
|
||||
left << [l(:field_vehicles), vehicle]
|
||||
left << [l(:field_vin), vin]
|
||||
left << [l(:field_notes), notes]
|
||||
#left << [l(:field_notes), notes]
|
||||
|
||||
right = []
|
||||
right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
|
||||
@@ -59,7 +60,7 @@ module IssuesPdfHelperPatch
|
||||
right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
|
||||
right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
|
||||
right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
|
||||
#right << [l(:field_notes), notes]
|
||||
right << [l(:field_notes), notes]
|
||||
|
||||
rows = left.size > right.size ? left.size : right.size
|
||||
while left.size < rows
|
||||
|
||||
@@ -35,7 +35,7 @@ module QueryPatch
|
||||
def available_columns_with_qbo
|
||||
unless @available_columns
|
||||
@available_columns = available_columns_without_qbo
|
||||
@available_columns << QueryColumn.new(:qbo_customer, :sortable => "#{QboCustomer.table_name}.name", :groupable => true, :caption => :field_qbo_customer)
|
||||
@available_columns << QueryColumn.new(:customer, :sortable => "#{Customer.table_name}.name", :groupable => true, :caption => :field_customer)
|
||||
end
|
||||
@available_columns
|
||||
end
|
||||
@@ -44,11 +44,20 @@ module QueryPatch
|
||||
unless @available_filters
|
||||
@available_filters = available_filters_without_qbo
|
||||
|
||||
qbo_filters = {
|
||||
"customer" => {
|
||||
:name => l(:field_qbo_customer),
|
||||
:type => :text,
|
||||
:order => @available_filters.size + 1},
|
||||
#qbo_filters = {
|
||||
# :customer => {
|
||||
# :id => l(:field_customer),
|
||||
# :type => :integer,
|
||||
# :order => @available_filters.size + 1},
|
||||
#}
|
||||
|
||||
qbo_filters = {
|
||||
"customer_id" => {
|
||||
:id => :customer_id,
|
||||
:type => :list_optional,
|
||||
:order => @available_filters.size + 1,
|
||||
#:values => Customer.find(:all).collect { |c| [c.name, c.id.to_s]}
|
||||
}
|
||||
}
|
||||
|
||||
@available_filters.merge!(qbo_filters)
|
||||
|
||||
Reference in New Issue
Block a user