20 Commits
1.1.3 ... 1.1.5

Author SHA1 Message Date
0513763607 Version 1.1.5 2022-03-14 19:41:53 -04:00
b7e3ea9e3d estimate not e 2022-03-14 19:39:36 -04:00
3ea2cd14d1 Fixed accidental removal of qbo prefix 2022-03-14 19:33:20 -04:00
7b7875991f created get_estimate to remove redundant code 2022-03-14 19:28:40 -04:00
b1a106d4d8 No id required 2022-03-14 19:27:31 -04:00
0281d86f1a No id required 2022-03-14 19:25:20 -04:00
2231156873 use estimate_doc_path not a hard coded path 2022-03-14 19:01:20 -04:00
ecc8930bec Try to bill completed issue, TODO handle errors 2022-03-13 17:51:52 -04:00
5814740a5d Allow customer to view estimate 2022-03-13 17:49:58 -04:00
25159c760a FIX - forgot to drop qbo from time_entries.billed 2022-03-13 01:17:18 -05:00
3ff9132acb Updated readme 2022-03-13 01:00:34 -05:00
b5f00f254c Added a copy link button 2022-03-13 00:53:32 -05:00
70f2c473d5 Moved buttons to watcher link location 2022-03-13 00:37:51 -05:00
b3b11d726d Version 1.1.4 2022-03-12 16:03:14 -05:00
f97d5bc731 Moving fat into CustomerToken 2022-03-12 16:01:13 -05:00
49507d06c7 Updated TODO list 2022-03-12 00:03:26 -05:00
5d928c486f Getter convenience method for tokens 2022-03-12 00:01:40 -05:00
0485e9d64c Allow attachment viewing w/ valid customer token 2022-03-11 23:16:23 -05:00
cc0839204e Ignore workspace files 2022-03-11 21:14:12 -05:00
760a85a1da removed link_to user & version 2022-03-10 06:53:05 -05:00
16 changed files with 146 additions and 50 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.bundle
.config
.dockerrc
.vscode
Gemfile.lock

View File

@@ -36,10 +36,12 @@ Use tags Version 1.0.0+ for Redmine 4+ and Version 0.8.1 for Redine 3
## The Install
1. To install, clone this repo into your plugin folder
1. To install, clone this repo into your plugin folder & checkout a tagged version
`git clone git@github.com:rickbarrette/redmine_qbo.git`
then
`git checkout <tag>`
2. Migrate your database
@@ -59,11 +61,8 @@ Use tags Version 1.0.0+ for Redmine 4+ and Version 0.8.1 for Redine 3
Note: After the inital synchronization, this plugin will recieve push notifications via Intuit's webhook service.
## TODO
* Customer link option to allow for temporary sharing of an issue
* Add Setting for Sandbox Mode
* Seperate Vehicles into a seperate plugin (I use redmine for my automotive shop management 😉)
* Make HTML Pretty (It's ugly right now but it works)
* Intergrate Customer Search into Redmine Search
* MORE Stuff as I make it up...
## License

View File

@@ -156,9 +156,11 @@ class CustomersController < ApplicationController
User.current = User.find_by lastname: 'Anonymous'
@token = CustomerToken.where("token = ? and expires_at > ?", params[:token], Time.now)
@token = @token.first
if @token
@token = CustomerToken.find_by token: params[:token]
begin
@token.destroy if @token.expired?
raise "Token Expired" if @token.destroyed
session[:token] = @token.token
@issue = Issue.find @token.issue_id
@journals = @issue.journals.
@@ -179,7 +181,7 @@ class CustomersController < ApplicationController
@priorities = IssuePriority.active
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@relation = IssueRelation.new
else
rescue
render_403
end
end

View File

@@ -12,17 +12,23 @@ class EstimateController < ApplicationController
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
#
def show
e = Estimate.find_by_id(params[:id]) if params[:id]
e = Estimate.find_by_doc_number(params[:search]) if params[:search]
estimate = get_estimate
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
redirect_to :back, :flash => { :error => "Estimate not found" }
end
@@ -32,11 +38,10 @@ class EstimateController < ApplicationController
# Downloads estimate by document number
#
def doc
e = Estimate.find_by_doc_number(params[:id]) if params[:id]
e = Estimate.find_by_doc_number(params[:search]) if params[:search]
estimate = get_estimate
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
redirect_to :back, :flash => { :error => "Estimate not found" }
end

View File

@@ -55,7 +55,7 @@ class QboController < ApplicationController
qbo.expire = 1.hour.from_now.utc
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
redirect_to plugin_settings_path(:redmine_qbo), :flash => { :error => "Error" }
end

View File

@@ -11,17 +11,51 @@
class CustomerToken < ActiveRecord::Base
unloadable
has_many :issues
validates_presence_of :expires_at, :issue_id
before_create :generate_token
validates_presence_of :issue_id
before_create :generate_token, :generate_expire_date
attr_accessor :destroyed
after_destroy :mark_as_destroyed
OAUTH_CONSUMER_SECRET = Setting.plugin_redmine_qbo['settingsOAuthConsumerSecret'] || 'CONFIGURE__' + SecureRandom.uuid
# generates a random token using the plugin setting settingsOAuthConsumerSecret for salt
def generate_token
self.token = SecureRandom.base64(15).tr('+/=lIO0', OAUTH_CONSUMER_SECRET)
end
def remove_expired_tokens
CustomerToken.where("expires_at < ?", Time.now).destroy_all
# generates an expiring date
def generate_expire_date
self.expires_at = Time.now + 1.month
end
# set destroyed flag
def mark_as_destroyed
self.destroyed = true
end
# purge expired tokens
def self.remove_expired_tokens
where("expires_at < ?", Time.now).destroy_all
end
# has the token expired?
def expired?
self.expires_at < Time.now
end
# Getter convenience method for tokens
def self.get_token(issue)
# check to see if token exists & if it is expired
token = find_by_issue_id issue.id
unless token.nil?
return token unless token.expired?
# remove expired tokens
token.destroy
end
# only create new token if we have an issue to attach it to
return create(:issue_id => issue.id) if User.current.logged?
end
end

View File

@@ -1,3 +1,5 @@
<p style="float: right;"> <%= copy_object_url_link(request.url) %> </p>
<h2><%= issue_heading(@issue) %></h2>
<div class="<%= @issue.css_classes %> details">
@@ -20,13 +22,13 @@
rows.left l(:field_status), @issue.status.name, :class => 'status'
rows.left l(:field_priority), @issue.priority.name, :class => 'priority'
unless @issue.disabled_core_fields.include?('assigned_to_id')
rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? link_to_user(@issue.assigned_to) : "-"), :class => 'assigned-to'
rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s.html_safe + (@issue.assigned_to ? @issue.assigned_to : "-"), :class => 'assigned-to'
end
unless @issue.disabled_core_fields.include?('category_id') || (@issue.category.nil? && @issue.project.issue_categories.none?)
rows.left l(:field_category), (@issue.category ? @issue.category.name : "-"), :class => 'category'
end
unless @issue.disabled_core_fields.include?('fixed_version_id') || (@issue.fixed_version.nil? && @issue.assignable_versions.none?)
rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
rows.left l(:field_fixed_version), (@issue.fixed_version ? @issue.fixed_version : "-"), :class => 'fixed-version'
end
unless @issue.disabled_core_fields.include?('start_date')
rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'

View File

@@ -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" %>
<%= submit_tag t(:label_search), :formtarget => "_blank" %>
<% end %>

View File

@@ -20,7 +20,7 @@ post 'qbo/webhook', :to => 'qbo#webhook'
# Estimate & Invoice PDF
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
#manual billing

View File

@@ -0,0 +1,15 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 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 RemoveQboTimeEntries < ActiveRecord::Migration[5.1]
def change
rename_column :time_entries, :qbo_billed, :billed
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 '1.1.3'
version '1.1.5'
url 'https://github.com/rickbarrette/redmine_qbo'
author_url 'http://rickbarrette.org'
settings :default => {'empty' => true}, :partial => 'qbo/settings'

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT)
#
#Copyright (c) 2017 rick barrette
#Copyright (c) 2022 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:
#
@@ -12,26 +12,31 @@ require_dependency 'attachments_controller'
module AttachmentsControllerPatch
def self.included(base) # :nodoc:
base.extend(ClassMethods)
def self.included(base)
base.send(:include, InstanceMethods)
# Same as typing in the class
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
skip_before_action :read_authorize
end
# check if login is globally required to access the application
def check_if_login_required
# no check needed if user is already logged in
return true if User.current.logged?
# Pull up the attachmet, & verify if we have a valid token for the Issue
attachment = Attachment.find(params[:id])
token = CustomerToken.where("token = ? and expires_at > ?", session[:token], Time.now)
token = token.first
unless token.nil?
return true if token.issue_id == attachment.container_id
end
module ClassMethods
require_login if Setting.login_required?
end
end
module InstanceMethods
end
end
# Add module to AttachmentsController

View File

@@ -99,7 +99,7 @@ module IssuePatch
# Create a shareable link for a customer
def share_token
CustomerToken.create(:expires_at => Time.now + 1.month, :issue_id => id)
CustomerToken.get_token self
end
end

View File

@@ -0,0 +1,35 @@
#The MIT License (MIT)
#
#Copyright (c) 2022 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.
require_dependency 'issues_controller'
module IssuesControllerPatch
module Helper
def watcher_link(issue, user)
link = +''
link << link_to(I18n.t(:label_bill_time), bill_path( issue.id ), method: :get, class: 'icon icon-email-add') if user.admin?
link << link_to(I18n.t(:label_share), share_path( issue.id ), method: :get, target: :_blank, class: 'icon icon-shared') if user.logged?
link.html_safe + super
end
end
def self.included(base)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
helper Helper
end
end
end
# Add module to IssuessController
IssuesController.send(:include, IssuesControllerPatch)

View File

@@ -19,7 +19,11 @@ class IssuesSaveHookListener < Redmine::Hook::ViewListener
# Called After Issue Saved
def controller_issues_edit_after_save(context={})
issue = context[:issue]
begin
issue.bill_time if issue.status.is_closed?
rescue
# TODO flash[:error] = "Unable to bill, check QBO Auth"
end
end
end

View File

@@ -60,11 +60,4 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
})
end
# Display Buttons under the issue above subtasks
def view_issues_show_description_bottom(context={})
bill_button = button_to I18n.t(:label_bill_time), bill_path( context[:issue].id ), method: :get if User.current.admin?
share_button = button_to I18n.t(:label_share), share_path( context[:issue].id ), method: :get if User.current.logged?
return "<br/> #{bill_button} #{share_button}"
end
end