mirror of
https://github.com/rickbarrette/redmine_qbo.git
synced 2026-04-02 16:21:58 -04:00
Compare commits
5 Commits
2026.3.9
...
3e6650ee65
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e6650ee65 | |||
| c2d0e5c702 | |||
| a4f461fd4d | |||
| 3e81d2840a | |||
| c9a5dc20f9 |
@@ -32,38 +32,36 @@ class CustomersController < ApplicationController
|
||||
|
||||
autocomplete :customer, :name, full: true, extra_data: [:id]
|
||||
|
||||
def address_to_s(address)
|
||||
return if address.nil?
|
||||
|
||||
lines = [
|
||||
address.line1,
|
||||
address.line2,
|
||||
address.line3,
|
||||
address.line4,
|
||||
address.line5
|
||||
].compact_blank
|
||||
|
||||
city_line = [
|
||||
address.city,
|
||||
address.country_sub_division_code,
|
||||
address.postal_code
|
||||
].compact_blank.join(" ")
|
||||
|
||||
lines << city_line unless city_line.blank?
|
||||
|
||||
lines.join("\n")
|
||||
end
|
||||
|
||||
def add_customer
|
||||
global_check_permission(:add_customers)
|
||||
end
|
||||
|
||||
def allowed_params
|
||||
params.require(:customer).permit(:name, :email, :primary_phone, :mobile_phone, :phone_number, :notes)
|
||||
end
|
||||
|
||||
# getter method for a customer's invoices
|
||||
# used for customer autocomplete field / issue form
|
||||
def filter_invoices_by_customer
|
||||
@filtered_invoices = Invoice.all.where(customer_id: params[:selected_customer])
|
||||
end
|
||||
|
||||
# getter method for a customer's estimates
|
||||
# used for customer autocomplete field / issue form
|
||||
def filter_estimates_by_customer
|
||||
@filtered_estimates = Estimate.all.where(customer_id: params[:selected_customer])
|
||||
end
|
||||
|
||||
# display a list of all customers
|
||||
def index
|
||||
if params[:search]
|
||||
@customers = Customer.search(params[:search]).order(:name).paginate(page: params[:page])
|
||||
if only_one_non_zero?(@customers)
|
||||
redirect_to @customers.first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# initialize a new customer
|
||||
def new
|
||||
@customer = Customer.new
|
||||
end
|
||||
|
||||
# create a new customer
|
||||
def create
|
||||
@customer = Customer.new(allowed_params)
|
||||
@customer.save
|
||||
@@ -76,7 +74,79 @@ class CustomersController < ApplicationController
|
||||
redirect_to new_customer_path
|
||||
end
|
||||
|
||||
# display a specific customer
|
||||
def edit
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
return render_404 unless @customer
|
||||
rescue => e
|
||||
log "Failed to edit customer"
|
||||
flash[:error] = e.message
|
||||
render_404
|
||||
end
|
||||
|
||||
def filter_estimates_by_customer
|
||||
@filtered_estimates = Estimate.all.where(customer_id: params[:selected_customer])
|
||||
end
|
||||
|
||||
def filter_invoices_by_customer
|
||||
@filtered_invoices = Invoice.all.where(customer_id: params[:selected_customer])
|
||||
end
|
||||
|
||||
def index
|
||||
if params[:search]
|
||||
@customers = Customer.search(params[:search]).order(:name).paginate(page: params[:page])
|
||||
if only_one_non_zero?(@customers)
|
||||
redirect_to @customers.first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_issue_data
|
||||
@journals = @issue.journals.preload(:details).preload(user: :email_address).reorder(:created_on, :id).to_a
|
||||
|
||||
@journals.each_with_index { |j, i| j.indice = i + 1 }
|
||||
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
|
||||
Journal.preload_journals_details_custom_fields(@journals)
|
||||
@journals.select! { |journal| journal.notes? || journal.visible_details.any? }
|
||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
@changesets = @issue.changesets.visible.preload(:repository, :user).to_a
|
||||
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
@relations = @issue.relations.select { |r| r.other_issue(@issue)&.visible? }
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@priorities = IssuePriority.active
|
||||
@time_entry = TimeEntry.new(issue: @issue, project: @issue.project)
|
||||
@relation = IssueRelation.new
|
||||
end
|
||||
|
||||
def log(msg)
|
||||
Rails.logger.info "[CustomersController] #{msg}"
|
||||
end
|
||||
|
||||
def new
|
||||
@customer = Customer.new
|
||||
end
|
||||
|
||||
def only_one_non_zero?(array)
|
||||
found_non_zero = false
|
||||
array.each do |val|
|
||||
if val != 0
|
||||
return false if found_non_zero
|
||||
found_non_zero = true
|
||||
end
|
||||
end
|
||||
found_non_zero
|
||||
end
|
||||
|
||||
def share
|
||||
issue = Issue.find(params[:id])
|
||||
token = issue.share_token
|
||||
redirect_to view_path(token.token)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
flash[:error] = t(:notice_issue_not_found)
|
||||
render_404
|
||||
end
|
||||
|
||||
def show
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
return render_404 unless @customer
|
||||
@@ -109,17 +179,11 @@ class CustomersController < ApplicationController
|
||||
render_404
|
||||
end
|
||||
|
||||
# return an HTML form for editing a customer
|
||||
def edit
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
return render_404 unless @customer
|
||||
rescue => e
|
||||
log "Failed to edit customer"
|
||||
flash[:error] = e.message
|
||||
render_404
|
||||
def sync
|
||||
Customer.sync
|
||||
redirect_to :home, flash: { notice: I18n.t(:label_syncing) }
|
||||
end
|
||||
|
||||
# update a specific customer
|
||||
def update
|
||||
@customer = Customer.find_by_id(params[:id])
|
||||
@customer.update(allowed_params)
|
||||
@@ -131,108 +195,21 @@ class CustomersController < ApplicationController
|
||||
redirect_to edit_customer_path
|
||||
end
|
||||
|
||||
# creates new customer view tokens, removes expired tokens & redirects to newly created customer view with new token.
|
||||
def share
|
||||
issue = Issue.find(params[:id])
|
||||
token = issue.share_token
|
||||
redirect_to view_path(token.token)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
flash[:error] = t(:notice_issue_not_found)
|
||||
render_404
|
||||
end
|
||||
|
||||
# displays an issue for a customer with a provided security CustomerToken
|
||||
def view
|
||||
User.current = User.anonymous
|
||||
|
||||
# Load only active, non-expired token
|
||||
@token = CustomerToken.active.find_by(token: params[:token])
|
||||
return render_403 unless @token
|
||||
|
||||
# Load associated issue
|
||||
@issue = @token.issue
|
||||
return render_403 unless @issue
|
||||
|
||||
# Optional: enforce token belongs to the issue's customer
|
||||
return render_403 unless @issue.customer_id == @token.issue.customer_id
|
||||
|
||||
# Store token in session for subsequent requests if needed
|
||||
session[:token] = @token.token
|
||||
|
||||
load_issue_data
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_403
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_issue_data
|
||||
@journals = @issue.journals.preload(:details).preload(user: :email_address).reorder(:created_on, :id).to_a
|
||||
|
||||
@journals.each_with_index { |j, i| j.indice = i + 1 }
|
||||
@journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
|
||||
Journal.preload_journals_details_custom_fields(@journals)
|
||||
@journals.select! { |journal| journal.notes? || journal.visible_details.any? }
|
||||
@journals.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
@changesets = @issue.changesets.visible.preload(:repository, :user).to_a
|
||||
@changesets.reverse! if User.current.wants_comments_in_reverse_order?
|
||||
|
||||
@relations = @issue.relations.select { |r| r.other_issue(@issue)&.visible? }
|
||||
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
|
||||
@priorities = IssuePriority.active
|
||||
@time_entry = TimeEntry.new(issue: @issue, project: @issue.project)
|
||||
@relation = IssueRelation.new
|
||||
end
|
||||
|
||||
# redmine permission - add customers
|
||||
def add_customer
|
||||
global_check_permission(:add_customers)
|
||||
end
|
||||
|
||||
# redmine permission - view customers
|
||||
def view_customer
|
||||
global_check_permission(:view_customers)
|
||||
end
|
||||
|
||||
# checks to see if there is only one item in an array
|
||||
# @return true if array only has one item
|
||||
def only_one_non_zero?( array )
|
||||
found_non_zero = false
|
||||
array.each do |val|
|
||||
if val!=0
|
||||
return false if found_non_zero
|
||||
found_non_zero = true
|
||||
end
|
||||
end
|
||||
found_non_zero
|
||||
end
|
||||
|
||||
# format a quickbooks address to a human readable string
|
||||
def address_to_s(address)
|
||||
return if address.nil?
|
||||
|
||||
lines = [
|
||||
address.line1,
|
||||
address.line2,
|
||||
address.line3,
|
||||
address.line4,
|
||||
address.line5
|
||||
].compact_blank
|
||||
|
||||
city_line = [
|
||||
address.city,
|
||||
address.country_sub_division_code,
|
||||
address.postal_code
|
||||
].compact_blank.join(" ")
|
||||
|
||||
lines << city_line unless city_line.blank?
|
||||
|
||||
lines.join("\n")
|
||||
end
|
||||
|
||||
def log(msg)
|
||||
Rails.logger.info "[CustomersController] #{msg}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -72,6 +72,13 @@ class EstimateController < ApplicationController
|
||||
redirect_back fallback_location: root_path, flash: { error: I18n.t(:notice_estimate_not_found) }
|
||||
end
|
||||
|
||||
def sync
|
||||
Estimate.sync
|
||||
redirect_to :home, flash: { notice: I18n.t(:label_syncing) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Logs messages with a consistent prefix for easier debugging.
|
||||
def log(msg)
|
||||
Rails.logger.info "[EstimateController] #{msg}"
|
||||
|
||||
@@ -27,6 +27,11 @@ class InvoiceController < ApplicationController
|
||||
redirect_back fallback_location: root_path, flash: { error: I18n.t(:notice_invoice_not_found) }
|
||||
end
|
||||
|
||||
def sync
|
||||
Invoice.sync
|
||||
redirect_to :home, flash: { notice: I18n.t(:label_syncing) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Logs messages with a consistent prefix for easier debugging.
|
||||
|
||||
@@ -33,8 +33,10 @@ class QboSyncDispatcher
|
||||
# Allow other plugins to add addtional sync jobs via Hooks
|
||||
Redmine::Hook.call_hook( :qbo_full_sync ).each do |context|
|
||||
next unless context
|
||||
jobs.push context
|
||||
log "Added additionals QBO Sync Job for #{context.to_s}"
|
||||
Array(context).each do |entity|
|
||||
jobs.push(entity)
|
||||
log "Added additional QBO Sync Job for #{entity.to_s}"
|
||||
end
|
||||
end
|
||||
|
||||
jobs.each { |job| QboSyncJob.perform_later(entity: job, full_sync: full_sync) }
|
||||
|
||||
@@ -47,8 +47,10 @@ class WebhookProcessJob < ActiveJob::Base
|
||||
# Allow other plugins to add addtional qbo entities via Hooks
|
||||
Redmine::Hook.call_hook( :qbo_additional_entities ).each do |context|
|
||||
next unless context
|
||||
entities.push context
|
||||
log "Added additional QBO entities: #{context}"
|
||||
Array(context).each do |entity|
|
||||
jobs.push(entity)
|
||||
log "Added additional QBO entity #{entity}"
|
||||
end
|
||||
end
|
||||
return unless entities.include?(name)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ get 'qbo/oauth_callback', to: 'qbo#oauth_callback'
|
||||
|
||||
#manual sync
|
||||
get 'qbo/sync', to: 'qbo#sync'
|
||||
get 'invoices/sync', to: 'invoice#sync'
|
||||
get 'estimates/sync', to: 'estimate#sync'
|
||||
|
||||
#webhook
|
||||
post 'qbo/webhook', to: 'qbo#webhook'
|
||||
@@ -36,4 +38,5 @@ get 'filter_invoices_by_customer' => 'customers#filter_invoices_by_customer'
|
||||
|
||||
resources :customers do
|
||||
get :autocomplete_customer_name, on: :collection
|
||||
get :sync
|
||||
end
|
||||
|
||||
2
init.rb
2
init.rb
@@ -14,7 +14,7 @@ Redmine::Plugin.register :redmine_qbo do
|
||||
name 'Redmine QBO plugin'
|
||||
author 'Rick Barrette'
|
||||
description 'A pluging for Redmine to connect with QuickBooks Online to create Time Activity Entries for billable hours logged when an Issue is closed'
|
||||
version '2026.3.9'
|
||||
version '2026.3.10'
|
||||
url 'https://github.com/rickbarrette/redmine_qbo'
|
||||
author_url 'https://barrettefabrication.com'
|
||||
settings default: {empty: true}, partial: 'qbo/settings'
|
||||
|
||||
Reference in New Issue
Block a user