11 Commits
1.1.3 ... 1.1.4

11 changed files with 126 additions and 39 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.bundle .bundle
.config .config
.dockerrc
.vscode
Gemfile.lock 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 ## 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` `git clone git@github.com:rickbarrette/redmine_qbo.git`
then then
`git checkout <tag>` `git checkout <tag>`
2. Migrate your database 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. Note: After the inital synchronization, this plugin will recieve push notifications via Intuit's webhook service.
## TODO ## TODO
* Customer link option to allow for temporary sharing of an issue
* Add Setting for Sandbox Mode * Add Setting for Sandbox Mode
* Seperate Vehicles into a seperate plugin (I use redmine for my automotive shop management 😉) * 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... * MORE Stuff as I make it up...
## License ## License

View File

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

View File

@@ -11,17 +11,51 @@
class CustomerToken < ActiveRecord::Base class CustomerToken < ActiveRecord::Base
unloadable unloadable
has_many :issues has_many :issues
validates_presence_of :expires_at, :issue_id validates_presence_of :issue_id
before_create :generate_token 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 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 def generate_token
self.token = SecureRandom.base64(15).tr('+/=lIO0', OAUTH_CONSUMER_SECRET) self.token = SecureRandom.base64(15).tr('+/=lIO0', OAUTH_CONSUMER_SECRET)
end end
def remove_expired_tokens # generates an expiring date
CustomerToken.where("expires_at < ?", Time.now).destroy_all 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
end end

View File

@@ -1,3 +1,5 @@
<p style="float: right;"> <%= copy_object_url_link(request.url) %> </p>
<h2><%= issue_heading(@issue) %></h2> <h2><%= issue_heading(@issue) %></h2>
<div class="<%= @issue.css_classes %> details"> <div class="<%= @issue.css_classes %> details">
@@ -20,13 +22,13 @@
rows.left l(:field_status), @issue.status.name, :class => 'status' rows.left l(:field_status), @issue.status.name, :class => 'status'
rows.left l(:field_priority), @issue.priority.name, :class => 'priority' rows.left l(:field_priority), @issue.priority.name, :class => 'priority'
unless @issue.disabled_core_fields.include?('assigned_to_id') 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 end
unless @issue.disabled_core_fields.include?('category_id') || (@issue.category.nil? && @issue.project.issue_categories.none?) 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' rows.left l(:field_category), (@issue.category ? @issue.category.name : "-"), :class => 'category'
end end
unless @issue.disabled_core_fields.include?('fixed_version_id') || (@issue.fixed_version.nil? && @issue.assignable_versions.none?) 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 end
unless @issue.disabled_core_fields.include?('start_date') unless @issue.disabled_core_fields.include?('start_date')
rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date' rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'

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' 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.3' version '1.1.4'
url 'https://github.com/rickbarrette/redmine_qbo' url 'https://github.com/rickbarrette/redmine_qbo'
author_url 'http://rickbarrette.org' author_url 'http://rickbarrette.org'
settings :default => {'empty' => true}, :partial => 'qbo/settings' settings :default => {'empty' => true}, :partial => 'qbo/settings'

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT) #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: #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,27 +12,32 @@ require_dependency 'attachments_controller'
module AttachmentsControllerPatch module AttachmentsControllerPatch
def self.included(base) # :nodoc: def self.included(base)
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
# Same as typing in the class
base.class_eval do base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development 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
# 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
require_login if Setting.login_required?
end
skip_before_action :read_authorize
end end
end end
module ClassMethods end
end
module InstanceMethods
end
end
# Add module to AttachmentsController # Add module to AttachmentsController
AttachmentsController.send(:include, AttachmentsControllerPatch) AttachmentsController.send(:include, AttachmentsControllerPatch)

View File

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

@@ -59,12 +59,5 @@ class IssuesShowHookListener < Redmine::Hook::ViewListener
} }
}) })
end 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 end