28 Commits
1.1.0 ... 1.1.2

Author SHA1 Message Date
7b5b673ebf Version 1.1.2 2022-03-06 18:20:30 -05:00
c72d0a83ca New line @ EOF & formating 2022-03-06 17:50:22 -05:00
3159289ac0 Removed unused code, only need to bill time
removed line items, payments, drop used db tables
2022-03-06 17:26:57 -05:00
a9cc5fac73 Removed unsed code & cleaned up comments 2022-03-06 17:05:04 -05:00
fe06fccacd Only show sync button if User is an admin 2022-03-06 16:49:07 -05:00
8b4a46f7eb H3 not H2 2022-03-06 16:27:39 -05:00
cf362caaf2 Cleaning up html formatting 2022-03-06 16:24:17 -05:00
de1be7d296 Make last 8 bold 2022-03-06 16:17:08 -05:00
d8e3e1a72f Styling & formatting 2022-03-06 13:46:55 -05:00
64a7ad844f Styling & formatting 2022-03-06 13:46:26 -05:00
9201c4ca96 render 404 on all exceptions 2022-03-06 09:23:05 -05:00
dab6b6f723 don't show doors if vehicle.doors is nil 2022-03-06 09:17:06 -05:00
495243d177 Add trim & doors to vehicle 2022-03-06 08:59:48 -05:00
332f07c93d Version 1.1.1 2022-03-06 07:51:39 -05:00
54d4be9762 Only show sidebar views when user is logged in 2022-03-06 07:32:01 -05:00
f1e3c29c97 Added Load Customer Link on Issue Form 2022-03-05 08:26:57 -05:00
66d393a465 Dynamically load hooks/patches & require redmine4+ 2022-03-02 07:15:54 -05:00
218d3392f0 Moved string to en.yml 2022-02-24 18:34:17 -05:00
0136d91cc3 Comments & formatting 2022-02-24 04:58:41 -05:00
a95f0350d8 Fixed missing transation error 2022-02-23 20:20:47 -05:00
55c04b6585 update Issue form on customer name change 2022-02-23 18:36:08 -05:00
ea21bc362a autocomplete off for forms & search 2022-02-23 18:24:57 -05:00
117d92b879 Fixed customer sorting & removed customer filter 2022-02-21 20:23:57 -05:00
440c8e4618 list issues in desc order 2022-02-21 19:33:38 -05:00
1344526f7f update txn_date too 2022-02-21 08:51:24 -05:00
19acfbc76f Added logging 2022-02-21 08:51:01 -05:00
9dfb27f0a4 Prevent webhook loops 2022-02-21 07:54:24 -05:00
51cd830710 Updated screenshots 2022-02-21 06:53:50 -05:00
41 changed files with 252 additions and 558 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -93,7 +93,7 @@ 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)
rescue ActiveRecord::RecordNotFound rescue
render_404 render_404
end end
end end
@@ -102,7 +102,7 @@ class CustomersController < ApplicationController
def edit def edit
begin begin
@customer = Customer.find_by_id(params[:id]) @customer = Customer.find_by_id(params[:id])
rescue ActiveRecord::RecordNotFound rescue
render_404 render_404
end end
end end
@@ -118,7 +118,7 @@ class CustomersController < ApplicationController
redirect_to edit_customer_path redirect_to edit_customer_path
flash[:error] = @customer.errors.full_messages.to_sentence if @customer.errors flash[:error] = @customer.errors.full_messages.to_sentence if @customer.errors
end end
rescue ActiveRecord::RecordNotFound rescue
render_404 render_404
end end
end end
@@ -129,7 +129,7 @@ class CustomersController < ApplicationController
Customer.find_by_id(params[:id]).destroy Customer.find_by_id(params[:id]).destroy
flash[:notice] = "Customer deleted successfully" flash[:notice] = "Customer deleted successfully"
redirect_to action: :index redirect_to action: :index
rescue ActiveRecord::RecordNotFound rescue
render_404 render_404
end end
end end

View File

@@ -1,94 +0,0 @@
#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.
# This controller class will handle map management
class LineItemsController < ApplicationController
unloadable
include AuthHelper
before_action :require_user
# display all line items for an issue
def index
if params[:issue_id]
begin
@line_items = Issue.find_by_id(params[:issue_id]).line_items
rescue ActiveRecord::RecordNotFound
render_404
end
end
end
# return an HTML form for creating a new line item
def new
@line_item = LineItem.new
end
# create a new line item
def create
@line_item = LineItem.new(params[:line_item])
if @line_item.save
flash[:notice] = "New Line Item Created"
redirect_to @line_item.issue
else
flash[:error] = @line_item.errors.full_messages.to_sentence
redirect_to new_line_item_path
end
end
# display a specific line item
def show
begin
@line_item = LineItem.find_by_id(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end
# return an HTML form for editing a line item
def edit
begin
@line_item = LineItem.find_by_id(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
end
# update a specific line item
def update
begin
@line_item = LineItem.find_by_id(params[:id])
if @line_item.update_attributes(params[:line_item])
flash[:notice] = "Line Item updated"
redirect_to @line_item
else
flash[:error] = @line_item.errors.full_messages.to_sentence if @line_item.errors
redirect_to edit_line_item_path
end
rescue ActiveRecord::RecordNotFound
render_404
end
end
# delete a specific line item
def destroy
begin
line_item = LineItem.find_by_id(params[:id])
issue = line_item.issue
line_item.destroy
flash[:notice] = "Line Item deleted successfully"
redirect_to issue
rescue ActiveRecord::RecordNotFound
render_404
end
end
end

View File

@@ -1,57 +0,0 @@
#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 PaymentsController < ApplicationController
unloadable
include AuthHelper
before_action :check_permissions
def new
@payment = Payment.new
@customers = Customer.all.sort_by &:name
@accounts = Qbo.get_base(:account).query("SELECT Id, Name FROM Account WHERE AccountType = 'Bank' Order By Name")
@payment_methods = Qbo.get_base(:payment_method).all
end
def create
@payment = Payment.new(params[:payment])
if @payment.save
flash[:notice] = "Payment Saved"
redirect_to Customer.find_by_id(@payment.customer_id)
else
flash[:error] = @payment.errors.full_messages.to_sentence
redirect_to new_customer_path
end
end
private
def check_permissions
if !allowed_to?(:add_payments)
render :file => "public/401.html.erb", :status => :unauthorized, :layout =>true
end
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
end

View File

@@ -92,7 +92,7 @@ class QboController < ApplicationController
# Quickbooks Webhook Callback # Quickbooks Webhook Callback
def qbo_webhook def qbo_webhook
logger.debug "Quickbooks is calling webhook" logger.info "Quickbooks is calling webhook"
# check the payload # check the payload
signature = request.headers['intuit-signature'] signature = request.headers['intuit-signature']
@@ -149,14 +149,14 @@ class QboController < ApplicationController
render nothing: true, status: 400 render nothing: true, status: 400
end end
logger.debug "Quickbooks webhook complete" logger.info "Quickbooks webhook complete"
end end
# #
# Synchronizes the QboCustomer table with QBO # Synchronizes the QboCustomer table with QBO
# #
def sync def sync
logger.debug "Syncing EVERYTHING" logger.info "Syncing EVERYTHING"
# Update info in background # Update info in background
Thread.new do Thread.new do
if Qbo.exists? if Qbo.exists?

View File

@@ -62,7 +62,9 @@ class VehiclesController < ApplicationController
begin begin
@vehicle = Vehicle.find_by_id(params[:id]) @vehicle = Vehicle.find_by_id(params[:id])
@vin = @vehicle.vin.scan(/.{1,9}/) if @vehicle.vin @vin = @vehicle.vin.scan(/.{1,9}/) if @vehicle.vin
rescue ActiveRecord::RecordNotFound @issues = @vehicle.issues.order(id: :desc)
@closed_issues = (@issues - @issues.open)
rescue
render_404 render_404
end end
end end
@@ -72,7 +74,7 @@ class VehiclesController < ApplicationController
begin begin
@vehicle = Vehicle.find_by_id(params[:id]) @vehicle = Vehicle.find_by_id(params[:id])
@customer = @vehicle.customer @customer = @vehicle.customer
rescue ActiveRecord::RecordNotFound rescue
render_404 render_404
end end
end end
@@ -90,7 +92,7 @@ class VehiclesController < ApplicationController
end end
#show any errors anyways #show any errors anyways
flash[:error] = @vehicle.errors.full_messages.to_sentence unless @vehicle.errors.empty? flash[:error] = @vehicle.errors.full_messages.to_sentence unless @vehicle.errors.empty?
rescue ActiveRecord::RecordNotFound rescue
render_404 render_404
end end
end end
@@ -101,7 +103,7 @@ class VehiclesController < ApplicationController
Vehicle.find_by_id(params[:id]).destroy Vehicle.find_by_id(params[:id]).destroy
flash[:notice] = "Vehicle deleted successfully" flash[:notice] = "Vehicle deleted successfully"
redirect_to action: :index redirect_to action: :index
rescue ActiveRecord::RecordNotFound rescue
render_404 render_404
end end
end end

View File

@@ -24,6 +24,7 @@ class QboEstimate < ActiveRecord::Base
# sync all estimates # sync all estimates
def self.sync def self.sync
logger.debug "Syncing ALL estimates"
estimates = get_base.all estimates = get_base.all
estimates.each { |estimate| estimates.each { |estimate|
process_estimate(estimate) process_estimate(estimate)
@@ -35,6 +36,7 @@ class QboEstimate < ActiveRecord::Base
# sync only one estimate # sync only one estimate
def self.sync_by_id(id) def self.sync_by_id(id)
logger.debug "Syncing estimate #{id}"
process_estimate(get_base.fetch_by_id(id)) process_estimate(get_base.fetch_by_id(id))
end end
@@ -44,11 +46,13 @@ class QboEstimate < ActiveRecord::Base
estimate = get_base.fetch_by_id(id) estimate = get_base.fetch_by_id(id)
qbo_estimate = find_or_create_by(id: id) qbo_estimate = find_or_create_by(id: id)
qbo_estimate.doc_number = estimate.doc_number qbo_estimate.doc_number = estimate.doc_number
qbo_estimate.txn_date = estimate.txn_date
qbo_estimate.save! qbo_estimate.save!
end end
# process an estimate into the database # process an estimate into the database
def self.process_estimate(estimate) def self.process_estimate(estimate)
logger.info "Processing estimate #{estimate.id}"
qbo_estimate = find_or_create_by(id: estimate.id) qbo_estimate = find_or_create_by(id: estimate.id)
qbo_estimate.doc_number = estimate.doc_number qbo_estimate.doc_number = estimate.doc_number
qbo_estimate.customer_id = estimate.customer_ref.value qbo_estimate.customer_id = estimate.customer_ref.value

View File

@@ -71,7 +71,7 @@ class QboInvoice < ActiveRecord::Base
# processes the invoice into the database # processes the invoice into the database
def self.process_invoice(invoice) def self.process_invoice(invoice)
logger.debug "Processing invoice" logger.info "Processing invoice #{invoice.id}"
# Load the invoice into the database # Load the invoice into the database
qbo_invoice = QboInvoice.find_or_create_by(id: invoice.id) qbo_invoice = QboInvoice.find_or_create_by(id: invoice.id)
@@ -104,6 +104,7 @@ class QboInvoice < ActiveRecord::Base
# this condions causes an infinite loop as the webhook is called when an invoice is updated # this condions causes an infinite loop as the webhook is called when an invoice is updated
# TODO maybe add a cf_sync_confict flag to invoices # TODO maybe add a cf_sync_confict flag to invoices
def self.compare_custom_fields(issue, invoice) def self.compare_custom_fields(issue, invoice)
logger.debug "Comparing custom fields"
# TODO break if QboInvoice.find(invoice.id).cf_sync_confict # TODO break if QboInvoice.find(invoice.id).cf_sync_confict
is_changed = false is_changed = false
@@ -115,6 +116,10 @@ class QboInvoice < ActiveRecord::Base
# TODO create hook for seperate plugin # TODO create hook for seperate plugin
begin begin
if cf.name.eql? "VIN" if cf.name.eql? "VIN"
# Only update if blank to prevent infite loops
# TODO check cf_sync_confict flag once implemented
if cf.string_value.to_s.blank?
logger.debug " VIN was blank, updating the invoice vin in quickbooks"
vin = Vehicle.find(issue.vehicles_id).vin vin = Vehicle.find(issue.vehicles_id).vin
break if vin.nil? break if vin.nil?
if not cf.string_value.to_s.eql? vin if not cf.string_value.to_s.eql? vin
@@ -122,6 +127,8 @@ class QboInvoice < ActiveRecord::Base
logger.debug "VIN has changed" logger.debug "VIN has changed"
is_changed = true is_changed = true
end end
end
end end
rescue rescue
#do nothing #do nothing

View File

@@ -1,47 +0,0 @@
#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 QboItem < ActiveRecord::Base
unloadable
has_many :issues
#attr_accessible :name
validates_presence_of :id, :name
self.primary_key = :id
def self.get_base
Qbo.get_base(:item)
end
def self.sync
last = Qbo.first.last_sync
query = "SELECT Id, Name FROM Item WHERE Type = 'Service' "
query << " AND Metadata.LastUpdatedTime >= '#{last.iso8601}' " if last
if count == 0
items = get_base.all
else
items = get_base.query(query)
end
unless items.count = 0
items.find_by(:type, "Service").each { |i|
qbo_item = QboItem.find_or_create_by(id: i.id)
qbo_item.name = i.name
qbo_item.id = i.id
qbo_item.save
}
end
# QboItem.where.not(items.map(&:id)).destroy_all
end
end

View File

@@ -1,47 +0,0 @@
#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 QboPurchase < ActiveRecord::Base
unloadable
belongs_to :issues
belongs_to :qbo_customer
#attr_accessible :description
validates_presence_of :id, :line_id, :description, :qbo_customer_id
def self.get_base
Qbo.get_base(:purchase)
end
def get_purchase(id)
get_base.find_by_id(id)
end
def self.sync
QboPurchase.get_base.all.each { |purchase|
purchase.line_items.all? { |line_item|
detail = line_item.account_based_expense_line_detail ? line_item.account_based_expense_line_detail : line_item.item_based_expense_line_detail
if detail.billable_status = "Billable"
qbo_purchase = find_or_create_by(id: purchase.id)
qbo_purchase.line_id = line_item.id
qbo_purchase.description = line_item.description
qbo_purchase.qbo_customer_id = detail.customer_ref
#TODO attach to issues
#qbo_purchase.issue_id = Issue.find_by_invoice()
qbo_purchase.save
end
}
}
end
end

View File

@@ -15,12 +15,9 @@ class Vehicle < ActiveRecord::Base
belongs_to :customer belongs_to :customer
has_many :issues, :foreign_key => 'vehicles_id' has_many :issues, :foreign_key => 'vehicles_id'
#attr_accessible :year, :make, :model, :customer_id, :notes, :vin
validates_presence_of :customer validates_presence_of :customer
validates :vin, uniqueness: true validates :vin, uniqueness: true
before_save :decode_vin before_save :decode_vin
#after_find :get_details
self.primary_key = :id self.primary_key = :id
@@ -34,35 +31,12 @@ class Vehicle < ActiveRecord::Base
end end
end end
# returns the raw JSON details from EMUNDS # returns the raw JSON details from NHTSA
def details def details
get_details if @details.nil? get_details if @details.nil?
return @details return @details
end end
# returns the style of the vehicle
def style
get_details if @details.nil?
begin
return @details.trim if @details
rescue
return nil
end
end
# returns the drive of the vehicle i.e. 2 wheel, 4 wheel, ect.
def drive
#todo fix this
#return @details.drive_type if @details
return nil
end
# returns the number of doors of the vehicle
def doors
get_details if @details.nil?
return @details.doors if @details
end
# Force Upper Case for make numbers # Force Upper Case for make numbers
def make=(val) def make=(val)
# The to_s is in case you get nil/non-string # The to_s is in case you get nil/non-string
@@ -75,9 +49,8 @@ class Vehicle < ActiveRecord::Base
write_attribute(:model, val.to_s.titleize) write_attribute(:model, val.to_s.titleize)
end end
# Force Upper Case for VIN numbers # Force Upper Case & strip VIN of all illegal chars (for barcode scanner)
def vin=(val) def vin=(val)
#strip VIN of all illegal chars (for barcode scanner)
val = val.to_s.upcase.gsub(/[^A-HJ-NPR-Za-hj-npr-z\d]+/,"") val = val.to_s.upcase.gsub(/[^A-HJ-NPR-Za-hj-npr-z\d]+/,"")
write_attribute(:vin, val) write_attribute(:vin, val)
end end
@@ -95,6 +68,8 @@ class Vehicle < ActiveRecord::Base
self.year = @details.year unless @details.year.nil? self.year = @details.year unless @details.year.nil?
self.make = @details.make unless @details.make.nil? self.make = @details.make unless @details.make.nil?
self.model = @details.model unless @details.model.nil? self.model = @details.model unless @details.model.nil?
self.doors = @details.doors unless @details.doors.nil?
self.trim = @details.trim unless @details.trim.nil?
rescue Exception => e rescue Exception => e
errors.add(:vin, e.message) errors.add(:vin, e.message)
end end
@@ -102,9 +77,9 @@ class Vehicle < ActiveRecord::Base
self.name = to_s self.name = to_s
end end
private private
# init method to pull JSON details from Edmunds # init method to pull JSON details from NHTSA
def get_details def get_details
if self.vin? if self.vin?
#validate the vin before calling a remote server #validate the vin before calling a remote server

View File

@@ -34,11 +34,10 @@
<th><%=t(:field_notes)%></th> <th><%=t(:field_notes)%></th>
<td><%= customer.notes %></td> <td><%= customer.notes %></td>
</tr> </tr>
<tr>
<td>
<%= button_to t(:label_edit_customer), edit_customer_path(customer), method: :get%>
</td>
</tr>
</tbody> </tbody>
</table> </table>
<div style="float: right;">
<%= button_to t(:label_edit_customer), edit_customer_path(customer), method: :get%>
</div>
<br/>
<br/>

View File

@@ -7,28 +7,28 @@
<div class="clearfix"> <div class="clearfix">
<%=t(:label_display_name)%> <%=t(:label_display_name)%>
<div class="input"> <div class="input">
<%= f.text_field :name, :required => true %> <%= f.text_field :name, :required => true, :autocomplete => "off" %>
</div> </div>
</div> </div>
<div class="clearfix"> <div class="clearfix">
<%=t(:label_primary_phone)%> <%=t(:label_primary_phone)%>
<div class="input"> <div class="input">
<%= f.telephone_field :primary_phone %> <%= f.telephone_field :primary_phone, :autocomplete => "off" %>
</div> </div>
</div> </div>
<div class="clearfix"> <div class="clearfix">
<%=t(:label_mobile_phone)%>: <%=t(:label_mobile_phone)%>:
<div class="input"> <div class="input">
<%= f.telephone_field :mobile_phone %> <%= f.telephone_field :mobile_phone, :autocomplete => "off" %>
</div> </div>
</div> </div>
<div class="clearfix"> <div class="clearfix">
<%=t(:label_email)%>: <%=t(:label_email)%>:
<div class="input"> <div class="input">
<%= f.email_field :email %> <%= f.email_field :email, :autocomplete => "off" %>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
<%= form_tag(customers_path, :method => "get", id: "search-form") do %> <%= form_tag(customers_path, :method => "get", id: "search-form") do %>
<%= text_field_tag :search, params[:search], placeholder: t(:label_search_customers), :autocomplete => "off" %> <%= text_field_tag :search, params[:search], placeholder: t(:label_search_customers), :autocomplete => "off" %>
<%= submit_tag t(:label_search) %> <%= submit_tag t(:label_search) %>
<% end %> <% end %>
<%= button_to t(:label_new_customer), new_customer_path, method: :get%> <%= button_to t(:label_new_customer), new_customer_path, method: :get%>
<%= button_to t(:label_sync), qbo_sync_path, method: :get%> <%= button_to(t(:label_sync), qbo_sync_path, method: :get) if User.current.admin?%>

View File

@@ -1,15 +1,12 @@
<h2><%=t(:field_customer)%> #<%= @customer.id %> - <%= @customer.name %> </h2> <h2><%=t(:field_customer)%> #<%= @customer.id %> - <%= @customer.name %> </h2>
<br/> <div class="issue">
<div class="subject">
<div><h4><%=t(:label_details)%>:</h4></div>
</div>
<div class="attributes">
<div class="splitcontent"> <div class="splitcontent">
<div class="splitcontentleft"> <div class="splitcontentleft">
<h4><%=t(:label_details)%>:</h4>
<%= render :partial => 'customers/details', locals: {customer: @customer} %> <%= render :partial => 'customers/details', locals: {customer: @customer} %>
<div class="splitcontent"> <div class="splitcontent">
@@ -29,15 +26,17 @@
<div class="splitcontentleft"> <div class="splitcontentleft">
<h4><%=t(:field_vehicles)%>:</h4> <h4><%=t(:field_vehicles)%>:</h4>
<%= render :partial => 'vehicles/list' %> <%= render :partial => 'vehicles/list' %>
<div style="float: right;">
<%= button_to "New Vehicle", new_customer_vehicle_path(@customer), method: :get %> <%= button_to "New Vehicle", new_customer_vehicle_path(@customer), method: :get %>
</div> </div>
</div> </div>
</div>
<br/>
<h2><%=@issues.open.count%> <%=t(:label_open_issues)%>:</h2>
<%= render :partial => 'issues/list_simple', locals: {issues: @issues.open} %>
<h2><%=@closed_issues.count%> <%=t(:label_closed_issues)%>:</h2>
<%= render :partial => 'issues/list_simple', locals: {issues: @closed_issues} %>
</div> </div>
<br/>
<h3><%=@issues.open.count%> <%=t(:label_open_issues)%>:</h3>
<%= render :partial => 'issues/list_simple', locals: {issues: @issues.open} %>
<h3><%=@closed_issues.count%> <%=t(:label_closed_issues)%>:</h3>
<%= render :partial => 'issues/list_simple', locals: {issues: @closed_issues} %>

View File

@@ -1,42 +0,0 @@
<div class="row">
<div class="span6 columns">
<fieldset>
<%= form_for @payment do |f| %>
<div class="clearfix">
<%=t(:field_customer)%>:
<div class="input">
<%= f.collection_select :customer_id, @customers, :id, :name, include_blank: true, :selected => @customer, :required => true%>
</div>
</div>
<div class="clearfix">
<%=t(:label_deposit_into)%>:
<div class="input">
<%= f.collection_select :account_id, @accounts, :id, :name, include_blank: true, :selected => @account, :required => true%>
</div>
</div>
<div class="clearfix">
<%=t(:label_payment_method)%>:
<div class="input">
<%= f.collection_select :payment_method_id, @payment_methods, :id, :name, include_blank: true, :selected => @payment_method, :required => true%>
</div>
</div>
<div class="clearfix">
<%=t(:label_amount)%>:
<div class="input">
<%= f.number_field :total_amount %>
</div>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
</fieldset>
</div>
</div>

View File

@@ -1,3 +0,0 @@
<h1><%=t(:label_new_payment)%></h1>
<br/>
<%= render :partial => 'payments/form' %>

View File

@@ -1,2 +1,6 @@
<%= render :partial => 'customers/sidebar' %> <% if User.current.logged? %>
<%= render :partial => 'estimates/sidebar' %>
<%= render :partial => 'customers/sidebar' %>
<%= render :partial => 'estimates/sidebar' %>
<% end %>

View File

@@ -30,4 +30,3 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '<%= authenticate_vendors_url %>'}); intuit.ipp.anywhere.setup({menuProxy: '/path/to/blue-dot', grantUrl: '<%= authenticate_vendors_url %>'});
</script> </script>
</body> </body>

View File

@@ -1,4 +1,9 @@
<table> <div class="issue">
<div class="splitcontent">
<div class="splitcontentleft">
<h4><%=t(:label_details)%>:</h4>
<table>
<tbody> <tbody>
<tr> <tr>
@@ -16,21 +21,25 @@
<td><%= @vin[0] if @vin %><b><%=@vin[1] if @vin%></b></td> <td><%= @vin[0] if @vin %><b><%=@vin[1] if @vin%></b></td>
</tr> </tr>
<tr> <th><%= t(:label_trim) %></th>
<th><%= t(:field_notes) %></th> <td><%= vehicle.doors %> <%=t(:label_door) if vehicle.doors? %> <%= vehicle.trim %></td>
</tr>
</tbody>
</table>
</div>
<div class="splitcontentleft">
<h4><%=t(:field_notes)%>:</h4>
<td><%= vehicle.notes %></td> <td><%= vehicle.notes %></td>
</tr> </tr>
<tr> </div>
<th> <%= t(:issues) %> </th> </div>
<td><%= vehicle.issues.count %></td> </div>
</tr>
<tr> <div style="float: right;">
<td>
<%= button_to t(:label_edit), edit_vehicle_path(vehicle), method: :get%> <%= button_to t(:label_edit), edit_vehicle_path(vehicle), method: :get%>
<%= button_to t(:label_delete), vehicle, method: :delete, data: {confirm: t(:warn_ru_sure)} %> <%= button_to t(:label_delete), vehicle, method: :delete, data: {confirm: t(:warn_ru_sure)} %>
</td> </div>
</tr>
</tbody>
</table>

View File

@@ -14,21 +14,21 @@
<div class="clearfix"> <div class="clearfix">
<%=t(:label_year)%>: <%=t(:label_year)%>:
<div class="input"> <div class="input">
<%= f.number_field :year %> <%= f.number_field :year, :autocomplete => "off" %>
</div> </div>
</div> </div>
<div class="clearfix"> <div class="clearfix">
<%=t(:label_make)%>: <%=t(:label_make)%>:
<div class="input"> <div class="input">
<%= f.text_field :make %> <%= f.text_field :make, :autocomplete => "off" %>
</div> </div>
</div> </div>
<div class="clearfix"> <div class="clearfix">
<%=t(:label_model)%>: <%=t(:label_model)%>:
<div class="input"> <div class="input">
<%= f.text_field :model %> <%= f.text_field :model, :autocomplete => "off" %>
</div> </div>
</div> </div>

View File

@@ -11,7 +11,7 @@
<br/> <br/>
<%= vehicle.customer %> <%= vehicle.customer %>
<br/> <br/>
<%= vehicle.vin %> <%= vehicle.vin.scan(/.{1,9}/)[0] if vehicle.vin %><b><%=vehicle.vin.scan(/.{1,9}/)[1] if vehicle.vin%></b>
</div> </div>
</div> </div>
<br/> <br/>

View File

@@ -1,4 +1,4 @@
<%= form_tag(vehicles_path, :method => "get", id: "search-form") do %> <%= form_tag(vehicles_path, :method => "get", id: "search-form") do %>
<%= text_field_tag :search, params[:search], placeholder: t(:label_seach_vin) %> <%= text_field_tag :search, params[:search], placeholder: t(:label_search_vin), :autocomplete => "off" %>
<%= submit_tag t(:label_search) %> <%= submit_tag t(:label_search) %>
<% end %> <% end %>

View File

@@ -1,14 +1,11 @@
<h2><%=t(:field_vehicle)%> #<%=@vehicle.id%> <span style="float:right"> <%= render :partial => 'customers/search' %> </span> </h2> <h2><%=t(:field_vehicle)%> #<%=@vehicle.id%></h2>
<br/>
<div style="text-align: left; width:90%;"> <%= render :partial => 'vehicles/details', locals: {vehicle: @vehicle} %>
<%= render :partial => 'vehicles/details', locals: {vehicle: @vehicle} %>
<p> <b> <%=t(:label_open_issues)%> </b> </p> <h3><%=@issues.open.count%> <%=t(:label_open_issues)%></h3>
<%= render :partial => 'issues/list_simple', locals: {issues: @vehicle.issues.open} %> <%= render :partial => 'issues/list_simple', locals: {issues: @issues.open} %>
<p> <b> <%=t(:label_closed_issues)%> </b> </p> <h3><%=@closed_issues.count%> <%=t(:label_closed_issues)%></h3>
<%= render :partial => 'issues/list_simple', locals: {issues: (@vehicle.issues - @vehicle.issues.open)} %> <%= render :partial => 'issues/list_simple', locals: {issues: (@closed_issues)} %>
</div>

View File

@@ -77,3 +77,7 @@ en:
label_no_estimates: "No Estimates" label_no_estimates: "No Estimates"
label_no_invoices: "No Invoices" label_no_invoices: "No Invoices"
label_invoices: "Invoices" label_invoices: "Invoices"
label_load_customer: "Load Customer"
label_door: "Door"
label_trim: "Trim"

View File

@@ -8,27 +8,23 @@
# #
#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. #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 LineItem < ActiveRecord::Base class UpdateVehiclesTrim < ActiveRecord::Migration[5.1]
def change
add_column :vehicles, :doors, :text
add_column :vehicles, :trim, :text
unloadable reversible do |direction|
direction.up {
belongs_to :issue # Update local vehicle database by forcing a save, look at before_save
vehicles = Vehicle.all
vehicles.each { |vehicle|
vehicle.save!
}
#attr_accessible :amount, :description, :unit_price, :quantity, :item_id }
validates_presence_of :amount, :description, :unit_price, :quantity
def add_to_invoice(invoice)
line_item = Quickbooks::Model::InvoiceLineItem.new
line_item.amount = amount
line_item.description = description
line_item.sales_item! do |detail|
detail.unit_price = unit_price
detail.quantity = quantity
detail.item_id = item_id # Item ID here... Where do i get this???
end end
invoice.line_items << line_item
return invoice
end end
end end

View File

@@ -1,6 +1,6 @@
#The MIT License (MIT) #The MIT License (MIT)
# #
#Copyright (c) 2020 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:
# #
@@ -8,30 +8,10 @@
# #
#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. #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 Payment class RemoveQboItems < ActiveRecord::Migration[5.1]
unloadable def change
drop_table :qbo_items
include ActiveModel::Model drop_table :qbo_purchases
drop_table :line_items
attr_accessor :errors, :customer_id, :account_id, :payment_method_id, :total_amount
validates_presence_of :customer_id, :account_id, :payment_method_id, :total_amount
validates :total_amount, numericality: true
def save
payment = Quickbooks::Model::Payment.new
payment.customer_id = @customer_id.to_i
payment.deposit_to_account_id = @account_id.to_i
payment.payment_method_id = @payment_method_id.to_i
payment.total = @total_amount
Qbo.get_base(:payment).update(payment)
end end
def save!
save
end
# Dummy stub to make validtions happy.
def update_attribute
end
end end

33
init.rb
View File

@@ -8,36 +8,27 @@
# #
#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. #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.
# Dynamically load all Hooks & Patches
ActiveSupport::Reloader.to_prepare do
Dir::foreach(File.join(File.dirname(__FILE__), 'lib')) do |file|
next unless /\.rb$/ =~ file
require_dependency file
end
end
Redmine::Plugin.register :redmine_qbo do Redmine::Plugin.register :redmine_qbo do
# View Hook Listeners
require_dependency 'issues_form_hook_listener'
require_dependency 'issues_save_hook_listener'
require_dependency 'issues_show_hook_listener'
require_dependency 'users_show_hook_listener'
require_dependency 'header_footer_hook_listener'
require_dependency 'projects_form_hook_listener'
require_dependency 'view_hook_listener'
# Patches to the Redmine core. Will not work in development mode
require_dependency 'issue_patch'
require_dependency 'project_patch'
require_dependency 'user_patch'
require_dependency 'query_patch'
require_dependency 'time_entry_query_patch'
require_dependency 'pdf_patch'
require_dependency 'attachments_controller_patch'
# About # About
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.0' version '1.1.2'
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'
requires_redmine :version_or_higher => '4.0.0'
# Add safe attributes # Add safe attributes for core models
Issue.safe_attributes 'customer_id' Issue.safe_attributes 'customer_id'
Issue.safe_attributes 'qbo_item_id' Issue.safe_attributes 'qbo_item_id'
Issue.safe_attributes 'qbo_estimate_id' Issue.safe_attributes 'qbo_estimate_id'
@@ -61,7 +52,7 @@ Redmine::Plugin.register :redmine_qbo do
permission :add_payments, :payments => :new, :public => false permission :add_payments, :payments => :new, :public => false
permission :view_vehicles, :payments => :new, :public => false permission :view_vehicles, :payments => :new, :public => false
# Register QBO top menu item # Register top menu items
menu :top_menu, :customers, { :controller => :customers, :action => :index }, :caption => 'Customers', :if => Proc.new {User.current.logged?} menu :top_menu, :customers, { :controller => :customers, :action => :index }, :caption => 'Customers', :if => Proc.new {User.current.logged?}
menu :top_menu, :vehicles, { :controller => :vehicles, :action => :index }, :caption => 'Vehicles', :if => Proc.new { User.current.logged? } menu :top_menu, :vehicles, { :controller => :vehicles, :action => :index }, :caption => 'Vehicles', :if => Proc.new { User.current.logged? }

View File

@@ -14,6 +14,6 @@ class HeaderFooterHookListener < Redmine::Hook::ViewListener
end end
def view_layouts_base_body_bottom(context = {}) def view_layouts_base_body_bottom(context = {})
return "<div id='qbo_footer' align='center'><b>Last Sync: </b> #{Qbo.last_sync if Qbo.exists?}</div>" return "<div id='qbo_footer' align='center'><b>#{I18n.translate(:label_last_sync)}: </b> #{Qbo.last_sync if Qbo.exists?}</div>"
end end
end end

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:
# #
@@ -18,7 +18,7 @@ class IssuesFormHookListener < Redmine::Hook::ViewListener
end end
# Edit Issue Form # Edit Issue Form
# Show a dropdown for quickbooks contacts # TODO figure out how to do this with a view
def view_issues_form_details_bottom(context={}) def view_issues_form_details_bottom(context={})
f = context[:form] f = context[:form]
@@ -28,7 +28,7 @@ class IssuesFormHookListener < Redmine::Hook::ViewListener
selected_vehicle = context[:project].vehicle ? context[:project].vehicle.id : nil selected_vehicle = context[:project].vehicle ? context[:project].vehicle.id : nil
end end
# Check to see if there is a quickbooks user attached to the issue # Check to see if there are things attached to the issue
selected_customer = context[:issue].customer ? context[:issue].customer.id : nil selected_customer = context[:issue].customer ? context[:issue].customer.id : nil
selected_estimate = context[:issue].qbo_estimate ? context[:issue].qbo_estimate.id : nil selected_estimate = context[:issue].qbo_estimate ? context[:issue].qbo_estimate.id : nil
selected_vehicle = context[:issue].vehicles_id ? context[:issue].vehicles_id : nil selected_vehicle = context[:issue].vehicles_id ? context[:issue].vehicles_id : nil
@@ -36,15 +36,22 @@ class IssuesFormHookListener < Redmine::Hook::ViewListener
# Load customer information # Load customer information
customer = Customer.find_by_id(selected_customer) if selected_customer customer = Customer.find_by_id(selected_customer) if selected_customer
# Customer Name Text Box with database backed autocomplete
search_customer = f.autocomplete_field :customer, search_customer = f.autocomplete_field :customer,
autocomplete_customer_name_customers_path, autocomplete_customer_name_customers_path,
:selected => selected_customer, :selected => selected_customer,
:update_elements => { :id => '#issue_customer_id', :value => '#issue_customer' } :onchange => "updateIssueFrom('/issues/#{context[:issue].id}/edit.js', this)",
:update_elements => {
:id => '#issue_customer_id',
:value => '#issue_customer'
}
# Customer ID - Hidden Field
customer_id = f.hidden_field :customer_id, customer_id = f.hidden_field :customer_id,
:id => "issue_customer_id", :id => "issue_customer_id",
:onchange => "updateIssueFrom('/issues/#{context[:issue].id}/edit.js', this)" :onchange => "updateIssueFrom('/issues/#{context[:issue].id}/edit.js', this)"
# Load estimates & vehicles
if context[:issue].customer if context[:issue].customer
if customer.vehicles if customer.vehicles
vehicles = customer.vehicles.pluck(:name, :id) vehicles = customer.vehicles.pluck(:name, :id)
@@ -57,11 +64,10 @@ class IssuesFormHookListener < Redmine::Hook::ViewListener
estimates = [nil].compact estimates = [nil].compact
end end
# Generate the drop down list of quickbooks extimates # Generate the drop down list of quickbooks extimates & vehicles
select_estimate = f.select :qbo_estimate_id, estimates, :selected => selected_estimate, include_blank: true select_estimate = f.select :qbo_estimate_id, estimates, :selected => selected_estimate, include_blank: true
vehicle = f.select :vehicles_id, vehicles, :selected => selected_vehicle, include_blank: true vehicle = f.select :vehicles_id, vehicles, :selected => selected_vehicle, include_blank: true
return "<p><label for=\"issue_customer\">Customer</label>#{search_customer} #{customer_id}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>" return "<p><label for=\"issue_customer\">Customer</label>#{search_customer} #{customer_id} #{link_to_function I18n.t(:label_load_customer), "updateIssueFrom('/issues/#{context[:issue].id}/edit.js', this)"}</p> <p>#{select_estimate}</p> <p>#{vehicle}</p>"
end end
end end

View File

@@ -32,5 +32,5 @@ class ProjectsFormHookListener < Redmine::Hook::ViewListener
vehicle = f.select :vehicle_id, vehicles, :selected => selected_vehicle, include_blank: true vehicle = f.select :vehicle_id, vehicles, :selected => selected_vehicle, include_blank: true
return "<p><label for=\"project_customer\">Customer</label>#{search_customer} #{customer_id}</p> <p>#{vehicle}</p>" return "<p><label for=\"project_customer\">Customer</label>#{search_customer} #{customer_id}</p> <p>#{vehicle}</p>"
end end
end end

View File

@@ -16,7 +16,7 @@ module QueryPatch
def available_columns def available_columns
unless @available_columns unless @available_columns
@available_columns = self.class.available_columns.dup @available_columns = self.class.available_columns.dup
@available_columns << QueryColumn.new(:customer, :sortable => "#{Customer.table_name}.name", :groupable => true, :caption => :field_customer) @available_columns << QueryColumn.new(:customer, :sortable => "#{Issue.table_name}.customer_id", :groupable => true, :caption => :field_customer)
@available_columns << QueryColumn.new(:qbo_billed, :sortable => "#{TimeEntry.table_name}.qbo_billed", :groupable => true, :caption => :field_qbo_billed) @available_columns << QueryColumn.new(:qbo_billed, :sortable => "#{TimeEntry.table_name}.qbo_billed", :groupable => true, :caption => :field_qbo_billed)
end end
super super
@@ -24,7 +24,7 @@ module QueryPatch
# Add customers to filters # Add customers to filters
def initialize_available_filters def initialize_available_filters
add_available_filter "customer", :type => :text #add_available_filter "customer", :type => :text
super super
end end

View File

@@ -1,3 +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 ViewHookListener < Redmine::Hook::ViewListener class ViewHookListener < Redmine::Hook::ViewListener
render_on :view_layouts_base_sidebar, :partial => "qbo/sidebar" render_on :view_layouts_base_sidebar, :partial => "qbo/sidebar"
end end