From 14f411c2e180412bc84d0813438204e4b4318d06 Mon Sep 17 00:00:00 2001 From: Rick Barrette Date: Wed, 21 Jan 2026 19:35:08 -0500 Subject: [PATCH] Moved hooks & patches into sperate folders --- init.rb | 12 +- .../header_footer_hook_listener.rb | 13 +- lib/hooks/issues_form_hook_listener.rb | 86 ++++++ lib/{ => hooks}/issues_save_hook_listener.rb | 32 ++- lib/hooks/issues_show_hook_listener.rb | 67 +++++ lib/hooks/projects_form_hook_listener.rb | 40 +++ lib/{ => hooks}/users_show_hook_listener.rb | 28 +- lib/{ => hooks}/view_hook_listener.rb | 12 +- lib/{ => hooks}/view_layouts_hook_listener.rb | 18 +- lib/issue_patch.rb | 110 -------- lib/issues_form_hook_listener.rb | 82 ------ lib/issues_show_hook_listener.rb | 63 ----- .../attachments_controller_patch.rb | 44 +-- lib/patches/issue_patch.rb | 115 ++++++++ lib/{ => patches}/issues_controller_patch.rb | 38 +-- lib/patches/pdf_patch.rb | 261 ++++++++++++++++++ .../project_patch.rb} | 82 +++--- lib/{ => patches}/query_patch.rb | 40 +-- lib/{ => patches}/time_entry_query_patch.rb | 36 +-- .../user_patch.rb} | 81 +++--- lib/pdf_patch.rb | 257 ----------------- lib/projects_form_hook_listener.rb | 36 --- 22 files changed, 811 insertions(+), 742 deletions(-) rename lib/{ => hooks}/header_footer_hook_listener.rb (80%) create mode 100644 lib/hooks/issues_form_hook_listener.rb rename lib/{ => hooks}/issues_save_hook_listener.rb (68%) create mode 100644 lib/hooks/issues_show_hook_listener.rb create mode 100644 lib/hooks/projects_form_hook_listener.rb rename lib/{ => hooks}/users_show_hook_listener.rb (65%) rename lib/{ => hooks}/view_hook_listener.rb (87%) rename lib/{ => hooks}/view_layouts_hook_listener.rb (70%) delete mode 100644 lib/issue_patch.rb delete mode 100644 lib/issues_form_hook_listener.rb delete mode 100644 lib/issues_show_hook_listener.rb rename lib/{ => patches}/attachments_controller_patch.rb (57%) create mode 100644 lib/patches/issue_patch.rb rename lib/{ => patches}/issues_controller_patch.rb (64%) create mode 100644 lib/patches/pdf_patch.rb rename lib/{user_patch.rb => patches/project_patch.rb} (70%) rename lib/{ => patches}/query_patch.rb (59%) rename lib/{ => patches}/time_entry_query_patch.rb (65%) rename lib/{project_patch.rb => patches/user_patch.rb} (66%) delete mode 100644 lib/pdf_patch.rb delete mode 100644 lib/projects_form_hook_listener.rb diff --git a/init.rb b/init.rb index 3372729..8b84924 100644 --- a/init.rb +++ b/init.rb @@ -45,8 +45,10 @@ Redmine::Plugin.register :redmine_qbo do end -# Dynamically load all Hooks & Patches -Dir::foreach(File.join(File.dirname(__FILE__), 'lib')) do |file| - next unless file.end_with?('.rb') - require_relative "lib/#{file.delete_suffix(".rb") }" -end +# Dynamically load all Hooks & Patches recursively +base_dir = File.join(File.dirname(__FILE__), 'lib') + +# '**' looks inside subdirectories, '*.rb' matches Ruby files +Dir.glob(File.join(base_dir, '**', '*.rb')).sort.each do |file| + require file +end \ No newline at end of file diff --git a/lib/header_footer_hook_listener.rb b/lib/hooks/header_footer_hook_listener.rb similarity index 80% rename from lib/header_footer_hook_listener.rb rename to lib/hooks/header_footer_hook_listener.rb index de70328..edb87aa 100644 --- a/lib/header_footer_hook_listener.rb +++ b/lib/hooks/header_footer_hook_listener.rb @@ -7,8 +7,13 @@ #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 HeaderFooterHookListener < Redmine::Hook::ViewListener - def view_layouts_base_body_bottom(context = {}) - return "" + +module Hooks + + class HeaderFooterHookListener < Redmine::Hook::ViewListener + def view_layouts_base_body_bottom(context = {}) + return "" + end end -end + +end \ No newline at end of file diff --git a/lib/hooks/issues_form_hook_listener.rb b/lib/hooks/issues_form_hook_listener.rb new file mode 100644 index 0000000..f93f741 --- /dev/null +++ b/lib/hooks/issues_form_hook_listener.rb @@ -0,0 +1,86 @@ +#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. + +module Hooks + + class IssuesFormHookListener < Redmine::Hook::ViewListener + + # Edit Issue Form + # Here we build the required form components before passing them to a partial view formatting. + def view_issues_form_details_bottom(context={}) + f = context[:form] + issue = context[:issue] + + # check project level customer ownership first + # This is done to preload customer information if the entire project is dedicated to a customer + if context[:project] + selected_customer = context[:project].customer ? context[:project].customer.id : nil + selected_vehicle = context[:project].vehicle ? context[:project].vehicle.id : nil + end + + # Check to see if the issue already belongs to a customer + selected_customer = issue.customer ? issue.customer.id : nil + selected_estimate = issue.estimate ? issue.estimate.id : nil + selected_vehicle = issue.vehicles_id ? issue.vehicles_id : nil + + # Gernerate edit.js link + js_link = issue.new_record? ? "updateIssueFrom('/projects/rmt/issues/new.js', this)" : "updateIssueFrom('/issues/#{issue.id}/edit.js', this)" + + # Load customer information + customer = Customer.find_by_id(selected_customer) if selected_customer + + # Customer Name Text Box with database backed autocomplete + search_customer = f.autocomplete_field :customer, + autocomplete_customer_name_customers_path, + :selected => selected_customer, + :onchange => js_link, + :update_elements => { + :id => '#issue_customer_id', + :value => '#issue_customer' + } + + # Customer ID - Hidden Field + customer_id = f.hidden_field :customer_id, + :id => "issue_customer_id", + :onchange => js_link + + # Load estimates & vehicles + if issue.customer + if customer.vehicles + vehicles = customer.vehicles.pluck(:name, :id) + else + vehicles = [nil].compact + end + estimates = customer.estimates.pluck(:doc_number, :id).sort! {|x, y| y <=> x} + else + vehicles = [nil].compact + estimates = [nil].compact + end + + # Generate the drop down list of quickbooks estimates & vehicles + select_estimate = f.select :estimate_id, estimates, :selected => selected_estimate, include_blank: true + vehicle = f.select :vehicles_id, vehicles, :selected => selected_vehicle, include_blank: true + + # Pass all prebuilt form components to our partial + context[:controller].send(:render_to_string, { + :partial => 'issues/form_hook', + locals: { + search_customer: search_customer, + customer_id: customer_id, + js_link: js_link, + select_estimate: select_estimate, + vehicle: vehicle + } + } + ) + end + end + +end \ No newline at end of file diff --git a/lib/issues_save_hook_listener.rb b/lib/hooks/issues_save_hook_listener.rb similarity index 68% rename from lib/issues_save_hook_listener.rb rename to lib/hooks/issues_save_hook_listener.rb index f67c9c1..e85756c 100644 --- a/lib/issues_save_hook_listener.rb +++ b/lib/hooks/issues_save_hook_listener.rb @@ -8,22 +8,26 @@ # #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 IssuesSaveHookListener < Redmine::Hook::ViewListener +module Hooks - # Called Before Issue Saved - def controller_issues_edit_before_save(context={}) - issue = context[:issue] - issue.subject = issue.subject.titleize - end + 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" + # Called Before Issue Saved + def controller_issues_edit_before_save(context={}) + issue = context[:issue] + issue.subject = issue.subject.titleize end + + # 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 -end +end \ No newline at end of file diff --git a/lib/hooks/issues_show_hook_listener.rb b/lib/hooks/issues_show_hook_listener.rb new file mode 100644 index 0000000..a7e2658 --- /dev/null +++ b/lib/hooks/issues_show_hook_listener.rb @@ -0,0 +1,67 @@ +#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. + +module Hooks + + class IssuesShowHookListener < Redmine::Hook::ViewListener + + # View Issue + # Display the quickbooks contact in the issue + def view_issues_show_details_bottom(context={}) + issue = context[:issue] + + # Check to see if there is a quickbooks user attached to the issue + if issue.customer + customer = link_to issue.customer.name, customer_path( issue.customer.id ) + end + + # Estimate Number + if issue.estimate + estimate = issue.estimate.doc_number + estimate_link = link_to estimate, estimate_path( issue.estimate.id ), :target => "_blank" + end + + # Invoice Number + invoice_link = "" + if issue.invoice_ids + issue.invoice_ids.each do |i| + invoice = Invoice.find i + invoice_link = invoice_link + link_to( invoice.doc_number, invoice_path( i ), :target => "_blank").to_s + " " + invoice_link = invoice_link.html_safe + end + end + + begin + v = Vehicle.find(issue.vehicles_id) + vehicle = link_to v.to_s, vehicle_path( v.id ) + vin = v.vin + notes = v.notes + rescue + #do nothing + end + + split_vin = vin.scan(/.{1,9}/) if vin + + context[:controller].send(:render_to_string, { + :partial => 'issues/show_details', + locals: { + customer: customer, + estimate_link: estimate_link, + invoice_link: invoice_link, + vehicle: vehicle, + split_vin: split_vin, + notes: notes + } + }) + end + + end + +end \ No newline at end of file diff --git a/lib/hooks/projects_form_hook_listener.rb b/lib/hooks/projects_form_hook_listener.rb new file mode 100644 index 0000000..184c9a5 --- /dev/null +++ b/lib/hooks/projects_form_hook_listener.rb @@ -0,0 +1,40 @@ +#The MIT License (MIT) +# +#Copyright (c) 2017 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. + +module Hooks + + class ProjectsFormHookListener < Redmine::Hook::ViewListener + + # Edit Project Form + def view_projects_form(context={}) + f = context[:form] + + # Check to see if there is a quickbooks user attached to the issue + selected_customer = context[:project].customer ? context[:project].customer : nil + selected_vehicle = context[:project].vehicle_id ? context[:project].vehicle_id : nil + + # Load customer information + customer = Customer.find_by_id(selected_customer) if selected_customer + search_customer = f.autocomplete_field :customer, autocomplete_customer_name_customers_path, :selected => selected_customer, :update_elements => {:id => '#project_customer_id', :value => '#project_customer'} + customer_id = f.hidden_field :customer_id, :id => "project_customer_id" + + if context[:project].customer + vehicles = customer.vehicles.pluck(:name, :id).sort! + else + vehicles = [nil].compact + end + + vehicle = f.select :vehicle_id, vehicles, :selected => selected_vehicle, include_blank: true + + return "

#{search_customer} #{customer_id}

#{vehicle}

" + end + end + +end \ No newline at end of file diff --git a/lib/users_show_hook_listener.rb b/lib/hooks/users_show_hook_listener.rb similarity index 65% rename from lib/users_show_hook_listener.rb rename to lib/hooks/users_show_hook_listener.rb index 025b889..5f0e2d4 100644 --- a/lib/users_show_hook_listener.rb +++ b/lib/hooks/users_show_hook_listener.rb @@ -8,18 +8,22 @@ # #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 UsersShowHookListener < Redmine::Hook::ViewListener +module Hooks - # View User - def view_users_form(context={}) - - # Update the users - #Employee.update_all - - # Check to see if there is a quickbooks user attached to the issue - @selected = context[:user].employee.id if context[:user].employee + class UsersShowHookListener < Redmine::Hook::ViewListener - # Generate the drop down list of quickbooks contacts - return "

#{context[:form].select :employee_id, Employee.all.pluck(:name, :id), :selected => @selected, include_blank: true}

" + # View User + def view_users_form(context={}) + + # Update the users + #Employee.update_all + + # Check to see if there is a quickbooks user attached to the issue + @selected = context[:user].employee.id if context[:user].employee + + # Generate the drop down list of quickbooks contacts + return "

#{context[:form].select :employee_id, Employee.all.pluck(:name, :id), :selected => @selected, include_blank: true}

" + end end -end + +end \ No newline at end of file diff --git a/lib/view_hook_listener.rb b/lib/hooks/view_hook_listener.rb similarity index 87% rename from lib/view_hook_listener.rb rename to lib/hooks/view_hook_listener.rb index f7cb0fe..3f1ba5e 100644 --- a/lib/view_hook_listener.rb +++ b/lib/hooks/view_hook_listener.rb @@ -8,8 +8,12 @@ # #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 +module Hooks - render_on :view_layouts_base_sidebar, :partial => "qbo/sidebar" - -end + class ViewHookListener < Redmine::Hook::ViewListener + + render_on :view_layouts_base_sidebar, :partial => "qbo/sidebar" + + end + +end \ No newline at end of file diff --git a/lib/view_layouts_hook_listener.rb b/lib/hooks/view_layouts_hook_listener.rb similarity index 70% rename from lib/view_layouts_hook_listener.rb rename to lib/hooks/view_layouts_hook_listener.rb index 29fdbba..01271d3 100644 --- a/lib/view_layouts_hook_listener.rb +++ b/lib/hooks/view_layouts_hook_listener.rb @@ -8,14 +8,18 @@ # #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 ViewLayoutsHookListener < Redmine::Hook::ViewListener +module Hooks + + class ViewLayoutsHookListener < Redmine::Hook::ViewListener + + # Load the javascript to support the autocomplete forms + def view_layouts_base_html_head(context = {}) + js = javascript_include_tag 'application.js', :plugin => 'redmine_qbo' + js += javascript_include_tag 'autocomplete-rails.js', :plugin => 'redmine_qbo' + js += javascript_include_tag 'checkbox_controller.js', :plugin => 'redmine_qbo' + return js + end - # Load the javascript to support the autocomplete forms - def view_layouts_base_html_head(context = {}) - js = javascript_include_tag 'application.js', :plugin => 'redmine_qbo' - js += javascript_include_tag 'autocomplete-rails.js', :plugin => 'redmine_qbo' - js += javascript_include_tag 'checkbox_controller.js', :plugin => 'redmine_qbo' - return js end end \ No newline at end of file diff --git a/lib/issue_patch.rb b/lib/issue_patch.rb deleted file mode 100644 index d5293b8..0000000 --- a/lib/issue_patch.rb +++ /dev/null @@ -1,110 +0,0 @@ -#The MIT License (MIT) -# -#Copyright (c) 2024 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 'issue' - -# Patches Redmine's Issues dynamically. -# Adds a relationships -module IssuePatch - - def self.included(base) # :nodoc: - base.extend(ClassMethods) - - base.send(:include, InstanceMethods) - - # Same as typing in the class - base.class_eval do - belongs_to :customer, primary_key: :id - belongs_to :customer_token, primary_key: :id - belongs_to :estimate, primary_key: :id - has_and_belongs_to_many :invoices - belongs_to :vehicle, primary_key: :id - end - - end - - module ClassMethods - - end - - module InstanceMethods - - # Create billable time entries - def bill_time - - # Check to see if we have everything we need to bill the customer - return if assigned_to.nil? - return unless Qbo.first - return unless customer - - # Get unbilled time entries - spent_time = time_entries.where(billed: [false, nil]) - spent_hours ||= spent_time.sum(:hours) || 0 - - if spent_hours > 0 then - - # Prepare to create a new Time Activity - qbo = Qbo.first - qbo.perform_authenticated_request do |access_token| - time_service = Quickbooks::Service::TimeActivity.new(:company_id => qbo.realm_id, :access_token => access_token) - item_service = Quickbooks::Service::Item.new(:company_id => qbo.realm_id, :access_token => access_token) - time_entry = Quickbooks::Model::TimeActivity.new - - # Lets total up each activity before billing. - # This will simpify the invoicing with a single billable time entry per time activity - h = Hash.new(0) - spent_time.each do |entry| - h[entry.activity.name] += entry.hours - # update time entries billed status - entry.billed = true - entry.save - end - - # Now letes upload our totals for each activity as their own billable time entry - h.each do |key, val| - - # Convert float spent time to hours and minutes - hours = val.to_i - minutesDecimal = (( val - hours) * 60) - minutes = minutesDecimal.to_i - - # Lets match the activity to an qbo item - item = item_service.query("SELECT * FROM Item WHERE Name = '#{key}' ").first - next if item.nil? - - # Create the new billable time entry and upload it - time_entry.description = "#{tracker} ##{id}: #{subject} #{"(Partial @ #{done_ratio}%)" if not closed?}" - time_entry.employee_id = assigned_to.employee_id - time_entry.customer_id = customer_id - time_entry.billable_status = "Billable" - time_entry.hours = hours - time_entry.minutes = minutes - time_entry.name_of = "Employee" - time_entry.txn_date = Date.today - time_entry.hourly_rate = item.unit_price - time_entry.item_id = item.id - time_entry.start_time = start_date - time_entry.end_time = Time.now - time_service.create(time_entry) - end - end - end - end - end - - # Create a shareable link for a customer - def share_token - CustomerToken.get_token self - end - -end - -# Add module to Issue -Issue.send(:include, IssuePatch) diff --git a/lib/issues_form_hook_listener.rb b/lib/issues_form_hook_listener.rb deleted file mode 100644 index 2a5c2e7..0000000 --- a/lib/issues_form_hook_listener.rb +++ /dev/null @@ -1,82 +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 IssuesFormHookListener < Redmine::Hook::ViewListener - - # Edit Issue Form - # Here we build the required form components before passing them to a partial view formatting. - def view_issues_form_details_bottom(context={}) - f = context[:form] - issue = context[:issue] - - # check project level customer ownership first - # This is done to preload customer information if the entire project is dedicated to a customer - if context[:project] - selected_customer = context[:project].customer ? context[:project].customer.id : nil - selected_vehicle = context[:project].vehicle ? context[:project].vehicle.id : nil - end - - # Check to see if the issue already belongs to a customer - selected_customer = issue.customer ? issue.customer.id : nil - selected_estimate = issue.estimate ? issue.estimate.id : nil - selected_vehicle = issue.vehicles_id ? issue.vehicles_id : nil - - # Gernerate edit.js link - js_link = issue.new_record? ? "updateIssueFrom('/projects/rmt/issues/new.js', this)" : "updateIssueFrom('/issues/#{issue.id}/edit.js', this)" - - # Load customer information - customer = Customer.find_by_id(selected_customer) if selected_customer - - # Customer Name Text Box with database backed autocomplete - search_customer = f.autocomplete_field :customer, - autocomplete_customer_name_customers_path, - :selected => selected_customer, - :onchange => js_link, - :update_elements => { - :id => '#issue_customer_id', - :value => '#issue_customer' - } - - # Customer ID - Hidden Field - customer_id = f.hidden_field :customer_id, - :id => "issue_customer_id", - :onchange => js_link - - # Load estimates & vehicles - if issue.customer - if customer.vehicles - vehicles = customer.vehicles.pluck(:name, :id) - else - vehicles = [nil].compact - end - estimates = customer.estimates.pluck(:doc_number, :id).sort! {|x, y| y <=> x} - else - vehicles = [nil].compact - estimates = [nil].compact - end - - # Generate the drop down list of quickbooks estimates & vehicles - select_estimate = f.select :estimate_id, estimates, :selected => selected_estimate, include_blank: true - vehicle = f.select :vehicles_id, vehicles, :selected => selected_vehicle, include_blank: true - - # Pass all prebuilt form components to our partial - context[:controller].send(:render_to_string, { - :partial => 'issues/form_hook', - locals: { - search_customer: search_customer, - customer_id: customer_id, - js_link: js_link, - select_estimate: select_estimate, - vehicle: vehicle - } - } - ) - end -end diff --git a/lib/issues_show_hook_listener.rb b/lib/issues_show_hook_listener.rb deleted file mode 100644 index 8a61e27..0000000 --- a/lib/issues_show_hook_listener.rb +++ /dev/null @@ -1,63 +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 IssuesShowHookListener < Redmine::Hook::ViewListener - - # View Issue - # Display the quickbooks contact in the issue - def view_issues_show_details_bottom(context={}) - issue = context[:issue] - - # Check to see if there is a quickbooks user attached to the issue - if issue.customer - customer = link_to issue.customer.name, customer_path( issue.customer.id ) - end - - # Estimate Number - if issue.estimate - estimate = issue.estimate.doc_number - estimate_link = link_to estimate, estimate_path( issue.estimate.id ), :target => "_blank" - end - - # Invoice Number - invoice_link = "" - if issue.invoice_ids - issue.invoice_ids.each do |i| - invoice = Invoice.find i - invoice_link = invoice_link + link_to( invoice.doc_number, invoice_path( i ), :target => "_blank").to_s + " " - invoice_link = invoice_link.html_safe - end - end - - begin - v = Vehicle.find(issue.vehicles_id) - vehicle = link_to v.to_s, vehicle_path( v.id ) - vin = v.vin - notes = v.notes - rescue - #do nothing - end - - split_vin = vin.scan(/.{1,9}/) if vin - - context[:controller].send(:render_to_string, { - :partial => 'issues/show_details', - locals: { - customer: customer, - estimate_link: estimate_link, - invoice_link: invoice_link, - vehicle: vehicle, - split_vin: split_vin, - notes: notes - } - }) - end - -end diff --git a/lib/attachments_controller_patch.rb b/lib/patches/attachments_controller_patch.rb similarity index 57% rename from lib/attachments_controller_patch.rb rename to lib/patches/attachments_controller_patch.rb index 69d2a7f..e43b690 100644 --- a/lib/attachments_controller_patch.rb +++ b/lib/patches/attachments_controller_patch.rb @@ -10,33 +10,37 @@ require_dependency 'attachments_controller' -module AttachmentsControllerPatch +module Patches - def self.included(base) + module AttachmentsControllerPatch - base.class_eval do + def self.included(base) - # 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 + base.class_eval do + + # 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 - require_login if Setting.login_required? end - + end - end + end -end + # Add module to AttachmentsController + AttachmentsController.send(:include, AttachmentsControllerPatch) -# Add module to AttachmentsController -AttachmentsController.send(:include, AttachmentsControllerPatch) +end \ No newline at end of file diff --git a/lib/patches/issue_patch.rb b/lib/patches/issue_patch.rb new file mode 100644 index 0000000..4e7ce37 --- /dev/null +++ b/lib/patches/issue_patch.rb @@ -0,0 +1,115 @@ +#The MIT License (MIT) +# +#Copyright (c) 2024 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 'issue' + +module Patches + + + # Patches Redmine's Issues dynamically. + # Adds a relationships + module IssuePatch + + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.send(:include, InstanceMethods) + + # Same as typing in the class + base.class_eval do + belongs_to :customer, primary_key: :id + belongs_to :customer_token, primary_key: :id + belongs_to :estimate, primary_key: :id + has_and_belongs_to_many :invoices + belongs_to :vehicle, primary_key: :id + end + + end + + module ClassMethods + + end + + module InstanceMethods + + # Create billable time entries + def bill_time + + # Check to see if we have everything we need to bill the customer + return if assigned_to.nil? + return unless Qbo.first + return unless customer + + # Get unbilled time entries + spent_time = time_entries.where(billed: [false, nil]) + spent_hours ||= spent_time.sum(:hours) || 0 + + if spent_hours > 0 then + + # Prepare to create a new Time Activity + qbo = Qbo.first + qbo.perform_authenticated_request do |access_token| + time_service = Quickbooks::Service::TimeActivity.new(:company_id => qbo.realm_id, :access_token => access_token) + item_service = Quickbooks::Service::Item.new(:company_id => qbo.realm_id, :access_token => access_token) + time_entry = Quickbooks::Model::TimeActivity.new + + # Lets total up each activity before billing. + # This will simpify the invoicing with a single billable time entry per time activity + h = Hash.new(0) + spent_time.each do |entry| + h[entry.activity.name] += entry.hours + # update time entries billed status + entry.billed = true + entry.save + end + + # Now letes upload our totals for each activity as their own billable time entry + h.each do |key, val| + + # Convert float spent time to hours and minutes + hours = val.to_i + minutesDecimal = (( val - hours) * 60) + minutes = minutesDecimal.to_i + + # Lets match the activity to an qbo item + item = item_service.query("SELECT * FROM Item WHERE Name = '#{key}' ").first + next if item.nil? + + # Create the new billable time entry and upload it + time_entry.description = "#{tracker} ##{id}: #{subject} #{"(Partial @ #{done_ratio}%)" if not closed?}" + time_entry.employee_id = assigned_to.employee_id + time_entry.customer_id = customer_id + time_entry.billable_status = "Billable" + time_entry.hours = hours + time_entry.minutes = minutes + time_entry.name_of = "Employee" + time_entry.txn_date = Date.today + time_entry.hourly_rate = item.unit_price + time_entry.item_id = item.id + time_entry.start_time = start_date + time_entry.end_time = Time.now + time_service.create(time_entry) + end + end + end + end + end + + # Create a shareable link for a customer + def share_token + CustomerToken.get_token self + end + + end + + # Add module to Issue + Issue.send(:include, IssuePatch) + +end \ No newline at end of file diff --git a/lib/issues_controller_patch.rb b/lib/patches/issues_controller_patch.rb similarity index 64% rename from lib/issues_controller_patch.rb rename to lib/patches/issues_controller_patch.rb index 85b3d70..083cbdb 100644 --- a/lib/issues_controller_patch.rb +++ b/lib/patches/issues_controller_patch.rb @@ -9,26 +9,30 @@ #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 Patches - 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 + module IssuesControllerPatch - def self.included(base) - - base.class_eval do - helper Helper + 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 - end + def self.included(base) -end + base.class_eval do + helper Helper + end -# Add module to IssuessController -IssuesController.send(:include, IssuesControllerPatch) + end + + end + + # Add module to IssuessController + IssuesController.send(:include, IssuesControllerPatch) + +end \ No newline at end of file diff --git a/lib/patches/pdf_patch.rb b/lib/patches/pdf_patch.rb new file mode 100644 index 0000000..5bd3ec2 --- /dev/null +++ b/lib/patches/pdf_patch.rb @@ -0,0 +1,261 @@ +#The MIT License (MIT) +# +#Copyright (c) 2024 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 'redmine/export/pdf' +require_dependency 'redmine/export/pdf/issues_pdf_helper' + +module Patches + + module PdfPatch + + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + alias_method :issue_to_pdf, :issue_to_pdf_with_patch + alias_method :issue_to_pdf_with_patch, :issue_to_pdf + end + end + + module InstanceMethods + + def issue_to_pdf_with_patch(issue, assoc={}) + pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language) + pdf.set_title("#{issue.project} - #{issue.tracker} ##{issue.id}") + pdf.alias_nb_pages + pdf.footer_date = format_date(Date.today) + pdf.add_page + pdf.SetFontStyle('B',11) + buf = "#{issue.project} - #{issue.tracker} ##{issue.id}" + pdf.RDMMultiCell(190, 5, buf) + pdf.SetFontStyle('',8) + base_x = pdf.get_x + i = 1 + issue.ancestors.visible.each do |ancestor| + pdf.set_x(base_x + i) + buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" + pdf.RDMMultiCell(190 - i, 5, buf) + i += 1 if i < 35 + end + pdf.SetFontStyle('B',11) + pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s) + pdf.SetFontStyle('',8) + pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") + pdf.ln + + customer = issue.customer.name if issue.customer + left = [] + left << [l(:field_status), issue.status] + left << [l(:field_priority), issue.priority] + left << [l(:field_customer), customer] + left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') + #left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') + #left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') + + v = Vehicle.find_by_id(issue.vehicles_id) + vehicle = v ? v.to_s : nil + vin = v ? v.vin : nil + notes = v ? v.notes : nil + left << [l(:field_vehicles), vehicle] + left << [l(:field_vin), vin ? vin.gsub(/(.{9})/, '\1 ') : nil] + #left << [l(:field_notes), notes] + + right = [] + right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') + right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') + right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') + right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') + right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) + right << [l(:field_notes), notes] + + rows = left.size > right.size ? left.size : right.size + while left.size < rows + left << nil + end + while right.size < rows + right << nil + end + + half = (issue.visible_custom_field_values.size / 2.0).ceil + issue.visible_custom_field_values.each_with_index do |custom_value, i| + (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)] + end + + if pdf.get_rtl + border_first_top = 'RT' + border_last_top = 'LT' + border_first = 'R' + border_last = 'L' + else + border_first_top = 'LT' + border_last_top = 'RT' + border_first = 'L' + border_last = 'R' + end + + rows = left.size > right.size ? left.size : right.size + rows.times do |i| + heights = [] + pdf.SetFontStyle('B',9) + item = left[i] + heights << pdf.get_string_height(35, item ? "#{item.first}:" : "") + item = right[i] + heights << pdf.get_string_height(35, item ? "#{item.first}:" : "") + pdf.SetFontStyle('',9) + item = left[i] + heights << pdf.get_string_height(60, item ? item.last.to_s : "") + item = right[i] + heights << pdf.get_string_height(60, item ? item.last.to_s : "") + height = heights.max + + item = left[i] + pdf.SetFontStyle('B',9) + pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0) + pdf.SetFontStyle('',9) + pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0) + + item = right[i] + pdf.SetFontStyle('B',9) + pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0) + pdf.SetFontStyle('',9) + pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2) + + pdf.set_x(base_x) + end + + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) + pdf.SetFontStyle('',9) + + # Set resize image scale + pdf.set_image_scale(1.6) + text = textilizable(issue, :description, + :only_path => false, + :edit_section_links => false, + :headings => false, + :inline_attachments => false + ) + pdf.RDMwriteFormattedCell(35+155, 5, '', '', text, issue.attachments, "LRB") + + unless issue.leaf? + truncate_length = (!is_cjk? ? 90 : 65) + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") + pdf.ln + issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| + buf = "#{child.tracker} # #{child.id}: #{child.subject}". + truncate(truncate_length) + level = 10 if level >= 10 + pdf.SetFontStyle('',8) + pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, border_first) + pdf.SetFontStyle('B',8) + pdf.RDMCell(20,5, child.status.to_s, border_last) + pdf.ln + end + end + + relations = issue.relations.select { |r| r.other_issue(issue).visible? } + unless relations.empty? + truncate_length = (!is_cjk? ? 80 : 60) + pdf.SetFontStyle('B',9) + pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") + pdf.ln + relations.each do |relation| + buf = relation.to_s(issue) {|other| + text = "" + if Setting.cross_project_issue_relations? + text += "#{relation.other_issue(issue).project} - " + end + text += "#{other.tracker} ##{other.id}: #{other.subject}" + text + } + buf = buf.truncate(truncate_length) + pdf.SetFontStyle('', 8) + pdf.RDMCell(35+155-60, 5, buf, border_first) + pdf.SetFontStyle('B',8) + pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "") + pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "") + pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), border_last) + pdf.ln + end + end + pdf.RDMCell(190,5, "", "T") + pdf.ln + + if issue.changesets.any? && + User.current.allowed_to?(:view_changesets, issue.project) + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_associated_revisions), "B") + pdf.ln + for changeset in issue.changesets + pdf.SetFontStyle('B',8) + csstr = "#{l(:label_revision)} #{changeset.format_identifier} - " + csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s + pdf.RDMCell(190, 5, csstr) + pdf.ln + unless changeset.comments.blank? + pdf.SetFontStyle('',8) + pdf.RDMwriteHTMLCell(190,5,'','', + changeset.comments.to_s, issue.attachments, "") + end + pdf.ln + end + end + + if assoc[:journals].present? + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_history), "B") + pdf.ln + assoc[:journals].each do |journal| + pdf.SetFontStyle('B',8) + title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}" + title << " (#{l(:field_private_notes)})" if journal.private_notes? + pdf.RDMCell(190,5, title) + pdf.ln + pdf.SetFontStyle('I',8) + details_to_strings(journal.visible_details, true).each do |string| + pdf.RDMMultiCell(190,5, "- " + string) + end + if journal.notes? + pdf.ln unless journal.details.empty? + pdf.SetFontStyle('',8) + text = textilizable(journal, :notes, + :only_path => false, + :edit_section_links => false, + :headings => false, + :inline_attachments => false + ) + pdf.RDMwriteFormattedCell(190,5,'','', text, issue.attachments, "") + end + pdf.ln + end + end + + if issue.attachments.any? + pdf.SetFontStyle('B',9) + pdf.RDMCell(190,5, l(:label_attachment_plural), "B") + pdf.ln + for attachment in issue.attachments + pdf.SetFontStyle('',8) + pdf.RDMCell(80,5, attachment.filename) + pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.RDMCell(65,5, attachment.author.name,0,0,"R") + pdf.ln + end + end + pdf.output + end + + end + end + + Redmine::Export::PDF::IssuesPdfHelper.send(:include, PdfPatch) + +end \ No newline at end of file diff --git a/lib/user_patch.rb b/lib/patches/project_patch.rb similarity index 70% rename from lib/user_patch.rb rename to lib/patches/project_patch.rb index 5b78a74..88c591f 100644 --- a/lib/user_patch.rb +++ b/lib/patches/project_patch.rb @@ -1,38 +1,44 @@ -#The MIT License (MIT) -# -#Copyright (c) 2024 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 'user' - -# Patches Redmine's User dynamically. -# Adds a relationships -module UserPatch - def self.included(base) # :nodoc: - base.extend(ClassMethods) - - base.send(:include, InstanceMethods) - - # Same as typing in the class - base.class_eval do - belongs_to :employee, primary_key: :id - end - end - - module ClassMethods - - end - - module InstanceMethods - - end - -end - -# Add module to Issue -User.send(:include, UserPatch) +#The MIT License (MIT) +# +#Copyright (c) 2024 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 'project' + +module Patches + + # Patches Redmine's Projects dynamically. + # Adds a relationships + module ProjectPatch + + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.send(:include, InstanceMethods) + + # Same as typing in the class + base.class_eval do + belongs_to :customer, primary_key: :id + belongs_to :vehicle, primary_key: :id + end + end + + end + + module ClassMethods + + end + + module InstanceMethods + + end + + # Add module to Project + Project.send(:include, ProjectPatch) + +end \ No newline at end of file diff --git a/lib/query_patch.rb b/lib/patches/query_patch.rb similarity index 59% rename from lib/query_patch.rb rename to lib/patches/query_patch.rb index b4a0e56..8dbf5eb 100644 --- a/lib/query_patch.rb +++ b/lib/patches/query_patch.rb @@ -10,25 +10,29 @@ require_dependency 'issue_query' -module QueryPatch - - # Add qbo options to the aviable columns - def available_columns - unless @available_columns - @available_columns = self.class.available_columns.dup - @available_columns << QueryColumn.new(:customer, :sortable => "#{Issue.table_name}.customer_id", :groupable => true, :caption => :field_customer) - @available_columns << QueryColumn.new(:billed, :sortable => "#{TimeEntry.table_name}.billed", :groupable => true, :caption => :field_billed) +module Patches + + module QueryPatch + + # Add qbo options to the aviable columns + def available_columns + unless @available_columns + @available_columns = self.class.available_columns.dup + @available_columns << QueryColumn.new(:customer, :sortable => "#{Issue.table_name}.customer_id", :groupable => true, :caption => :field_customer) + @available_columns << QueryColumn.new(:billed, :sortable => "#{TimeEntry.table_name}.billed", :groupable => true, :caption => :field_billed) + end + super end - super - end - - # Add customers to filters - def initialize_available_filters - #add_available_filter "customer", :type => :text - super + + # Add customers to filters + def initialize_available_filters + #add_available_filter "customer", :type => :text + super + end + end -end + # Add module to Issue + IssueQuery.send(:prepend, QueryPatch) -# Add module to Issue -IssueQuery.send(:prepend, QueryPatch) +end \ No newline at end of file diff --git a/lib/time_entry_query_patch.rb b/lib/patches/time_entry_query_patch.rb similarity index 65% rename from lib/time_entry_query_patch.rb rename to lib/patches/time_entry_query_patch.rb index dce2bac..591aade 100644 --- a/lib/time_entry_query_patch.rb +++ b/lib/patches/time_entry_query_patch.rb @@ -10,24 +10,28 @@ require_dependency 'time_entry_query' -module TimeEntryQueryPatch +module Patches - # Add QBO options to columns - def available_columns - unless @available_columns - @available_columns = self.class.available_columns.dup - @available_columns << QueryColumn.new(:billed, :sortable => "#{TimeEntry.table_name}.name", :groupable => true, :caption => :field_billed) + module TimeEntryQueryPatch + + # Add QBO options to columns + def available_columns + unless @available_columns + @available_columns = self.class.available_columns.dup + @available_columns << QueryColumn.new(:billed, :sortable => "#{TimeEntry.table_name}.name", :groupable => true, :caption => :field_billed) + end + super end - super - end - - # Add QBO options to the filter - def initialize_available_filters - add_available_filter "billed", :type => :boolean - super + + # Add QBO options to the filter + def initialize_available_filters + add_available_filter "billed", :type => :boolean + super + end + end -end + # Add module to TimeEntryQuery + TimeEntryQuery.send(:prepend, QueryPatch) -# Add module to TimeEntryQuery -TimeEntryQuery.send(:prepend, QueryPatch) +end \ No newline at end of file diff --git a/lib/project_patch.rb b/lib/patches/user_patch.rb similarity index 66% rename from lib/project_patch.rb rename to lib/patches/user_patch.rb index 925bde6..d717c77 100644 --- a/lib/project_patch.rb +++ b/lib/patches/user_patch.rb @@ -1,39 +1,42 @@ -#The MIT License (MIT) -# -#Copyright (c) 2024 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 'project' - -# Patches Redmine's Projects dynamically. -# Adds a relationships -module ProjectPatch - - def self.included(base) # :nodoc: - base.extend(ClassMethods) - - base.send(:include, InstanceMethods) - - # Same as typing in the class - base.class_eval do - belongs_to :customer, primary_key: :id - belongs_to :vehicle, primary_key: :id - end - end -end - -module ClassMethods - -end - -module InstanceMethods - -end - -# Add module to Project -Project.send(:include, ProjectPatch) +#The MIT License (MIT) +# +#Copyright (c) 2024 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 'user' + +module Patches + + # Patches Redmine's User dynamically. + # Adds a relationships + module UserPatch + def self.included(base) # :nodoc: + base.extend(ClassMethods) + + base.send(:include, InstanceMethods) + + # Same as typing in the class + base.class_eval do + belongs_to :employee, primary_key: :id + end + end + + module ClassMethods + + end + + module InstanceMethods + + end + + end + + # Add module to Issue + User.send(:include, UserPatch) + +end \ No newline at end of file diff --git a/lib/pdf_patch.rb b/lib/pdf_patch.rb deleted file mode 100644 index 274bf57..0000000 --- a/lib/pdf_patch.rb +++ /dev/null @@ -1,257 +0,0 @@ -#The MIT License (MIT) -# -#Copyright (c) 2024 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 'redmine/export/pdf' -require_dependency 'redmine/export/pdf/issues_pdf_helper' - -module PdfPatch - - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - alias_method :issue_to_pdf, :issue_to_pdf_with_patch - alias_method :issue_to_pdf_with_patch, :issue_to_pdf - end - end - - module InstanceMethods - - def issue_to_pdf_with_patch(issue, assoc={}) - pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language) - pdf.set_title("#{issue.project} - #{issue.tracker} ##{issue.id}") - pdf.alias_nb_pages - pdf.footer_date = format_date(Date.today) - pdf.add_page - pdf.SetFontStyle('B',11) - buf = "#{issue.project} - #{issue.tracker} ##{issue.id}" - pdf.RDMMultiCell(190, 5, buf) - pdf.SetFontStyle('',8) - base_x = pdf.get_x - i = 1 - issue.ancestors.visible.each do |ancestor| - pdf.set_x(base_x + i) - buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" - pdf.RDMMultiCell(190 - i, 5, buf) - i += 1 if i < 35 - end - pdf.SetFontStyle('B',11) - pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s) - pdf.SetFontStyle('',8) - pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") - pdf.ln - - customer = issue.customer.name if issue.customer - left = [] - left << [l(:field_status), issue.status] - left << [l(:field_priority), issue.priority] - left << [l(:field_customer), customer] - left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') - #left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') - #left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') - - v = Vehicle.find_by_id(issue.vehicles_id) - vehicle = v ? v.to_s : nil - vin = v ? v.vin : nil - notes = v ? v.notes : nil - left << [l(:field_vehicles), vehicle] - left << [l(:field_vin), vin ? vin.gsub(/(.{9})/, '\1 ') : nil] - #left << [l(:field_notes), notes] - - right = [] - right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') - right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') - right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') - right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') - right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) - right << [l(:field_notes), notes] - - rows = left.size > right.size ? left.size : right.size - while left.size < rows - left << nil - end - while right.size < rows - right << nil - end - - half = (issue.visible_custom_field_values.size / 2.0).ceil - issue.visible_custom_field_values.each_with_index do |custom_value, i| - (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)] - end - - if pdf.get_rtl - border_first_top = 'RT' - border_last_top = 'LT' - border_first = 'R' - border_last = 'L' - else - border_first_top = 'LT' - border_last_top = 'RT' - border_first = 'L' - border_last = 'R' - end - - rows = left.size > right.size ? left.size : right.size - rows.times do |i| - heights = [] - pdf.SetFontStyle('B',9) - item = left[i] - heights << pdf.get_string_height(35, item ? "#{item.first}:" : "") - item = right[i] - heights << pdf.get_string_height(35, item ? "#{item.first}:" : "") - pdf.SetFontStyle('',9) - item = left[i] - heights << pdf.get_string_height(60, item ? item.last.to_s : "") - item = right[i] - heights << pdf.get_string_height(60, item ? item.last.to_s : "") - height = heights.max - - item = left[i] - pdf.SetFontStyle('B',9) - pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0) - pdf.SetFontStyle('',9) - pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0) - - item = right[i] - pdf.SetFontStyle('B',9) - pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0) - pdf.SetFontStyle('',9) - pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2) - - pdf.set_x(base_x) - end - - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) - pdf.SetFontStyle('',9) - - # Set resize image scale - pdf.set_image_scale(1.6) - text = textilizable(issue, :description, - :only_path => false, - :edit_section_links => false, - :headings => false, - :inline_attachments => false - ) - pdf.RDMwriteFormattedCell(35+155, 5, '', '', text, issue.attachments, "LRB") - - unless issue.leaf? - truncate_length = (!is_cjk? ? 90 : 65) - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") - pdf.ln - issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| - buf = "#{child.tracker} # #{child.id}: #{child.subject}". - truncate(truncate_length) - level = 10 if level >= 10 - pdf.SetFontStyle('',8) - pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, border_first) - pdf.SetFontStyle('B',8) - pdf.RDMCell(20,5, child.status.to_s, border_last) - pdf.ln - end - end - - relations = issue.relations.select { |r| r.other_issue(issue).visible? } - unless relations.empty? - truncate_length = (!is_cjk? ? 80 : 60) - pdf.SetFontStyle('B',9) - pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") - pdf.ln - relations.each do |relation| - buf = relation.to_s(issue) {|other| - text = "" - if Setting.cross_project_issue_relations? - text += "#{relation.other_issue(issue).project} - " - end - text += "#{other.tracker} ##{other.id}: #{other.subject}" - text - } - buf = buf.truncate(truncate_length) - pdf.SetFontStyle('', 8) - pdf.RDMCell(35+155-60, 5, buf, border_first) - pdf.SetFontStyle('B',8) - pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "") - pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "") - pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), border_last) - pdf.ln - end - end - pdf.RDMCell(190,5, "", "T") - pdf.ln - - if issue.changesets.any? && - User.current.allowed_to?(:view_changesets, issue.project) - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_associated_revisions), "B") - pdf.ln - for changeset in issue.changesets - pdf.SetFontStyle('B',8) - csstr = "#{l(:label_revision)} #{changeset.format_identifier} - " - csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s - pdf.RDMCell(190, 5, csstr) - pdf.ln - unless changeset.comments.blank? - pdf.SetFontStyle('',8) - pdf.RDMwriteHTMLCell(190,5,'','', - changeset.comments.to_s, issue.attachments, "") - end - pdf.ln - end - end - - if assoc[:journals].present? - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_history), "B") - pdf.ln - assoc[:journals].each do |journal| - pdf.SetFontStyle('B',8) - title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}" - title << " (#{l(:field_private_notes)})" if journal.private_notes? - pdf.RDMCell(190,5, title) - pdf.ln - pdf.SetFontStyle('I',8) - details_to_strings(journal.visible_details, true).each do |string| - pdf.RDMMultiCell(190,5, "- " + string) - end - if journal.notes? - pdf.ln unless journal.details.empty? - pdf.SetFontStyle('',8) - text = textilizable(journal, :notes, - :only_path => false, - :edit_section_links => false, - :headings => false, - :inline_attachments => false - ) - pdf.RDMwriteFormattedCell(190,5,'','', text, issue.attachments, "") - end - pdf.ln - end - end - - if issue.attachments.any? - pdf.SetFontStyle('B',9) - pdf.RDMCell(190,5, l(:label_attachment_plural), "B") - pdf.ln - for attachment in issue.attachments - pdf.SetFontStyle('',8) - pdf.RDMCell(80,5, attachment.filename) - pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") - pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") - pdf.RDMCell(65,5, attachment.author.name,0,0,"R") - pdf.ln - end - end - pdf.output - end - - end -end - -Redmine::Export::PDF::IssuesPdfHelper.send(:include, PdfPatch) diff --git a/lib/projects_form_hook_listener.rb b/lib/projects_form_hook_listener.rb deleted file mode 100644 index ef04179..0000000 --- a/lib/projects_form_hook_listener.rb +++ /dev/null @@ -1,36 +0,0 @@ -#The MIT License (MIT) -# -#Copyright (c) 2017 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 ProjectsFormHookListener < Redmine::Hook::ViewListener - - # Edit Project Form - def view_projects_form(context={}) - f = context[:form] - - # Check to see if there is a quickbooks user attached to the issue - selected_customer = context[:project].customer ? context[:project].customer : nil - selected_vehicle = context[:project].vehicle_id ? context[:project].vehicle_id : nil - - # Load customer information - customer = Customer.find_by_id(selected_customer) if selected_customer - search_customer = f.autocomplete_field :customer, autocomplete_customer_name_customers_path, :selected => selected_customer, :update_elements => {:id => '#project_customer_id', :value => '#project_customer'} - customer_id = f.hidden_field :customer_id, :id => "project_customer_id" - - if context[:project].customer - vehicles = customer.vehicles.pluck(:name, :id).sort! - else - vehicles = [nil].compact - end - - vehicle = f.select :vehicle_id, vehicles, :selected => selected_vehicle, include_blank: true - - return "

#{search_customer} #{customer_id}

#{vehicle}

" - end -end