33 Commits

Author SHA1 Message Date
19733c3f8c Added gem rexml 2025-06-16 23:41:30 -04:00
f22795ac90 Moved strings for notices to en.yml 2025-06-16 22:56:54 -04:00
166a9ee31b Removed has_many purchases, table doesn't exist anymore 2025-06-16 22:54:47 -04:00
4d85c24872 generate redirect_uri protocol based one site settings 2025-06-16 22:30:30 -04:00
43c7374c42 Load oauth key & secret when constructing client, not on application start up 2025-06-16 22:16:58 -04:00
60857e9dca generate redirect_uri protocol based one site settings 2025-06-16 22:15:42 -04:00
d38f0d6ac1 Merge branch 'master' of https://github.com/rickbarrette/redmine_qbo 2025-06-15 19:03:59 -04:00
f6da031e72 include Redmine::I18n 2025-06-15 18:59:00 -04:00
9779437c00 Log token refresh 2024-12-19 09:41:53 -05:00
1a37926628 Log error not info 2024-12-19 09:36:23 -05:00
dac9a7c756 Started Support for Redmine 6 2024-12-12 06:00:36 -05:00
9ac1261ed0 Sort by id not doc_number
This fixes the bug where documents were displayed out of order
2024-08-30 09:19:58 -04:00
9b69d3f728 Added link to customer profile for appointments 2024-08-26 11:00:50 -04:00
a5de879260 Fixed formatting 2024-08-26 08:41:56 -04:00
6464e1cbc6 Added actions 2024-08-26 07:58:40 -04:00
7f3a94229a Create Estimate 2024-08-26 07:58:05 -04:00
395e0117fb Update _actions.html.erb 2024-08-26 07:57:10 -04:00
e04d363e42 Added label for actions 2024-08-26 07:56:06 -04:00
3b6c0d4a70 Removed Action links 2024-08-26 07:52:27 -04:00
d1f6ccd9cb Create _actions.html.erb 2024-08-26 07:51:38 -04:00
74f7ba41df Add Appointment Link 2024-08-21 21:39:50 -04:00
4fb424faa8 Only sync by doc number if not in database 2024-08-20 07:14:37 -04:00
63218e7f42 Fixed formating 2024-08-19 23:28:54 -04:00
7f0bb3cae7 Removed extra end 2024-08-19 23:26:43 -04:00
ad7417c233 Moved work into thread to repsond quickly 2024-08-19 23:21:56 -04:00
cf0be2336b Removed sync button from sidebar 2024-08-19 23:12:20 -04:00
6e08746611 2.1.1 Force Estimate sync by Doc Number when searching 2024-08-19 22:51:53 -04:00
7eb26facaf Use the first result 2024-08-19 22:49:20 -04:00
9115cc662c Forgot params[:search] 2024-08-19 22:39:50 -04:00
9e7c1dbfb2 removed () 2024-08-19 22:38:16 -04:00
e99f5d2e52 Added webhook view 2024-08-19 22:36:44 -04:00
039d1ca993 Use Logger.info 2024-08-19 22:31:41 -04:00
dd9ac3c481 Added Estimate.sync_by_doc_number 2024-08-19 22:30:34 -04:00
31 changed files with 199 additions and 144 deletions

View File

@@ -7,6 +7,7 @@ gem 'nhtsa_vin'
gem 'will_paginate'
gem 'rails-jquery-autocomplete'
gem 'jquery-ui-rails'
gem 'rexml'
group :assets do
gem 'coffee-rails'

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -10,7 +10,6 @@
# This controller class will handle map management
class CustomersController < ApplicationController
unloadable
include AuthHelper
helper :issues
@@ -142,7 +141,7 @@ class CustomersController < ApplicationController
def share
Thread.new do
logger.debug "Removing expired customer tokens"
logger.info "Removing expired customer tokens"
CustomerToken.remove_expired_tokens
ActiveRecord::Base.connection.close
end

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -8,7 +8,6 @@
#
#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 EstimateController < ApplicationController
unloadable
include AuthHelper
@@ -16,6 +15,15 @@ class EstimateController < ApplicationController
skip_before_action :verify_authenticity_token, :check_if_login_required, :unless => proc {|c| session[:token].nil? }
def get_estimate
# Force sync for estimate by doc number if not found
if Estimate.find_by_doc_number(params[:search]).nil?
begin
Estimate.sync_by_doc_number(params[:search]) if params[:search]
rescue
logger.info "Estimate.find_by_doc_number failed"
end
end
estimate = Estimate.find_by_id(params[:id]) if params[:id]
estimate = Estimate.find_by_doc_number(params[:search]) if params[:search]
return estimate

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2024 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:
#
@@ -8,7 +8,6 @@
#
#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 InvoiceController < ApplicationController
unloadable
include AuthHelper

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2016 - 2025 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:
#
@@ -9,7 +9,6 @@
#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 QboController < ApplicationController
unloadable
require 'openssl'
@@ -26,8 +25,8 @@ class QboController < ApplicationController
# Called when the user requests that Redmine to connect to QBO
#
def authenticate
redirect_uri = "https://" + Setting.host_name + qbo_oauth_callback_path
logger.info "redirect_uri: " + redirect_uri
redirect_uri = "#{Setting.protocol}://#{Setting.host_name + qbo_oauth_callback_path}"
logger.info "redirect_uri: #{redirect_uri}"
oauth2_client = Qbo.construct_oauth2_client
grant_url = oauth2_client.auth_code.authorize_url(redirect_uri: redirect_uri, response_type: "code", state: SecureRandom.hex(12), scope: "com.intuit.quickbooks.accounting")
redirect_to grant_url
@@ -40,7 +39,7 @@ class QboController < ApplicationController
if params[:state].present?
oauth2_client = Qbo.construct_oauth2_client
# use the state value to retrieve from your backend any information you need to identify the customer in your system
redirect_uri = "https://" + Setting.host_name + qbo_oauth_callback_path
redirect_uri = "#{Setting.protocol}://#{Setting.host_name + qbo_oauth_callback_path}"
if resp = oauth2_client.auth_code.get_token(params[:code], redirect_uri: redirect_uri)
# Remove the last authentication information
@@ -52,9 +51,9 @@ class QboController < ApplicationController
qbo.refresh_token!
if qbo.save!
redirect_to qbo_sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
redirect_to qbo_sync_path, :flash => { :notice => I18n.t(:label_connected) }
else
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => I18n.t(:label_error) }
end
end
@@ -66,9 +65,9 @@ class QboController < ApplicationController
i = Issue.find_by_id params[:id]
if i.customer
i.bill_time
redirect_to i, :flash => { :notice => "Successfully Billed #{i.customer.name}" }
redirect_to i, :flash => { :notice => I18n.t(:label_billed_success) + i.customer.name }
else
redirect_to i, :flash => { :error => "Cannot bill without a customer assigned" }
redirect_to i, :flash => { :error => I18n.t(:label_billing_error) }
end
end
@@ -85,44 +84,46 @@ class QboController < ApplicationController
# 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']
logger.debug "Casting #{name.constantize} to obj"
# 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!
Thread.new do
if request.headers['content-type'] == 'application/json'
data = JSON.parse(data)
else
begin
obj.sync_by_id(id)
rescue => e
logger.error "Failed to call sync_by_id on obj"
logger.error e.message
logger.error e.backtrace.join("\n")
# 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']
logger.info "Casting #{name.constantize} to obj"
# 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
begin
obj.sync_by_id(id)
rescue => e
logger.error "Failed to call sync_by_id on obj"
logger.error e.message
logger.error e.backtrace.join("\n")
end
end
end
# Record that last time we updated
Qbo.update_time_stamp
ActiveRecord::Base.connection.close
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, status: 200
else
@@ -151,6 +152,6 @@ class QboController < ApplicationController
ActiveRecord::Base.connection.close
end
redirect_to :home, :flash => { :notice => "Syncing Quickbooks" }
redirect_to :home, :flash => { :notice => I18n.t(:label_syncing) }
end
end

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -10,7 +10,6 @@
# This controller class will handle map management
class VehiclesController < ApplicationController
unloadable
include AuthHelper

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2016 - 2025 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:
#
@@ -11,9 +11,6 @@
module QuickbooksOauth
extend ActiveSupport::Concern
OAUTH_CONSUMER_KEY = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
OAUTH_CONSUMER_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
#== Instance Methods
def perform_authenticated_request(&block)
@@ -21,7 +18,7 @@ module QuickbooksOauth
begin
yield oauth_access_token
rescue OAuth2::Error, Quickbooks::AuthorizationFailure => ex
Rails.logger.info("QuickbooksOauth.perform: #{ex.message}")
Rails.logger.error("QuickbooksOauth.perform: #{ex.message}")
# to prevent an infinite loop here keep a counter and bail out after N times...
attempts += 1
@@ -36,6 +33,7 @@ module QuickbooksOauth
end
def refresh_token!
Rails.logger.info("QuickbooksOauth.refresh_token!")
t = oauth_access_token
refreshed = t.refresh!
@@ -45,6 +43,8 @@ module QuickbooksOauth
oauth2_refresh_token_expires_at = 100.days.from_now
end
Rails.logger.info("QuickbooksOauth.refresh_token!: #{oauth2_refresh_token_expires_at}")
update!(
oauth2_access_token: refreshed.token,
oauth2_access_token_expires_at: Time.at(refreshed.expires_at),
@@ -68,12 +68,16 @@ module QuickbooksOauth
module ClassMethods
def construct_oauth2_client
oauth_consumer_key = Setting.plugin_redmine_qbo['settingsOAuthConsumerKey']
oauth_consumer_secret = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret']
options = {
site: "https://appcenter.intuit.com/connect/oauth2",
authorize_url: "https://appcenter.intuit.com/connect/oauth2",
token_url: "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"
}
OAuth2::Client.new(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET, options)
OAuth2::Client.new(oauth_consumer_key, oauth_consumer_secret, options)
end
end

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2016 - 2025 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:
#
@@ -9,10 +9,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.
class Customer < ActiveRecord::Base
unloadable
has_many :issues
has_many :purchases
has_many :invoices
has_many :estimates
has_many :vehicles

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -9,7 +9,7 @@
#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 CustomerToken < ActiveRecord::Base
unloadable
has_many :issues
validates_presence_of :issue_id
before_create :generate_token, :generate_expire_date

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2024 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:
#
@@ -9,7 +9,7 @@
#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 Employee < ActiveRecord::Base
unloadable
has_many :users
validates_presence_of :id, :name

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2024 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:
#
@@ -9,7 +9,6 @@
#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 Estimate < ActiveRecord::Base
unloadable
has_and_belongs_to_many :issues
belongs_to :customer
@@ -18,7 +17,7 @@ class Estimate < ActiveRecord::Base
# sync all estimates
def self.sync
logger.debug "Syncing ALL estimates"
logger.info "Syncing ALL estimates"
qbo = Qbo.first
estimates = qbo.perform_authenticated_request do |access_token|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
@@ -37,13 +36,23 @@ class Estimate < ActiveRecord::Base
# sync only one estimate
def self.sync_by_id(id)
logger.debug "Syncing estimate #{id}"
logger.info "Syncing estimate #{id}"
qbo = Qbo.first
qbo.perform_authenticated_request do |access_token|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
process_estimate(service.fetch_by_id(id))
end
end
# sync only one estimate
def self.sync_by_doc_number(number)
logger.info "Syncing estimate by doc number #{number}"
qbo = Qbo.first
qbo.perform_authenticated_request do |access_token|
service = Quickbooks::Service::Estimate.new(:company_id => qbo.realm_id, :access_token => access_token)
process_estimate(service.find_by( :doc_number, number).first)
end
end
# update an estimate
def self.update(id)

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2024 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:
#
@@ -9,7 +9,7 @@
#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 Invoice < ActiveRecord::Base
unloadable
has_and_belongs_to_many :issues
belongs_to :customer
validates_presence_of :doc_number, :id, :customer_id, :txn_date
@@ -17,7 +17,7 @@ class Invoice < ActiveRecord::Base
# sync ALL the invoices
def self.sync
logger.debug "Syncing all invoices"
logger.info "Syncing all invoices"
last = Qbo.first.last_sync
query = "SELECT Id, DocNumber FROM Invoice"
@@ -40,7 +40,7 @@ class Invoice < ActiveRecord::Base
#sync by invoice ID
def self.sync_by_id(id)
logger.debug "Syncing invoice #{id}"
logger.info "Syncing invoice #{id}"
qbo = Qbo.first
qbo.perform_authenticated_request do |access_token|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
@@ -58,7 +58,7 @@ class Invoice < ActiveRecord::Base
# skip this issue if the issue customer is not the same as the invoice customer
return if issue.customer_id != invoice.customer_ref.value.to_i
logger.debug "Attaching invoice #{invoice.id} to issue #{issue.id}"
logger.info "Attaching invoice #{invoice.id} to issue #{issue.id}"
invoice = Invoice.find_or_create_by(id: invoice.id)
@@ -105,7 +105,7 @@ class Invoice < ActiveRecord::Base
# this condions causes an infinite loop as the webhook is called when an invoice is updated
# TODO maybe add a cf_sync_confict flag to invoices
def self.compare_custom_fields(issue, invoice)
logger.debug "Comparing custom fields"
logger.info "Comparing custom fields"
# TODO break if Invoice.find(invoice.id).cf_sync_confict
is_changed = false
@@ -120,12 +120,12 @@ class Invoice < ActiveRecord::Base
# Only update if blank to prevent infite loops
# TODO check cf_sync_confict flag once implemented
if cf.string_value.to_s.blank?
logger.debug " VIN was blank, updating the invoice vin in quickbooks"
logger.info " VIN was blank, updating the invoice vin in quickbooks"
vin = Vehicle.find(issue.vehicles_id).vin
break if vin.nil?
if not cf.string_value.to_s.eql? vin
cf.string_value = vin.to_s
logger.debug "VIN has changed"
logger.info "VIN has changed"
is_changed = true
end
@@ -155,7 +155,7 @@ class Invoice < ActiveRecord::Base
# Push updates
begin
logger.debug "Trying to update invoice"
logger.info "Trying to update invoice"
qbo = Qbo.first
qbo.perform_authenticated_request do |access_token|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2016 - 2025 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:
#
@@ -9,9 +9,9 @@
#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 Qbo < ActiveRecord::Base
unloadable
include QuickbooksOauth
include Redmine::I18n
# Updates last sync time stamp
def self.update_time_stamp

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -10,8 +10,6 @@
class Vehicle < ActiveRecord::Base
unloadable
belongs_to :customer
has_many :issues, :foreign_key => 'vehicles_id'

View File

@@ -0,0 +1,11 @@
<%= link_to t(:label_appointment), "https://calendar.google.com/calendar/render?action=TEMPLATE&text=#{@customer.name}+-&details=#{ link_to "Customer Details", "https://#{Setting.host_name}#{customer_path @customer.id}"}%0A#{@customer.primary_phone}&dates=#{Time.now.strftime("%Y%m%d")}T090000/#{Time.now.strftime("%Y%m%d")}T170000", target: :_blank %>
<br/>
<br/>
<%= link_to t(:label_create_estimate), "https://qbo.intuit.com/app/estimate?nameId=#{@customer.id}", target: :_blank %>
<br/>
<br/>
<%= button_to t(:label_edit_customer), edit_customer_path(@customer), method: :get%>

View File

@@ -41,9 +41,6 @@
<td><%= customer.notes %></td>
</tr>
</tbody>
</table>
<div style="float: right;">
<%= button_to t(:label_edit_customer), edit_customer_path(customer), method: :get%>
</div>
</table>
<br/>
<br/>

View File

@@ -3,4 +3,3 @@
<%= submit_tag t(:label_search) %>
<% end %>
<%= button_to t(:label_new_customer), new_customer_path, method: :get%>
<%= button_to(t(:label_sync), qbo_sync_path, method: :get) if User.current.admin?%>

View File

@@ -7,7 +7,22 @@
<h4><%=t(:label_details)%>:</h4>
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
<!-- Customer Info -->
<div class="splitcontent">
<div class="splitcontentleft">
<h4><%=t(:label_customer)%>:</h4>
<%= render :partial => 'customers/details', locals: {customer: @customer} %>
</div>
<div class="splitcontentleft">
<h4><%=t(:label_actions)%>:</h4>
<%= render :partial => 'customers/actions', locals: {customer: @customer} %>
</div>
</div>
<!-- QBO Info -->
<div class="splitcontent">
<div class="splitcontentleft">

View File

@@ -1,6 +1,6 @@
<% if @customer.present? %>
<% @customer.estimates.order(doc_number: :desc).each do |estimate| %>
<% @customer.estimates.order(id: :desc).each do |estimate| %>
<div class="row">
<b><%= link_to "##{estimate.doc_number}", estimate_path(estimate), target: :_blank %></b> <%= estimate.txn_date %>
</div>

View File

@@ -1,6 +1,6 @@
<% if @customer.present? %>
<% @customer.invoices.order(doc_number: :desc).each do |invoice| %>
<% @customer.invoices.order(id: :desc).each do |invoice| %>
<div class="row">
<b><%= link_to "##{invoice.doc_number}", invoice_path(invoice), target: :_blank %></b> <%= invoice.txn_date %>
</div>

13
app/views/qbo/webhook.erb Normal file
View File

@@ -0,0 +1,13 @@
<!--
The MIT License (MIT)
Copyright (c) 2024 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.
-->
<h2>QboController#webhook</h2>

View File

@@ -89,4 +89,7 @@ en:
label_hours: "Hours"
label_oauth2_refresh_token_expires_at: "Refresh Token Expires At"
label_name: "Name"
label_appointment: "Add Appointment"
label_actions: "Actions"
label_create_estimate: "Create Estimate"
label_syncing: "Syncing Quickbooks"

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2024 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:
#
@@ -9,41 +9,46 @@
#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 AddTxnDates < ActiveRecord::Migration[5.1]
def change
add_column :qbo_invoices, :txn_date, :date
add_column :qbo_estimates, :txn_date, :date
begin
add_column :qbo_invoices, :txn_date, :date
add_column :qbo_estimates, :txn_date, :date
reversible do |direction|
direction.up {
break unless Qbo.first
reversible do |direction|
direction.up {
break unless Qbo.first
QboEstimate.reset_column_information
QboInvoice.reset_column_information
QboEstimate.reset_column_information
QboInvoice.reset_column_information
say "Sync Estimates"
say "Sync Estimates"
QboEstimate.sync
QboEstimate.sync
say "Sync Invoices"
say "Sync Invoices"
qbo = Qbo.first
invoices = qbo.perform_authenticated_request do |access_token|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
service.all
end
qbo = Qbo.first
invoices = qbo.perform_authenticated_request do |access_token|
service = Quickbooks::Service::Invoice.new(:company_id => qbo.realm_id, :access_token => access_token)
service.all
end
return unless invoices
return unless invoices
invoices.each { |invoice|
# Load the invoice into the database
qbo_invoice = QboInvoice.find_or_create_by(id: invoice.id)
qbo_invoice.doc_number = invoice.doc_number
qbo_invoice.id = invoice.id
qbo_invoice.customer_id = invoice.customer_ref
qbo_invoice.txn_date = invoice.txn_date
qbo_invoice.save!
invoices.each { |invoice|
# Load the invoice into the database
qbo_invoice = QboInvoice.find_or_create_by(id: invoice.id)
qbo_invoice.doc_number = invoice.doc_number
qbo_invoice.id = invoice.id
qbo_invoice.customer_id = invoice.customer_ref
qbo_invoice.txn_date = invoice.txn_date
qbo_invoice.save!
}
}
}
end
rescue
logger.error "AddTxnDates Failed"
end
end

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -10,19 +10,22 @@
class UpdateVehiclesTrim < ActiveRecord::Migration[5.1]
def change
add_column :vehicles, :doors, :text
add_column :vehicles, :trim, :text
begin
add_column :vehicles, :doors, :text
add_column :vehicles, :trim, :text
reversible do |direction|
direction.up {
# Update local vehicle database by forcing a save, look at before_save
vehicles = Vehicle.all
vehicles.each { |vehicle|
vehicle.save!
}
reversible do |direction|
direction.up {
# Update local vehicle database by forcing a save, look at before_save
vehicles = Vehicle.all
vehicles.each { |vehicle|
vehicle.save!
}
}
end
rescue
logger.error "Failed to update vehicles"
end
end

View File

@@ -22,7 +22,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 '2.1.0'
version '2.1.1'
url 'https://github.com/rickbarrette/redmine_qbo'
author_url 'https://barrettefabrication.com'
settings :default => {'empty' => true}, :partial => 'qbo/settings'

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -15,7 +15,6 @@ module AttachmentsControllerPatch
def self.included(base)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
# check if login is globally required to access the application
def check_if_login_required

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2024 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:
#
@@ -21,7 +21,6 @@ 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 :customer, primary_key: :id
belongs_to :customer_token, primary_key: :id
belongs_to :estimate, primary_key: :id

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -23,7 +23,6 @@ module IssuesControllerPatch
def self.included(base)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
helper Helper
end

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2023 rick barrette
#Copyright (c) 2024 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:
#
@@ -16,10 +16,8 @@ module PdfPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
alias_method :issue_to_pdf, :issue_to_pdf_with_patch
alias_method :issue_to_pdf_with_patch, :issue_to_pdf
end
end

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2017 rick barrette
#Copyright (c) 2024 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:
#
@@ -21,7 +21,6 @@ module ProjectPatch
# Same as typing in the class
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
belongs_to :customer, primary_key: :id
belongs_to :vehicle, primary_key: :id
end

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 rick barrette
#Copyright (c) 2024 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:
#
@@ -20,7 +20,6 @@ module UserPatch
# Same as typing in the class
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
belongs_to :employee, primary_key: :id
end
end