mirror of
https://github.com/rickbarrette/redmine_qbo.git
synced 2025-11-09 01:14:23 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7839116134 | |||
| b3a809ab1c | |||
| 3a0e58c3da | |||
| 26433c9020 | |||
| 6dbf84f401 | |||
| 3220ff728f | |||
| d8d1942673 | |||
| 8e329b2dd2 | |||
| 3622f8cad7 | |||
| 0513763607 | |||
| b7e3ea9e3d | |||
| 3ea2cd14d1 | |||
| 7b7875991f | |||
| b1a106d4d8 | |||
| 0281d86f1a | |||
| 2231156873 | |||
| ecc8930bec | |||
| 5814740a5d |
2
Gemfile
2
Gemfile
@@ -1,7 +1,7 @@
|
|||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
gem 'quickbooks-ruby'
|
gem 'quickbooks-ruby'
|
||||||
gem 'oauth2', '1.4.7'
|
gem 'oauth2'
|
||||||
gem 'roxml'
|
gem 'roxml'
|
||||||
gem 'nhtsa_vin'
|
gem 'nhtsa_vin'
|
||||||
gem 'will_paginate'
|
gem 'will_paginate'
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ The goal of this project is to allow Redmine to connect with Quickbooks Online t
|
|||||||
|
|
||||||
Note: Although the core functionality is complete, this project is still under development & the master branch may be unstable. Tags should be stable and are recommended
|
Note: Although the core functionality is complete, this project is still under development & the master branch may be unstable. Tags should be stable and are recommended
|
||||||
|
|
||||||
Use tags Version 1.0.0+ for Redmine 4+ and Version 0.8.1 for Redine 3
|
Use tags for the following Redmine Versions
|
||||||
|
* Version 2.0.0+ for Redmine 5+
|
||||||
|
* Version 1.0.0+ for Redmine 4+
|
||||||
|
* Version 0.8.1 for Redine 3
|
||||||
|
|
||||||
#### Features
|
#### Features
|
||||||
* Issues can be assigned to a Customer via drop down in the edit Issue form
|
* Issues can be assigned to a Customer via drop down in the edit Issue form
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ class CustomersController < ApplicationController
|
|||||||
@billing_address = address_to_s(@customer.billing_address)
|
@billing_address = address_to_s(@customer.billing_address)
|
||||||
@shipping_address = address_to_s(@customer.shipping_address)
|
@shipping_address = address_to_s(@customer.shipping_address)
|
||||||
@closed_issues = (@issues - @issues.open)
|
@closed_issues = (@issues - @issues.open)
|
||||||
|
@hours = 0
|
||||||
|
@closed_hours = 0
|
||||||
|
@issues.open.each { |i| @hours+= i.total_spent_hours }
|
||||||
|
@closed_issues.each { |i| @closed_hours+= i.total_spent_hours }
|
||||||
rescue
|
rescue
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
@@ -214,14 +218,14 @@ class CustomersController < ApplicationController
|
|||||||
# format a quickbooks address to a human readable string
|
# format a quickbooks address to a human readable string
|
||||||
def address_to_s (address)
|
def address_to_s (address)
|
||||||
return if address.nil?
|
return if address.nil?
|
||||||
string = address.line1
|
string = address.line1 if address.line1
|
||||||
string << "\n" + address.line2 if address.line2
|
string << "\n" + address.line2 if address.line2
|
||||||
string << "\n" + address.line3 if address.line3
|
string << "\n" + address.line3 if address.line3
|
||||||
string << "\n" + address.line4 if address.line4
|
string << "\n" + address.line4 if address.line4
|
||||||
string << "\n" + address.line5 if address.line5
|
string << "\n" + address.line5 if address.line5
|
||||||
string << " " + address.city
|
string << " " + address.city if address.city
|
||||||
string << ", " + address.country_sub_division_code
|
string << ", " + address.country_sub_division_code if address.country_sub_division_code
|
||||||
string << " " + address.postal_code
|
string << " " + address.postal_code if address.postal_code
|
||||||
return string
|
return string
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -12,17 +12,23 @@ class EstimateController < ApplicationController
|
|||||||
|
|
||||||
include AuthHelper
|
include AuthHelper
|
||||||
|
|
||||||
before_action :require_user
|
before_action :require_user, :unless => proc {|c| session[:token].nil? }
|
||||||
|
skip_before_action :verify_authenticity_token, :check_if_login_required, :unless => proc {|c| session[:token].nil? }
|
||||||
|
|
||||||
|
def get_estimate
|
||||||
|
estimate = Estimate.find_by_id(params[:id]) if params[:id]
|
||||||
|
estimate = Estimate.find_by_doc_number(params[:search]) if params[:search]
|
||||||
|
return estimate
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Downloads and forwards the estimate pdf
|
# Downloads and forwards the estimate pdf
|
||||||
#
|
#
|
||||||
def show
|
def show
|
||||||
e = Estimate.find_by_id(params[:id]) if params[:id]
|
estimate = get_estimate
|
||||||
e = Estimate.find_by_doc_number(params[:search]) if params[:search]
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
send_data e.pdf, filename: "estimate #{e.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
send_data estimate.pdf, filename: "estimate #{estimate.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
||||||
rescue
|
rescue
|
||||||
redirect_to :back, :flash => { :error => "Estimate not found" }
|
redirect_to :back, :flash => { :error => "Estimate not found" }
|
||||||
end
|
end
|
||||||
@@ -32,11 +38,10 @@ class EstimateController < ApplicationController
|
|||||||
# Downloads estimate by document number
|
# Downloads estimate by document number
|
||||||
#
|
#
|
||||||
def doc
|
def doc
|
||||||
e = Estimate.find_by_doc_number(params[:id]) if params[:id]
|
estimate = get_estimate
|
||||||
e = Estimate.find_by_doc_number(params[:search]) if params[:search]
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
send_data e.pdf, filename: "estimate #{e.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
send_data estimate.pdf, filename: "estimate #{estimate.doc_number}.pdf", :disposition => 'inline', :type => "application/pdf"
|
||||||
rescue
|
rescue
|
||||||
redirect_to :back, :flash => { :error => "Estimate not found" }
|
redirect_to :back, :flash => { :error => "Estimate not found" }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class QboController < ApplicationController
|
|||||||
qbo.expire = 1.hour.from_now.utc
|
qbo.expire = 1.hour.from_now.utc
|
||||||
|
|
||||||
if qbo.save!
|
if qbo.save!
|
||||||
redirect_to sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
|
redirect_to qbo_sync_path, :flash => { :notice => "Successfully connected to Quickbooks" }
|
||||||
else
|
else
|
||||||
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
|
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Customer < ActiveRecord::Base
|
|||||||
|
|
||||||
# returns a human readable string
|
# returns a human readable string
|
||||||
def to_s
|
def to_s
|
||||||
return name
|
return "#{self[:name]} - #{phone_number.split(//).last(4).join unless phone_number.nil?}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convenience Method
|
# Convenience Method
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ class Estimate < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
# process an estimate into the database
|
# process an estimate into the database
|
||||||
def self.process_estimate(estimate)
|
def self.process_estimate(qbo_estimate)
|
||||||
logger.info "Processing estimate #{estimate.id}"
|
logger.info "Processing estimate #{qbo_estimate.id}"
|
||||||
estimate = find_or_create_by(id: estimate.id)
|
estimate = find_or_create_by(id: qbo_estimate.id)
|
||||||
estimate.doc_number = estimate.doc_number
|
estimate.doc_number = qbo_estimate.doc_number
|
||||||
estimate.customer_id = estimate.customer_ref.value
|
estimate.customer_id = qbo_estimate.customer_ref.value
|
||||||
estimate.id = estimate.id
|
estimate.id = qbo_estimate.id
|
||||||
estimate.txn_date = estimate.txn_date
|
estimate.txn_date = qbo_estimate.txn_date
|
||||||
estimate.save!
|
estimate.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -70,26 +70,26 @@ class Invoice < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
# processes the invoice into the database
|
# processes the invoice into the database
|
||||||
def self.process_invoice(invoice)
|
def self.process_invoice(i)
|
||||||
logger.info "Processing invoice #{invoice.id}"
|
logger.info "Processing invoice #{i.id}"
|
||||||
|
|
||||||
# Load the invoice into the database
|
# Load the invoice into the database
|
||||||
invoice = Invoice.find_or_create_by(id: invoice.id)
|
invoice = Invoice.find_or_create_by(id: i.id)
|
||||||
invoice.doc_number = invoice.doc_number
|
invoice.doc_number = i.doc_number
|
||||||
invoice.id = invoice.id
|
invoice.id = i.id
|
||||||
invoice.customer_id = invoice.customer_ref
|
invoice.customer_id = i.customer_ref
|
||||||
invoice.txn_date = invoice.txn_date
|
invoice.txn_date = i.txn_date
|
||||||
invoice.save!
|
invoice.save!
|
||||||
|
|
||||||
# Scan the private notes for hashtags and attach to the applicable issues
|
# Scan the private notes for hashtags and attach to the applicable issues
|
||||||
if not invoice.private_note.nil?
|
if not i.private_note.nil?
|
||||||
invoice.private_note.scan(/#(\w+)/).flatten.each { |issue|
|
i.private_note.scan(/#(\w+)/).flatten.each { |issue|
|
||||||
attach_to_issue(Issue.find_by_id(issue.to_i), invoice)
|
attach_to_issue(Issue.find_by_id(issue.to_i), invoice)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Scan the line items for hashtags and attach to the applicable issues
|
# Scan the line items for hashtags and attach to the applicable issues
|
||||||
invoice.line_items.each { |line|
|
i.line_items.each { |line|
|
||||||
if line.description
|
if line.description
|
||||||
line.description.scan(/#(\w+)/).flatten.each { |issue|
|
line.description.scan(/#(\w+)/).flatten.each { |issue|
|
||||||
attach_to_issue(Issue.find_by_id(issue.to_i), invoice)
|
attach_to_issue(Issue.find_by_id(issue.to_i), invoice)
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<h3><%=@issues.open.count%> <%=t(:label_open_issues)%>:</h3>
|
<h3><%=@issues.open.count%> <%=t(:label_open_issues)%> - <%=@hours.round(1)%> <%=t(:label_hours)%></h3>
|
||||||
<%= render :partial => 'issues/list_simple', locals: {issues: @issues.open} %>
|
<%= render :partial => 'issues/list_simple', locals: {issues: @issues.open} %>
|
||||||
|
|
||||||
<h3><%=@closed_issues.count%> <%=t(:label_closed_issues)%>:</h3>
|
<h3><%=@closed_issues.count%> <%=t(:label_closed_issues)%> - <%= @closed_hours.round(1)%> <%=t(:label_hours)%></h3>
|
||||||
<%= render :partial => 'issues/list_simple', locals: {issues: @closed_issues} %>
|
<%= render :partial => 'issues/list_simple', locals: {issues: @closed_issues} %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<%= form_tag("/qbo/estimate/doc", :method => "get", id: "est-search-form") do %>
|
<%= form_tag(estimate_doc_path, :method => "get") do %>
|
||||||
<%= text_field_tag :search, params[:search], placeholder: t(:label_search_estimates), :autocomplete => "off" %>
|
<%= text_field_tag :search, params[:search], placeholder: t(:label_search_estimates), :autocomplete => "off" %>
|
||||||
<%= submit_tag t(:label_search), :formtarget => "_blank" %>
|
<%= submit_tag t(:label_search), :formtarget => "_blank" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -87,4 +87,5 @@ en:
|
|||||||
label_billed_success: "Successfully Billed "
|
label_billed_success: "Successfully Billed "
|
||||||
label_billing_error: "Cannot bill without a customer assigned"
|
label_billing_error: "Cannot bill without a customer assigned"
|
||||||
label_qbo_sync_success: "Successfully synced to Quickbooks"
|
label_qbo_sync_success: "Successfully synced to Quickbooks"
|
||||||
|
label_hours: "Hours"
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ post 'qbo/webhook', :to => 'qbo#webhook'
|
|||||||
|
|
||||||
# Estimate & Invoice PDF
|
# Estimate & Invoice PDF
|
||||||
get 'estimates/:id', :to => 'estimate#show', as: :estimate
|
get 'estimates/:id', :to => 'estimate#show', as: :estimate
|
||||||
get 'estimates/doc/:id', :to => 'estimate#doc', as: :estimate_doc
|
get 'estimates/doc/', :to => 'estimate#doc', as: :estimate_doc
|
||||||
get 'invoices/:id', :to => 'invoice#show', as: :invoice
|
get 'invoices/:id', :to => 'invoice#show', as: :invoice
|
||||||
|
|
||||||
#manual billing
|
#manual billing
|
||||||
|
|||||||
8
init.rb
8
init.rb
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2023 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:
|
#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:
|
||||||
#
|
#
|
||||||
@@ -22,11 +22,11 @@ Redmine::Plugin.register :redmine_qbo do
|
|||||||
name 'Redmine Quickbooks Online plugin'
|
name 'Redmine Quickbooks Online plugin'
|
||||||
author 'Rick Barrette'
|
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'
|
description 'This is a plugin for Redmine to intergrate with Quickbooks Online to allow for seamless intergration CRM and invoicing of completed issues'
|
||||||
version '1.1.4'
|
version '2.0.0'
|
||||||
url 'https://github.com/rickbarrette/redmine_qbo'
|
url 'https://github.com/rickbarrette/redmine_qbo'
|
||||||
author_url 'http://rickbarrette.org'
|
author_url 'https://barrettefabrication.com'
|
||||||
settings :default => {'empty' => true}, :partial => 'qbo/settings'
|
settings :default => {'empty' => true}, :partial => 'qbo/settings'
|
||||||
requires_redmine :version_or_higher => '4.0.0'
|
requires_redmine :version_or_higher => '5.1.0'
|
||||||
|
|
||||||
# Add safe attributes for core models
|
# Add safe attributes for core models
|
||||||
Issue.safe_attributes 'customer_id'
|
Issue.safe_attributes 'customer_id'
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
|
|||||||
# Called After Issue Saved
|
# Called After Issue Saved
|
||||||
def controller_issues_edit_after_save(context={})
|
def controller_issues_edit_after_save(context={})
|
||||||
issue = context[:issue]
|
issue = context[:issue]
|
||||||
|
begin
|
||||||
issue.bill_time if issue.status.is_closed?
|
issue.bill_time if issue.status.is_closed?
|
||||||
|
rescue
|
||||||
|
# TODO flash[:error] = "Unable to bill, check QBO Auth"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#The MIT License (MIT)
|
#The MIT License (MIT)
|
||||||
#
|
#
|
||||||
#Copyright (c) 2022 rick barrette
|
#Copyright (c) 2023 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:
|
#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,7 +11,7 @@
|
|||||||
require_dependency 'redmine/export/pdf'
|
require_dependency 'redmine/export/pdf'
|
||||||
require_dependency 'redmine/export/pdf/issues_pdf_helper'
|
require_dependency 'redmine/export/pdf/issues_pdf_helper'
|
||||||
|
|
||||||
module IssuesPdfHelperPatch
|
module PdfPatch
|
||||||
|
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.send(:include, InstanceMethods)
|
base.send(:include, InstanceMethods)
|
||||||
@@ -256,4 +256,4 @@ module IssuesPdfHelperPatch
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Redmine::Export::PDF::IssuesPdfHelper.send(:include, IssuesPdfHelperPatch)
|
Redmine::Export::PDF::IssuesPdfHelper.send(:include, PdfPatch)
|
||||||
|
|||||||
Reference in New Issue
Block a user