mirror of
https://github.com/rickbarrette/redmine_qbo.git
synced 2026-10-04 01:00:44 -04:00
Compare commits
5
Commits
57dc8127b0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf3342bcc5 | ||
|
|
bab019a152 | ||
|
|
fd91bd6fa2 | ||
|
|
04a0eede5f | ||
|
|
ba40496421 |
No files matched your search
@@ -25,6 +25,8 @@ class CustomersController < ApplicationController
|
|||||||
include SortHelper
|
include SortHelper
|
||||||
helper :timelog
|
helper :timelog
|
||||||
|
|
||||||
|
# This tells Redmine to check global permissions for the current controller/action
|
||||||
|
before_action :authorize_global
|
||||||
before_action :add_customer, only: [:new, :create]
|
before_action :add_customer, only: [:new, :create]
|
||||||
before_action :view_customer, except: [:new, :create, :view]
|
before_action :view_customer, except: [:new, :create, :view]
|
||||||
skip_before_action :verify_authenticity_token, :check_if_login_required, only: [:view]
|
skip_before_action :verify_authenticity_token, :check_if_login_required, only: [:view]
|
||||||
@@ -61,12 +63,43 @@ class CustomersController < ApplicationController
|
|||||||
|
|
||||||
# Used for autocomplete form
|
# Used for autocomplete form
|
||||||
def autocomplete
|
def autocomplete
|
||||||
term = ActiveRecord::Base.sanitize_sql_like(params[:q].to_s)
|
# Support both existing 'q' param and new 'name'/'phone' params
|
||||||
|
name_query = (params[:name] || params[:q] || params[:term]).to_s.strip
|
||||||
|
phone_query = params[:phone].to_s.gsub(/\D/, '')
|
||||||
|
|
||||||
items = Customer.where("name LIKE :t OR phone_number LIKE :t OR mobile_phone_number LIKE :t", t: "%#{term}%")
|
sql_matches = []
|
||||||
.order(:name)
|
|
||||||
.limit(20)
|
|
||||||
|
|
||||||
|
# 1. Check for phone number matches via SQL
|
||||||
|
if phone_query.present?
|
||||||
|
sql_matches += Customer.where("phone_number LIKE :p OR mobile_phone_number LIKE :p", p: "%#{phone_query}%")
|
||||||
|
end
|
||||||
|
|
||||||
|
# 2. Check for exact or partial name matches via SQL
|
||||||
|
if name_query.present?
|
||||||
|
safe_name = ActiveRecord::Base.sanitize_sql_like(name_query)
|
||||||
|
sql_matches += Customer.where("name LIKE :t", t: "%#{safe_name}%")
|
||||||
|
end
|
||||||
|
|
||||||
|
sql_matches = sql_matches.uniq
|
||||||
|
|
||||||
|
# 3. Handle spelling errors using built-in string distance
|
||||||
|
fuzzy_matches = []
|
||||||
|
if name_query.present?
|
||||||
|
require 'did_you_mean/jaro_winkler'
|
||||||
|
|
||||||
|
# Only scan records we haven't already matched
|
||||||
|
unmatched = Customer.all - sql_matches
|
||||||
|
|
||||||
|
fuzzy_matches = unmatched.select do |c|
|
||||||
|
# 0.6 is the threshold. 1.0 is an exact match.
|
||||||
|
DidYouMean::JaroWinkler.distance(c.name.to_s.downcase, name_query.downcase) > 0.6
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Combine results and limit to top 20
|
||||||
|
items = (sql_matches + fuzzy_matches).first(20)
|
||||||
|
|
||||||
|
# Return JSON formatted for both existing usage and new jQuery UI requirements
|
||||||
render json: items.map { |i|
|
render json: items.map { |i|
|
||||||
{ id: i.id, name: i.name, phone_number: i.phone_number, mobile_phone_number: i.mobile_phone_number }
|
{ id: i.id, name: i.name, phone_number: i.phone_number, mobile_phone_number: i.mobile_phone_number }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
|
|
||||||
<%= call_hook :customer_actions_bottom, { customer: @customer } %>
|
<%= call_hook :customer_actions_bottom, { customer: @customer } %>
|
||||||
|
|
||||||
<%= button_to t(:label_edit_customer), edit_customer_path(@customer), method: :get%>
|
<% if User.current.allowed_to?(:edit_customers, nil, global: true) %>
|
||||||
|
<%= button_to t(:label_edit_customer), edit_customer_path(@customer), method: :get%>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function handleSingleSelect(event, className) {
|
function handleSingleSelect(event, className) {
|
||||||
|
|||||||
@@ -2,5 +2,7 @@
|
|||||||
<%= text_field_tag :search, params[:search], class: "customer-name", placeholder: t(:label_search_customers), autocomplete: "off", data: { autocomplete_url: "/customers/autocomplete" } %>
|
<%= text_field_tag :search, params[:search], class: "customer-name", placeholder: t(:label_search_customers), autocomplete: "off", data: { autocomplete_url: "/customers/autocomplete" } %>
|
||||||
<%= submit_tag t(:label_search) %>
|
<%= submit_tag t(:label_search) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= button_to t(:label_new_customer), new_customer_path, method: :get%>
|
<% if User.current.allowed_to?(:add_customers, nil, global: true) %>
|
||||||
|
<%= button_to t(:label_new_customer), new_customer_path, method: :get%>
|
||||||
|
<% end %>
|
||||||
<%= button_to(t(:label_sync), qbo_sync_path, method: :get) if User.current.admin?%>
|
<%= button_to(t(:label_sync), qbo_sync_path, method: :get) if User.current.admin?%>
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
<% if User.current.logged? %>
|
<% if User.current.logged? %>
|
||||||
|
|
||||||
<%= render partial: 'customers/sidebar' %>
|
<% if User.current.allowed_to?(:view_customers, nil, global: true) %>
|
||||||
|
<%= render partial: 'customers/sidebar' %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= render partial: 'estimates/sidebar' %>
|
<%= render partial: 'estimates/sidebar' %>
|
||||||
|
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -14,7 +14,7 @@ Redmine::Plugin.register :redmine_qbo do
|
|||||||
name 'Redmine QBO plugin'
|
name 'Redmine QBO plugin'
|
||||||
author 'Rick Barrette'
|
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'
|
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.8.2'
|
version '2026.9.0'
|
||||||
url 'https://github.com/rickbarrette/redmine_qbo'
|
url 'https://github.com/rickbarrette/redmine_qbo'
|
||||||
author_url 'https://barrettefabrication.com'
|
author_url 'https://barrettefabrication.com'
|
||||||
settings default: {empty: true}, partial: 'qbo/settings'
|
settings default: {empty: true}, partial: 'qbo/settings'
|
||||||
@@ -30,12 +30,13 @@ Redmine::Plugin.register :redmine_qbo do
|
|||||||
# set per_page globally
|
# set per_page globally
|
||||||
WillPaginate.per_page = 20
|
WillPaginate.per_page = 20
|
||||||
|
|
||||||
# Permissions for security
|
# Global Permissions
|
||||||
permission :view_customers, customers: :index, public: false
|
permission :view_customers, { customers: [:index, :show] }, global: true
|
||||||
permission :add_customers, customers: :new, public: false
|
permission :add_customers, { customers: [:new, :create] }, global: true
|
||||||
|
permission :edit_customers, { customers: [:edit, :update] }, global: true
|
||||||
|
|
||||||
# Register top menu items
|
# Register top menu items
|
||||||
menu :top_menu, :customers, { controller: :customers, action: :index }, caption: :label_customers, if: Proc.new {User.current.logged?}
|
menu :top_menu, :customers, { controller: :customers, action: :index }, caption: :label_customers, if: Proc.new {User.current.allowed_to?(:view_customers, nil, global: true)}
|
||||||
|
|
||||||
Redmine::Search.map do |search|
|
Redmine::Search.map do |search|
|
||||||
search.register :customers
|
search.register :customers
|
||||||
|
|||||||
@@ -29,6 +29,15 @@ module RedmineQbo
|
|||||||
RedmineQbo::Hooks::IssuesHookListener
|
RedmineQbo::Hooks::IssuesHookListener
|
||||||
RedmineQbo::Hooks::UsersShowHookListener
|
RedmineQbo::Hooks::UsersShowHookListener
|
||||||
RedmineQbo::Hooks::ViewHookListener
|
RedmineQbo::Hooks::ViewHookListener
|
||||||
|
|
||||||
|
# If the SubtaskFieldCopier plugin is installed, register our fields
|
||||||
|
if defined?(SubtaskFieldCopier)
|
||||||
|
SubtaskFieldCopier.registered_fields << :customer
|
||||||
|
SubtaskFieldCopier.registered_fields << :estimate
|
||||||
|
|
||||||
|
# Ensure there are no duplicates in case of hot-reloads in development mode
|
||||||
|
SubtaskFieldCopier.registered_fields.uniq!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.settings
|
def self.settings
|
||||||
|
|||||||
Reference in new issue
Block a user