Initial Redmine tooling and local plugin forks
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
ActionDispatch::Callbacks.to_prepare do
|
||||
require 'redmine_helpdesk/patches/issues_controller_patch'
|
||||
require 'redmine_helpdesk/patches/journals_controller_patch'
|
||||
require 'redmine_helpdesk/patches/attachments_controller_patch'
|
||||
require 'redmine_helpdesk/patches/issue_patch'
|
||||
require 'redmine_helpdesk/patches/journal_patch'
|
||||
require 'redmine_helpdesk/patches/contact_patch'
|
||||
require 'redmine_helpdesk/patches/issue_query_patch'
|
||||
require 'redmine_helpdesk/patches/time_report_patch'
|
||||
require 'redmine_helpdesk/patches/queries_helper_patch'
|
||||
require 'redmine_helpdesk/patches/projects_helper_patch'
|
||||
require 'redmine_helpdesk/patches/contacts_helper_patch'
|
||||
require 'redmine_helpdesk/patches/application_helper_patch'
|
||||
require 'redmine_helpdesk/patches/mail_handler_patch'
|
||||
require 'redmine_helpdesk/patches/compatibility_patch'
|
||||
require 'redmine_helpdesk/patches/contact_query_patch'
|
||||
|
||||
require 'redmine_helpdesk/hooks/view_layouts_hook'
|
||||
require 'redmine_helpdesk/hooks/view_issues_hook'
|
||||
require 'redmine_helpdesk/hooks/view_projects_hook'
|
||||
require 'redmine_helpdesk/hooks/view_contacts_hook'
|
||||
require 'redmine_helpdesk/hooks/view_journals_hook'
|
||||
require 'redmine_helpdesk/hooks/controller_contacts_duplicates_hook'
|
||||
require 'redmine_helpdesk/hooks/helper_issues_hook'
|
||||
require 'redmine_helpdesk/hooks/issues_controller_hook'
|
||||
|
||||
require 'redmine_helpdesk/wiki_macros/helpdesk_wiki_macro'
|
||||
end
|
||||
|
||||
class HelpdeskLogFormatter < Logger::Formatter
|
||||
def call(severity, time, progname, msg)
|
||||
"[%s] - %s - %s\n" % [severity, time.to_s(:short), msg2str(msg)] unless msg2str(msg).blank?
|
||||
end
|
||||
end
|
||||
|
||||
HelpdeskLogger = Logger.new(Rails.root.join('log/redmine_helpdesk.log'))
|
||||
HelpdeskLogger.formatter = HelpdeskLogFormatter.new
|
||||
|
||||
class HelpdeskSettings
|
||||
MACRO_LIST = %w({%contact.first_name%} {%contact.name%} {%contact.company%} {%contact.last_name%}
|
||||
{%contact.middle_name%} {%date%} {%ticket.assigned_to%} {%ticket.id%} {%ticket.tracker%}
|
||||
{%ticket.project%} {%ticket.subject%} {%ticket.quoted_description%} {%ticket.history%} {%ticket.status%}
|
||||
{%ticket.priority%} {%ticket.estimated_hours%} {%ticket.done_ratio%} {%ticket.public_url%} {%ticket.closed_on%} {%ticket.due_date%}
|
||||
{%ticket.start_date%} {%ticket.voting%} {%ticket.voting.good%} {%ticket.voting.okay%} {%ticket.voting.bad%}
|
||||
{%response.author%} {%response.author.first_name%} {%response.author.last_name%})
|
||||
|
||||
FROM_MACRO_LIST = %w({%response.author%} {%response.author.first_name%})
|
||||
|
||||
# Returns the value of the setting named name
|
||||
def self.[](name, project_id)
|
||||
project_id = project_id.id if project_id.is_a?(Project)
|
||||
ContactsSetting[name.to_s, project_id].present? ? ContactsSetting[name.to_s, project_id] : RedmineHelpdesk.settings[name.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
module RedmineHelpdesk
|
||||
module ContactUserMethods
|
||||
def name(formatter = nil)
|
||||
f = self.class.name_formatter(formatter)
|
||||
if formatter
|
||||
eval('"' + f[:string] + '"')
|
||||
else
|
||||
@name ||= eval('"' + f[:string] + '"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.settings() Setting[:plugin_redmine_contacts_helpdesk] ? Setting[:plugin_redmine_contacts_helpdesk] : {} end
|
||||
|
||||
def self.public_title
|
||||
universal_setting("helpdesk_public_title")
|
||||
end
|
||||
|
||||
def self.public_tickets?
|
||||
universal_setting("helpdesk_public_tickets").to_i > 0
|
||||
end
|
||||
|
||||
def self.vote_allow?
|
||||
universal_setting("helpdesk_vote_accept").to_i > 0
|
||||
end
|
||||
def self.vote_comment_allow?
|
||||
universal_setting("helpdesk_vote_comment_accept").to_i > 0
|
||||
end
|
||||
|
||||
def self.strip_tags?
|
||||
universal_setting("helpdesk_do_not_strip_tags").to_i <= 0
|
||||
end
|
||||
|
||||
def self.public_comments?
|
||||
universal_setting("helpdesk_public_comments").to_i > 0
|
||||
end
|
||||
|
||||
def self.public_spent_time?
|
||||
universal_setting("helpdesk_public_show_spent_time").to_i > 0
|
||||
end
|
||||
|
||||
def self.save_log?
|
||||
universal_setting(:helpdesk_vote_save_log).to_i > 0
|
||||
end
|
||||
|
||||
def self.autoclose_tickets_after
|
||||
universal_setting("helpdesk_autoclose_tickets_after").to_i
|
||||
end
|
||||
|
||||
def self.autoclose_from_status
|
||||
universal_setting("helpdesk_autoclose_from_status").to_i
|
||||
end
|
||||
|
||||
def self.autoclose_to_status
|
||||
universal_setting("helpdesk_autoclose_to_status").to_i
|
||||
end
|
||||
|
||||
def self.autoclose_time_unit
|
||||
universal_setting("helpdesk_autoclose_tickets_time_unit")
|
||||
end
|
||||
|
||||
def self.autoclose_time_unit_is?(value)
|
||||
autoclose_time_unit == value
|
||||
end
|
||||
|
||||
def self.autoclose_time_interval
|
||||
case autoclose_time_unit
|
||||
when 'day'
|
||||
1.day * autoclose_tickets_after
|
||||
when 'hour'
|
||||
1.hour * autoclose_tickets_after
|
||||
else
|
||||
-1
|
||||
end
|
||||
end
|
||||
|
||||
def self.universal_setting(setting)
|
||||
[settings[setting.to_sym], settings[setting.to_s]].compact.first
|
||||
end
|
||||
end
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
module RedmineHelpdesk
|
||||
module Hooks
|
||||
class ControllerContactsDuplicatesHook < Redmine::Hook::ViewListener
|
||||
def controller_contacts_duplicates_merge(context={})
|
||||
context[:duplicate].journal_messages << context[:contact].journal_messages
|
||||
context[:duplicate].helpdesk_tickets << context[:contact].helpdesk_tickets
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
module RedmineHelpdesk
|
||||
module Hooks
|
||||
class HelperIssuesHook < Redmine::Hook::ViewListener
|
||||
|
||||
def helper_issues_show_detail_after_setting(context={})
|
||||
if context[:detail].prop_key == 'vote'
|
||||
detail = context[:detail]
|
||||
context[:detail].value = HelpdeskTicket.vote_message(detail.value) if detail.value && detail.value.to_s =~ /^\d$/
|
||||
context[:detail].old_value = HelpdeskTicket.vote_message(detail.old_value) if detail.old_value && detail.old_value.to_s =~ /^\d$/
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
module RedmineHelpdesk
|
||||
module Hooks
|
||||
class IssuesControllerHook < Redmine::Hook::ViewListener
|
||||
def controller_issues_new_before_save(context = {})
|
||||
ticket = context[:issue].helpdesk_ticket
|
||||
return if ticket.nil? || ticket.from_address.present? || ticket.customer.nil? || ticket.customer.primary_email.blank?
|
||||
ticket.from_address = ticket.customer.primary_email
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
module RedmineHelpdesk
|
||||
module Hooks
|
||||
class ViewContactsHook < Redmine::Hook::ViewListener
|
||||
render_on :view_contacts_context_menu_before_delete, :partial => "context_menus/helpdesk_contacts"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
module RedmineHelpdesk
|
||||
module Hooks
|
||||
class ViewIssuesHook < Redmine::Hook::ViewListener
|
||||
render_on :view_issues_edit_notes_bottom, :partial => 'issues/send_response'
|
||||
def view_issues_sidebar_issues_bottom(context = {})
|
||||
context[:controller].send(:render_to_string, { :partial => 'issues/helpdesk_reports', :locals => context }) +
|
||||
context[:controller].send(:render_to_string, { :partial => 'issues/helpdesk_customer_profile', :locals => context })
|
||||
end
|
||||
render_on :view_issues_show_details_bottom, :partial => 'issues/ticket_data'
|
||||
render_on :view_issues_form_details_top, :partial => 'issues/ticket_data_form'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
include ContactsHelper
|
||||
|
||||
module RedmineHelpdesk
|
||||
module Hooks
|
||||
class ShowJournalContactHook < Redmine::Hook::ViewListener
|
||||
render_on :view_issues_history_journal_bottom, :partial => "journals/journal_contact"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
module RedmineHelpdesk
|
||||
module Hooks
|
||||
class ViewsLayoutsHook < Redmine::Hook::ViewListener
|
||||
def view_layouts_base_html_head(context={})
|
||||
return stylesheet_link_tag(:helpdesk, :plugin => 'redmine_contacts_helpdesk')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
module RedmineHelpdesk
|
||||
module Hooks
|
||||
class ViewProjectsHook < Redmine::Hook::ViewListener
|
||||
def view_projects_show_sidebar_bottom(context = {})
|
||||
context[:controller].send(:render_to_string, { :partial => 'issues/helpdesk_reports', :locals => context }) +
|
||||
context[:controller].send(:render_to_string, { :partial => 'projects/helpdesk_tickets', :locals => context })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
require_dependency 'application_helper'
|
||||
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module ApplicationHelperPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
|
||||
alias_method_chain :avatar, :helpdesk
|
||||
alias_method_chain :link_to_user, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module InstanceMethods
|
||||
# include ContactsHelper
|
||||
|
||||
def avatar_with_helpdesk(user, options = { })
|
||||
if user.is_a?(Contact)
|
||||
avatar_to(user, options)
|
||||
else
|
||||
avatar_without_helpdesk(user, options)
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_user_with_helpdesk(user, options={})
|
||||
if user.is_a?(Contact)
|
||||
link_to_source(user, options)
|
||||
else
|
||||
link_to_user_without_helpdesk(user, options)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless ApplicationHelper.included_modules.include?(RedmineHelpdesk::Patches::ApplicationHelperPatch)
|
||||
ApplicationHelper.send(:include, RedmineHelpdesk::Patches::ApplicationHelperPatch)
|
||||
end
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
|
||||
module AttachmentsControllerPatch
|
||||
def self.included(base) # :nodoc:
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
alias_method_chain :read_authorize, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def read_authorize_with_helpdesk
|
||||
unless params[:ticket_id] && params[:hash] && HelpdeskTicket.where(:id => params[:ticket_id]).first && HelpdeskTicket.where(:id => params[:ticket_id]).first.try(:token) == params[:hash]
|
||||
read_authorize_without_helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless AttachmentsController.included_modules.include?(RedmineHelpdesk::Patches::AttachmentsControllerPatch)
|
||||
AttachmentsController.send(:include, RedmineHelpdesk::Patches::AttachmentsControllerPatch)
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
if Redmine::VERSION.to_s < '2.4'
|
||||
def accept_attachment?(attachment)
|
||||
true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module ContactPatch
|
||||
def self.included(base) # :nodoc:
|
||||
base.send(:include, InstanceMethods)
|
||||
base.class_eval do
|
||||
unloadable # Send unloadable so it will not be unloaded in development
|
||||
has_many :journals, :through => :journal_messages
|
||||
has_many :journal_messages, :dependent => :destroy
|
||||
|
||||
has_many :tickets, :through => :helpdesk_tickets, :source => :issue #class_name => "Issue", :as => :issue, :foreign_key => 'issue_id'
|
||||
has_many :helpdesk_tickets, :dependent => :destroy
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def mail
|
||||
self.primary_email
|
||||
end
|
||||
|
||||
def email_name
|
||||
[name, ' <', mail, '>'].join
|
||||
end
|
||||
|
||||
def all_tickets
|
||||
if self.is_company
|
||||
Issue.eager_load(:customer).where(:contacts => {:id => [self.id] | self.company_contacts.map(&:id) })
|
||||
else
|
||||
self.tickets
|
||||
end
|
||||
end
|
||||
|
||||
def find_assigned_user(project, current_assigned_id)
|
||||
return Principal.find_by_id(current_assigned_id) unless RedmineHelpdesk.settings["helpdesk_assign_contact_user"].to_i > 0
|
||||
return assigned_to if assigned_to.present? && Project.visible(assigned_to).pluck(:id).include?(project.id)
|
||||
return contact_company.assigned_to if contact_company.present? && contact_company.assigned_to.present? &&
|
||||
Project.visible(contact_company.assigned_to).pluck(:id).include?(project.id)
|
||||
Principal.find_by_id(current_assigned_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Contact.included_modules.include?(RedmineHelpdesk::Patches::ContactPatch)
|
||||
Contact.send(:include, RedmineHelpdesk::Patches::ContactPatch)
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
require_dependency 'query'
|
||||
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module ContactQueryPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.send(:include, HelpdeskHelper)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
|
||||
alias_method_chain :available_filters, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def sql_for_number_of_tickets_field(_, operator, value)
|
||||
"(#{Contact.table_name}.id IN (SELECT #{HelpdeskTicket.table_name}.contact_id
|
||||
FROM #{HelpdeskTicket.table_name}
|
||||
GROUP BY #{HelpdeskTicket.table_name}.contact_id
|
||||
HAVING count(#{HelpdeskTicket.table_name}.contact_id) #{operator} #{value.first}))"
|
||||
end
|
||||
|
||||
def sql_for_open_tickets_field(_, operator, value)
|
||||
value = value.first
|
||||
in_cond = if (operator == '!' && value == '0') || (operator == '=' && value == '1')
|
||||
'IN'
|
||||
else
|
||||
'NOT IN'
|
||||
end
|
||||
"(#{Contact.table_name}.id #{in_cond} (SELECT #{HelpdeskTicket.table_name}.contact_id
|
||||
FROM #{HelpdeskTicket.table_name}
|
||||
INNER JOIN #{Issue.table_name} on #{Issue.table_name}.id = #{HelpdeskTicket.table_name}.issue_id
|
||||
INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id
|
||||
WHERE (#{IssueStatus.table_name}.is_closed = #{ActiveRecord::Base.connection.quoted_false})
|
||||
))"
|
||||
end
|
||||
|
||||
def available_filters_with_helpdesk
|
||||
if @available_filters.blank? && User.current.allowed_to?(:view_helpdesk_tickets, project, :global => true)
|
||||
|
||||
add_available_filter('number_of_tickets', :type => :integer, :name => l(:label_helpdesk_number_of_tickets)) unless available_filters_without_helpdesk.key?('number_of_tickets')
|
||||
add_available_filter('open_tickets', :type => :list, :name => l(:label_helpdesk_open_tickets), :values => [[l(:general_text_yes), '1'], [l(:general_text_no), '0']]) unless available_filters_without_helpdesk.key?('open_tickets')
|
||||
else
|
||||
available_filters_without_helpdesk
|
||||
end
|
||||
@available_filters
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless ContactQuery.included_modules.include?(RedmineHelpdesk::Patches::ContactQueryPatch)
|
||||
ContactQuery.send(:include, RedmineHelpdesk::Patches::ContactQueryPatch)
|
||||
end
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module ContactsHelperPatch
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
unloadable
|
||||
alias_method_chain :contact_tabs, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
def contact_tabs_with_helpdesk(contact)
|
||||
tabs = contact_tabs_without_helpdesk(contact)
|
||||
|
||||
if contact.all_tickets.visible.count > 0
|
||||
tabs.push({:name => 'helpdesk', :partial => 'contacts/helpdesk_tickets', :label => l(:label_helpdesk_ticket_plural) + " (#{contact.all_tickets.visible.open.count}/#{contact.all_tickets.visible.count})"} )
|
||||
end
|
||||
tabs
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless ContactsHelper.included_modules.include?(RedmineHelpdesk::Patches::ContactsHelperPatch)
|
||||
ContactsHelper.send(:include, RedmineHelpdesk::Patches::ContactsHelperPatch)
|
||||
end
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
require_dependency 'queries_helper'
|
||||
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module GravatarHelperPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
|
||||
alias_method_chain :gravatar_api_url, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def gravatar_api_url_with_helpdesk(hash)
|
||||
[Setting[:protocol], ':', gravatar_api_url_without_helpdesk(hash)].join
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless GravatarHelper::PublicMethods.included_modules.include?(RedmineHelpdesk::Patches::GravatarHelperPatch)
|
||||
GravatarHelper::PublicMethods.send(:include, RedmineHelpdesk::Patches::GravatarHelperPatch)
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module IssuePatch
|
||||
def self.included(base)
|
||||
base.send(:extend, ClassMethods)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.send(:include, ActionView::Helpers::DateHelper)
|
||||
base.class_eval do
|
||||
unloadable # Send unloadable so it will not be unloaded in development
|
||||
has_one :customer, :through => :helpdesk_ticket
|
||||
has_one :helpdesk_ticket, :dependent => :destroy
|
||||
|
||||
scope :order_by_status, lambda { joins(:status).order("#{IssueStatus.table_name}.is_closed, #{IssueStatus.table_name}.id, #{Issue.table_name}.id DESC") }
|
||||
|
||||
accepts_nested_attributes_for :helpdesk_ticket
|
||||
|
||||
safe_attributes 'helpdesk_ticket_attributes',
|
||||
:if => lambda { |issue, user| user.allowed_to?(:edit_helpdesk_tickets, issue.project) }
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def load_helpdesk_data(issues, user=User.current)
|
||||
if issues.any?
|
||||
helpdesk_tickets = HelpdeskTicket.where(:issue_id => issues.map(&:id))
|
||||
issues.each do |issue|
|
||||
issue.instance_variable_set "@helpdesk_ticket", (helpdesk_tickets.detect{|c| c.issue_id == issue.id} || nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def journal_messages
|
||||
@journal_messages ||= JournalMessage.includes(:message_file, :contact => [:avatar, :projects]).
|
||||
where(:journal_id => journals.pluck(:id)).
|
||||
uniq.to_a
|
||||
end
|
||||
|
||||
def is_ticket?
|
||||
helpdesk_ticket.present?
|
||||
end
|
||||
|
||||
def last_message
|
||||
self.helpdesk_ticket.last_message.content.truncate(250) if self.helpdesk_ticket
|
||||
end
|
||||
|
||||
def ticket_source
|
||||
self.helpdesk_ticket.ticket_source_name if self.helpdesk_ticket
|
||||
end
|
||||
|
||||
def customer_company
|
||||
return nil unless self.customer
|
||||
self.customer.company
|
||||
end
|
||||
|
||||
def last_message_date
|
||||
self.helpdesk_ticket.last_message_date if self.helpdesk_ticket
|
||||
end
|
||||
|
||||
def ticket_reaction_time
|
||||
helpdesk_ticket && helpdesk_ticket.reaction_time ? distance_of_time_in_words(helpdesk_ticket.reaction_time) : ""
|
||||
end
|
||||
|
||||
def ticket_first_response_time
|
||||
helpdesk_ticket && helpdesk_ticket.first_response_time ? distance_of_time_in_words(helpdesk_ticket.first_response_time) : ""
|
||||
end
|
||||
|
||||
def ticket_resolve_time
|
||||
helpdesk_ticket && helpdesk_ticket.resolve_time ? distance_of_time_in_words(helpdesk_ticket.resolve_time) : ""
|
||||
end
|
||||
|
||||
def vote
|
||||
helpdesk_ticket.present? && helpdesk_ticket.vote.present? ? HelpdeskTicket.vote_message(helpdesk_ticket.vote) : ""
|
||||
end
|
||||
|
||||
def vote_comment
|
||||
helpdesk_ticket.present? && helpdesk_ticket.vote_comment.present? ? helpdesk_ticket.vote_comment.to_s : ""
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Issue.included_modules.include?(RedmineHelpdesk::Patches::IssuePatch)
|
||||
Issue.send(:include, RedmineHelpdesk::Patches::IssuePatch)
|
||||
end
|
||||
@@ -0,0 +1,185 @@
|
||||
require_dependency 'query'
|
||||
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module IssueQueryPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
base.send(:include, HelpdeskHelper)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
|
||||
alias_method_chain :available_columns, :helpdesk
|
||||
alias_method_chain :available_filters, :helpdesk
|
||||
alias_method_chain :joins_for_order_statement, :helpdesk
|
||||
alias_method_chain :issues, :helpdesk
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module InstanceMethods
|
||||
# def issues_with_helpdesk(options={})
|
||||
# if project.blank? || (project && User.current.allowed_to?(:view_helpdesk_tickets, project))
|
||||
# options[:include] = (options[:include] || []) + [:helpdesk_ticket]
|
||||
# end
|
||||
# issues_without_helpdesk(options)
|
||||
# end
|
||||
|
||||
def issues_with_helpdesk(options={})
|
||||
issues = issues_without_helpdesk(options)
|
||||
if has_column?(:last_message) || has_column?(:last_message_date) || has_column?(:customer) || has_column?(:ticket_source) || has_column?(:customer_company) || has_column?(:helpdesk_ticket) || has_column?(:ticket_reaction_time) || has_column?(:ticket_first_response_time) || has_column?(:ticket_resolve_time) || has_column?(:vote) || has_column?(:vote_comment)
|
||||
Issue.load_helpdesk_data(issues)
|
||||
end
|
||||
issues
|
||||
end
|
||||
|
||||
def joins_for_order_statement_with_helpdesk(order_options)
|
||||
joins = joins_for_order_statement_without_helpdesk(order_options)
|
||||
ticket_joins = [joins].flatten
|
||||
if order_options && (order_options.include?('reaction_time') ||
|
||||
order_options.include?('first_response_time') ||
|
||||
order_options.include?('resolve_time') ||
|
||||
order_options.include?('vote'))
|
||||
ticket_joins << "LEFT OUTER JOIN #{HelpdeskTicket.table_name} ON #{Issue.table_name}.id = #{HelpdeskTicket.table_name}.issue_id"
|
||||
end
|
||||
ticket_joins.any? ? ticket_joins.join(' ') : nil
|
||||
end
|
||||
|
||||
|
||||
def sql_for_customer_field(field, operator, value)
|
||||
case operator
|
||||
when "*", "!*" # Member / Not member
|
||||
sw = operator == "!*" ? 'NOT' : ''
|
||||
"(#{Issue.table_name}.id #{sw} IN (SELECT DISTINCT #{HelpdeskTicket.table_name}.issue_id FROM #{HelpdeskTicket.table_name}))"
|
||||
when "=", "!"
|
||||
sw = operator == "!" ? 'NOT' : ''
|
||||
contacts_select = "SELECT #{HelpdeskTicket.table_name}.issue_id FROM #{HelpdeskTicket.table_name}
|
||||
WHERE #{HelpdeskTicket.table_name}.contact_id IN (#{value.join(',')})"
|
||||
|
||||
"(#{Issue.table_name}.id #{sw} IN (#{contacts_select}))"
|
||||
end
|
||||
end
|
||||
|
||||
def sql_for_ticket_source_field(field, operator, value)
|
||||
case operator
|
||||
when "=", "!"
|
||||
sw = operator == "!" ? 'NOT' : ''
|
||||
contacts_select = "SELECT #{HelpdeskTicket.table_name}.issue_id FROM #{HelpdeskTicket.table_name}
|
||||
WHERE #{HelpdeskTicket.table_name}.source IN (#{value.join(',')})"
|
||||
|
||||
"(#{Issue.table_name}.id #{sw} IN (#{contacts_select}))"
|
||||
end
|
||||
end
|
||||
|
||||
def sql_for_customer_company_field(field, operator, value)
|
||||
sw = ["!", "!~"].include?(operator) ? 'NOT' : ''
|
||||
case operator
|
||||
when "="
|
||||
like_value = "LIKE '#{value.first.to_s.downcase}'"
|
||||
when "!*"
|
||||
like_value = "IS NULL OR #{Contact.table_name}.company = ''"
|
||||
when "*"
|
||||
like_value = "IS NOT NULL OR #{Contact.table_name}.company <> ''"
|
||||
when "~", "!~"
|
||||
like_value ="LIKE '%#{self.class.connection.quote_string(value.first.to_s.downcase)}%'"
|
||||
end
|
||||
|
||||
contacts_select = "SELECT #{HelpdeskTicket.table_name}.issue_id FROM #{HelpdeskTicket.table_name}
|
||||
WHERE #{HelpdeskTicket.table_name}.contact_id IN (
|
||||
SELECT #{Contact.table_name}.id
|
||||
FROM #{Contact.table_name}
|
||||
WHERE LOWER(#{Contact.table_name}.company) #{like_value}
|
||||
)"
|
||||
|
||||
"(#{Issue.table_name}.id #{sw} IN (#{contacts_select}))"
|
||||
end
|
||||
|
||||
def sql_for_ticket_reaction_time_field(field, operator, value)
|
||||
"(#{Issue.table_name}.id IN (SELECT #{HelpdeskTicket.table_name}.issue_id
|
||||
FROM #{HelpdeskTicket.table_name}
|
||||
WHERE #{sql_for_field(field, operator, value.map{|v| v.to_i * 60},
|
||||
HelpdeskTicket.table_name, "reaction_time")}))"
|
||||
end
|
||||
|
||||
def sql_for_ticket_first_response_time_field(field, operator, value)
|
||||
"(#{Issue.table_name}.id IN (SELECT #{HelpdeskTicket.table_name}.issue_id
|
||||
FROM #{HelpdeskTicket.table_name}
|
||||
WHERE #{sql_for_field(field, operator, value.map{|v| v.to_i * 60},
|
||||
HelpdeskTicket.table_name, "first_response_time")}))"
|
||||
end
|
||||
|
||||
def sql_for_ticket_resolve_time_field(field, operator, value)
|
||||
"(#{Issue.table_name}.id IN (SELECT #{HelpdeskTicket.table_name}.issue_id
|
||||
FROM #{HelpdeskTicket.table_name}
|
||||
WHERE #{sql_for_field(field, operator, value.map{|v| v.to_i * 60},
|
||||
HelpdeskTicket.table_name, "resolve_time")}))"
|
||||
end
|
||||
|
||||
def sql_for_vote_field(field, operator, value)
|
||||
case operator
|
||||
when '=', '*'
|
||||
compare = 'IN'
|
||||
when '!', '!*'
|
||||
compare = 'NOT IN'
|
||||
end
|
||||
issues_select = "SELECT DISTINCT(issue_id) FROM helpdesk_tickets WHERE vote IN (#{ value.join(',') })"
|
||||
issues_with_votes = 'SELECT DISTINCT(issue_id) FROM helpdesk_tickets WHERE vote IS NOT NULL'
|
||||
"(#{Issue.table_name}.id #{compare} (#{ %w(= !).include?(operator) ? issues_select : issues_with_votes }))"
|
||||
end
|
||||
|
||||
def available_columns_with_helpdesk
|
||||
if @available_columns.blank? && User.current.allowed_to?(:view_helpdesk_tickets, project, :global => true)
|
||||
@available_columns = available_columns_without_helpdesk
|
||||
@available_columns << QueryColumn.new(:last_message, :caption => :label_helpdesk_last_message)
|
||||
@available_columns << QueryColumn.new(:last_message_date, :caption => :label_helpdesk_last_message_date)
|
||||
@available_columns << QueryColumn.new(:customer, :caption => :label_helpdesk_contact)
|
||||
@available_columns << QueryColumn.new(:ticket_source, :caption => :label_helpdesk_ticket_source)
|
||||
@available_columns << QueryColumn.new(:customer_company, :caption => :label_helpdesk_contact_company)
|
||||
@available_columns << QueryColumn.new(:helpdesk_ticket, :caption => :label_helpdesk_ticket)
|
||||
@available_columns << QueryColumn.new(:ticket_reaction_time, :caption => :label_helpdesk_ticket_reaction_time, :sortable => "#{HelpdeskTicket.table_name}.reaction_time")
|
||||
@available_columns << QueryColumn.new(:ticket_first_response_time, :caption => :label_helpdesk_ticket_first_response_time, :sortable => "#{HelpdeskTicket.table_name}.first_response_time")
|
||||
@available_columns << QueryColumn.new(:ticket_resolve_time, :caption => :label_helpdesk_ticket_resolve_time, :sortable => "#{HelpdeskTicket.table_name}.resolve_time")
|
||||
@available_columns << QueryColumn.new(:vote, :caption => :label_helpdesk_vote, :sortable => "#{HelpdeskTicket.table_name}.vote")
|
||||
@available_columns << QueryColumn.new(:vote_comment, :caption => :label_helpdesk_vote_comment)
|
||||
else
|
||||
available_columns_without_helpdesk
|
||||
end
|
||||
@available_columns
|
||||
end
|
||||
|
||||
def available_filters_with_helpdesk
|
||||
# && !RedmineHelpdesk.settings[:issues_filters]
|
||||
if @available_filters.blank? && User.current.allowed_to?(:view_helpdesk_tickets, project, :global => true)
|
||||
selected_customer = filters['customer'].present? ? Contact.visible.where(:id => filters['customer'][:values]).map { |c| [c.name, c.id.to_s] } : []
|
||||
add_available_filter('customer', :type => :list_optional, :field_format => 'contact', :name => l(:label_helpdesk_contact),
|
||||
:values => selected_customer) unless available_filters_without_helpdesk.key?('customer')
|
||||
|
||||
add_available_filter('ticket_source', :type => :list, :name => l(:label_helpdesk_ticket_source),
|
||||
:values => helpdesk_tickets_source_for_select) unless available_filters_without_helpdesk.key?('ticket_source')
|
||||
|
||||
add_available_filter('customer_company', :type => :string, :name => l(:label_helpdesk_contact_company)) unless available_filters_without_helpdesk.key?('customer_company')
|
||||
|
||||
add_available_filter('ticket_reaction_time', :type => :integer, :name => l(:label_helpdesk_ticket_reaction_time)) unless available_filters_without_helpdesk.key?('ticket_reaction_time')
|
||||
|
||||
add_available_filter('ticket_first_response_time', :type => :integer, :name => l(:label_helpdesk_ticket_first_response_time)) unless available_filters_without_helpdesk.key?('ticket_first_response_time')
|
||||
|
||||
add_available_filter('ticket_resolve_time', :type => :integer, :name => l(:label_helpdesk_ticket_resolve_time)) unless available_filters_without_helpdesk.key?('ticket_resolve_time')
|
||||
|
||||
add_available_filter('vote', :type => :list_optional, :name => l(:label_helpdesk_vote),
|
||||
:values => [[l(:label_helpdesk_mark_awesome), '2'], [l(:label_helpdesk_mark_justok), '1'], [l(:label_helpdesk_mark_notgood), '0']]) unless available_filters_without_helpdesk.key?('vote')
|
||||
|
||||
else
|
||||
available_filters_without_helpdesk
|
||||
end
|
||||
@available_filters
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless IssueQuery.included_modules.include?(RedmineHelpdesk::Patches::IssueQueryPatch)
|
||||
IssueQuery.send(:include, RedmineHelpdesk::Patches::IssueQueryPatch)
|
||||
end
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
|
||||
module IssuesControllerPatch
|
||||
def self.included(base) # :nodoc:
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
# before_filter :apply_helpdesk_macro, :only => :update
|
||||
after_filter :flash_helpdesk, :only => :update
|
||||
after_filter :send_auto_answer, :only => :create
|
||||
|
||||
alias_method_chain :build_new_issue_from_params, :helpdesk
|
||||
alias_method_chain :update_issue_from_params, :helpdesk
|
||||
helper :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
||||
def flash_helpdesk
|
||||
if @issue.current_journal.is_send_note
|
||||
render_send_note_warning_if_needed(@issue.current_journal)
|
||||
flash[:notice] = flash[:notice].to_s + " " + l(:notice_email_sent, "<span class='icon icon-email'>" + @issue.current_journal.journal_message.to_address + "</span>") if @issue.current_journal.send_note_errors.blank?
|
||||
end
|
||||
end
|
||||
|
||||
def send_auto_answer
|
||||
return unless @issue && @issue.customer && User.current.allowed_to?(:send_response, @project)
|
||||
case params[:helpdesk_send_as].to_i
|
||||
when HelpdeskTicket::SEND_AS_NOTIFICATION
|
||||
msg = HelpdeskMailer.auto_answer(@issue.customer, @issue).deliver
|
||||
when HelpdeskTicket::SEND_AS_MESSAGE
|
||||
if msg = HelpdeskMailer.initial_message(@issue.customer, @issue, params).deliver
|
||||
@issue.helpdesk_ticket.message_id = msg.message_id
|
||||
@issue.helpdesk_ticket.is_incoming = false
|
||||
@issue.helpdesk_ticket.from_address = @issue.customer.primary_email
|
||||
@issue.helpdesk_ticket.save
|
||||
end
|
||||
end
|
||||
flash[:notice].blank? ? flash[:notice] = l(:notice_email_sent, "<span class='icon icon-email'>" + msg.to_addrs.first + "</span>") : flash[:notice] << " " + l(:notice_email_sent, "<span class='icon icon-email'>" + msg.to_addrs.first + "</span>") if msg
|
||||
rescue Exception => e
|
||||
flash[:error].blank? ? flash[:error] = e.message : flash[:error] << " " + e.message
|
||||
end
|
||||
|
||||
def update_issue_from_params_with_helpdesk
|
||||
is_updated = update_issue_from_params_without_helpdesk
|
||||
return false unless is_updated
|
||||
if params[:helpdesk] && params[:helpdesk][:is_send_mail] && User.current.allowed_to?(:send_response, @project) && @issue.customer
|
||||
@issue.current_journal.build_journal_message
|
||||
journal_params = params[:journal_message].merge(Hash[params[:journal_message].slice('to_address', 'cc_address', 'bcc_address').
|
||||
map { |k, v| [k, v.join(',')] }]) if params[:journal_message]
|
||||
@issue.current_journal.journal_message.update_attributes(journal_params)
|
||||
@issue.current_journal.journal_message.to_address ||= @issue.customer.primary_email
|
||||
@issue.current_journal.is_send_note = true
|
||||
@issue.current_journal.notes = HelpdeskMailer.apply_macro(@issue.current_journal.notes, @issue.customer, @issue, User.current)
|
||||
end
|
||||
is_updated
|
||||
end
|
||||
|
||||
def build_new_issue_from_params_with_helpdesk
|
||||
build_new_issue_from_params_without_helpdesk
|
||||
return if @issue.blank? || params[:customer_id].blank?
|
||||
contact = Contact.visible.find_by_id(params[:customer_id])
|
||||
@issue.build_helpdesk_ticket(:issue => @issue, :ticket_date => Time.now, :customer => contact) if contact
|
||||
@issue.helpdesk_ticket.source = params[:source] if params[:source]
|
||||
end
|
||||
|
||||
def render_send_note_warning_if_needed(journal)
|
||||
return false if journal.blank? || journal.journal_message.blank?
|
||||
flash[:warning] = flash[:warning].to_s + " " + l(:label_helpdesk_email_sending_problems) + ": " + journal.send_note_errors unless journal.send_note_errors.blank?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless IssuesController.included_modules.include?(RedmineHelpdesk::Patches::IssuesControllerPatch)
|
||||
IssuesController.send(:include, RedmineHelpdesk::Patches::IssuesControllerPatch)
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
|
||||
module JournalPatch
|
||||
def self.included(base) # :nodoc:
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
unloadable # Send unloadable so it will not be unloaded in development
|
||||
has_one :contact, :through => :journal_message
|
||||
has_one :journal_message, :dependent => :destroy
|
||||
|
||||
attr_accessor :is_send_note
|
||||
attr_accessor :send_note_errors
|
||||
|
||||
after_create :send_note
|
||||
after_create :update_helpdesk_ticket
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module InstanceMethods
|
||||
|
||||
def is_incoming?
|
||||
self.journal_message && self.journal_message.is_incoming?
|
||||
end
|
||||
|
||||
def is_sent?
|
||||
self.journal_message && !self.journal_message.is_incoming?
|
||||
end
|
||||
|
||||
def message_author
|
||||
self.is_incoming? ? self.contact : self.user
|
||||
end
|
||||
|
||||
def helpdesk_ticket
|
||||
self.journalized.respond_to?(:helpdesk_ticket) && self.journalized.helpdesk_ticket
|
||||
end
|
||||
|
||||
|
||||
def send_note
|
||||
require 'timeout'
|
||||
if self.issue.customer && self.is_send_note && self.notes
|
||||
journal_message = self.journal_message
|
||||
begin
|
||||
response_options = {:to_address => journal_message.to_address, :cc_address => journal_message.cc_address, :bcc_address => journal_message.bcc_address}
|
||||
Timeout::timeout(60) do
|
||||
HelpdeskMailer.with_activated_perform_deliveries do
|
||||
if msg = HelpdeskMailer.issue_response(self.issue.customer, self, response_options).deliver
|
||||
journal_message.message_date = msg.date
|
||||
journal_message.is_incoming = false
|
||||
journal_message.message_id = msg.message_id.to_s.slice(0, 255)
|
||||
journal_message.source = HelpdeskTicket::HELPDESK_EMAIL_SOURCE
|
||||
journal_message.contact = Contact.find_by_emails([msg.to_addrs.first]).first || self.issue.customer
|
||||
journal_message.to_address = msg.to_addrs.first.to_s.slice(0, 255)
|
||||
journal_message.cc_address = msg.cc.join(', ').to_s.slice(0, 255)
|
||||
journal_message.bcc_address = msg.bcc.join(', ').to_s.slice(0, 255)
|
||||
journal_message.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
self.send_note_errors = e.message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
def update_helpdesk_ticket
|
||||
return false if helpdesk_ticket.blank? || (helpdesk_ticket && helpdesk_ticket.ticket_date.blank?)
|
||||
helpdesk_ticket.save
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Journal.included_modules.include?(RedmineHelpdesk::Patches::JournalPatch)
|
||||
Journal.send(:include, RedmineHelpdesk::Patches::JournalPatch)
|
||||
end
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
|
||||
module JournalsControllerPatch
|
||||
def self.included(base) # :nodoc:
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
alias_method_chain :new, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def new_with_helpdesk
|
||||
@journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
|
||||
if @journal
|
||||
user = @journal.user
|
||||
text = @journal.notes
|
||||
if user.anonymous? && @journal.contact
|
||||
user = @journal.contact
|
||||
end
|
||||
else
|
||||
user = @issue.author
|
||||
text = @issue.description
|
||||
if user.anonymous? && @issue.customer
|
||||
user = @issue.customer
|
||||
end
|
||||
end
|
||||
# Replaces pre blocks with [...]
|
||||
text = text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]')
|
||||
@content = "#{ll(Setting.default_language, :text_user_wrote, user)}\n> "
|
||||
@content << text.gsub(/(\r?\n|\r\n?)/, "\n> ") + "\n\n"
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless JournalsController.included_modules.include?(RedmineHelpdesk::Patches::JournalsControllerPatch)
|
||||
JournalsController.send(:include, RedmineHelpdesk::Patches::JournalsControllerPatch)
|
||||
end
|
||||
@@ -0,0 +1,99 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module MailHandlerPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
alias_method_chain :receive_issue_reply, :handle_helpdesk
|
||||
alias_method_chain :cleanup_body, :handle_helpdesk
|
||||
alias_method_chain :dispatch, :handle_helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def receive_issue_reply_with_handle_helpdesk(issue_id, from_journal=nil)
|
||||
journal = receive_issue_reply_without_handle_helpdesk(issue_id, from_journal)
|
||||
helpdesk_receive_issue_reply(issue_id, journal, from_journal)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dispatch_with_handle_helpdesk
|
||||
if email_tag
|
||||
tag_issue = Issue.where(:id => email_tag[/\+(\d+)@/, 1].to_i).first
|
||||
return dispatch_without_handle_helpdesk unless tag_issue
|
||||
receive_issue_reply(tag_issue.id)
|
||||
else
|
||||
dispatch_without_handle_helpdesk
|
||||
end
|
||||
rescue MissingInformation => e
|
||||
logger.error "#{email && email.message_id}: missing information from #{user}: #{e.message}" if logger
|
||||
false
|
||||
rescue UnauthorizedAction => e
|
||||
logger.error "#{email && email.message_id}: unauthorized attempt from #{user}" if logger
|
||||
false
|
||||
rescue Exception => e
|
||||
# TODO: send a email to the user
|
||||
logger.error "#{email && email.message_id}: dispatch error #{e.message}" if logger
|
||||
false
|
||||
end
|
||||
|
||||
def email_tag
|
||||
(email.to.present? && email.to.find { |email| email.match(/\+\d+@/) }) ||
|
||||
(email.cc.present? && email.cc.find { |email| email.match(/\+\d+@/) })
|
||||
end
|
||||
|
||||
def helpdesk_receive_issue_reply(issue_id, journal, from_journal=nil)
|
||||
return unless journal
|
||||
return journal if journal.notes.blank?
|
||||
project = journal.issue.project
|
||||
return journal unless journal.user.allowed_to?(:send_response, journal.issue.project) && journal.issue.customer
|
||||
|
||||
unless HelpdeskSettings["send_note_by_default", project]
|
||||
regexp = /^@@sendmail@@\s*$/
|
||||
return journal unless journal.notes.match(regexp)
|
||||
journal.notes = journal.notes.gsub(regexp, '')
|
||||
end
|
||||
|
||||
contact = journal.issue.customer
|
||||
|
||||
begin
|
||||
HelpdeskMailer.with_activated_perform_deliveries do
|
||||
if msg = HelpdeskMailer.issue_response(contact, journal).deliver
|
||||
JournalMessage.create(:to_address => msg.to_addrs.first.to_s.slice(0, 255),
|
||||
:is_incoming => false,
|
||||
:message_date => Time.now,
|
||||
:message_id => msg.message_id.to_s.slice(0, 255),
|
||||
:source => HelpdeskTicket::HELPDESK_EMAIL_SOURCE,
|
||||
:cc_address => msg.cc.join(', ').to_s.slice(0, 255),
|
||||
:bcc_address => msg.bcc.join(', ').to_s.slice(0, 255),
|
||||
:contact => contact,
|
||||
:journal => journal)
|
||||
journal.issue.assigned_to = User.current unless journal.issue.assigned_to
|
||||
journal.issue.status_id = HelpdeskSettings[:helpdesk_new_status, journal.issue.project_id] unless HelpdeskSettings[:helpdesk_new_status, journal.issue.project_id].blank?
|
||||
journal.issue.save
|
||||
HelpdeskLogger.info "#{msg.message_id}: Replay sent to #{contact.name} - [#{contact.emails.first}]" if HelpdeskLogger
|
||||
end
|
||||
end
|
||||
rescue Exception => e
|
||||
HelpdeskLogger.info "Error of replay sending to #{contact.name} - [#{contact.emails.first}], #{e.message}" if HelpdeskLogger
|
||||
end
|
||||
|
||||
journal.save!
|
||||
journal
|
||||
end
|
||||
|
||||
def cleanup_body_with_handle_helpdesk(body)
|
||||
cleanup_body_without_handle_helpdesk(body).gsub(' ', '')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless MailHandler.included_modules.include?(RedmineHelpdesk::Patches::MailHandlerPatch)
|
||||
MailHandler.send(:include, RedmineHelpdesk::Patches::MailHandlerPatch)
|
||||
end
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
require_dependency 'queries_helper'
|
||||
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module ProjectsHelperPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
|
||||
alias_method_chain :project_settings_tabs, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
# include ContactsHelper
|
||||
|
||||
def project_settings_tabs_with_helpdesk
|
||||
tabs = project_settings_tabs_without_helpdesk
|
||||
|
||||
helpdesk_tabs = []
|
||||
helpdesk_tabs.push({ :name => 'helpdesk',
|
||||
:action => :edit_helpdesk_settings,
|
||||
:partial => 'projects/settings/helpdesk_settings',
|
||||
:label => :label_helpdesk })
|
||||
helpdesk_tabs.push({ :name => 'helpdesk_template',
|
||||
:action => :edit_helpdesk_settings,
|
||||
:partial => 'projects/settings/helpdesk_template',
|
||||
:label => :label_helpdesk_template })
|
||||
helpdesk_tabs.push({ :name => 'helpdesk_canned_responses',
|
||||
:action => :manage_canned_responses,
|
||||
:partial => 'projects/settings/helpdesk_canned_responses',
|
||||
:label => :label_helpdesk_canned_response_plural })
|
||||
helpdesk_tabs.each { |tab| tabs << tab if User.current.allowed_to?(tab[:action], @project) }
|
||||
tabs
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless ProjectsHelper.included_modules.include?(RedmineHelpdesk::Patches::ProjectsHelperPatch)
|
||||
ProjectsHelper.send(:include, RedmineHelpdesk::Patches::ProjectsHelperPatch)
|
||||
end
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
require_dependency 'queries_helper'
|
||||
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module QueriesHelperPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
alias_method_chain :column_content, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module InstanceMethods
|
||||
include ContactsHelper
|
||||
|
||||
|
||||
def column_content_with_helpdesk(column, issue)
|
||||
if column.name.eql?(:last_message) && issue.helpdesk_ticket && issue.is_a?(Issue)
|
||||
content_tag(:span, '', :class => "icon #{issue.helpdesk_ticket.last_message.is_incoming ? 'icon-email' : 'icon-email-to'}") +
|
||||
link_to(content_tag(:span, content_tag(:small, issue.helpdesk_ticket.last_message.content.truncate(250)), :class => 'description'),
|
||||
{:controller => 'issues', :action => 'show', :id => issue.id, :anchor => "change-#{issue.helpdesk_ticket.last_message.id}"})
|
||||
elsif column.name.eql?(:customer) && issue.is_a?(Issue)
|
||||
issue.customer ? contact_tag(issue.customer, :type => "plain") : ""
|
||||
elsif column.name.eql?(:customer_company) && issue.is_a?(Issue)
|
||||
issue.customer ? issue.customer.company : ""
|
||||
elsif column.name.eql?(:ticket_source) && issue.is_a?(Issue)
|
||||
issue.helpdesk_ticket ? issue.helpdesk_ticket.ticket_source_name : ""
|
||||
elsif column.name.eql?(:ticket_reaction_time) && issue.is_a?(Issue)
|
||||
issue.ticket_reaction_time
|
||||
elsif column.name.eql?(:ticket_first_response_time) && issue.is_a?(Issue)
|
||||
issue.ticket_first_response_time
|
||||
elsif column.name.eql?(:ticket_resolve_time) && issue.is_a?(Issue)
|
||||
issue.ticket_resolve_time
|
||||
elsif column.name.eql?(:last_message_date) && issue.is_a?(Issue)
|
||||
issue.helpdesk_ticket ? l(:label_helpdesk_ago, time_tag(issue.helpdesk_ticket.last_message_date)) : ""
|
||||
elsif column.name.eql?(:helpdesk_ticket) && issue.customer && issue.is_a?(Issue)
|
||||
contact_tag(issue.customer, :size => "32", :type => "avatar", :class => "avatar") +
|
||||
content_tag(:div,
|
||||
content_tag(:p, link_to(issue.subject, issue_path(issue)), :class => 'ticket-name') +
|
||||
content_tag(:p, content_tag(:small, issue.description.gsub("(\n|\r)", "").strip.truncate(100)), :class => "ticket-description") +
|
||||
content_tag(:p, "#{content_tag('span', '', :class => "icon #{helpdesk_ticket_source_icon(issue.helpdesk_ticket)}", :title => l(:label_note_type_email))} #{l(:label_helpdesk_from)}: #{contact_tag(issue.customer, :type => "plain")}, ".html_safe + l(:label_updated_time, time_tag(issue.helpdesk_ticket.last_message_date)).html_safe, :class => "contact-info"),
|
||||
:class => 'ticket-data')
|
||||
# content_tag(:div, content_tag(:span, issue.status.name, :class => "tag-label-color status-#{issue.status.id}" , :style => "background-color: #{tag_color(issue.status.name)}"), :class => "ticket-status")
|
||||
elsif column.name.eql?(:vote) && issue.helpdesk_ticket && issue.is_a?(Issue)
|
||||
content_tag(:span, HelpdeskTicket.vote_message(issue.helpdesk_ticket.vote)) if issue.helpdesk_ticket.vote
|
||||
elsif column.name.eql?(:vote_comment) && issue.helpdesk_ticket && issue.is_a?(Issue)
|
||||
textilizable(issue.helpdesk_ticket.vote_comment.to_s)
|
||||
else
|
||||
column_content_without_helpdesk(column, issue)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless QueriesHelper.included_modules.include?(RedmineHelpdesk::Patches::QueriesHelperPatch)
|
||||
QueriesHelper.send(:include, RedmineHelpdesk::Patches::QueriesHelperPatch)
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
module RedmineHelpdesk
|
||||
module Patches
|
||||
module TimeReportPatch
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
|
||||
base.class_eval do
|
||||
unloadable
|
||||
alias_method_chain :load_available_criteria, :helpdesk
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module InstanceMethods
|
||||
def load_available_criteria_with_helpdesk
|
||||
@available_criteria = load_available_criteria_without_helpdesk
|
||||
@available_criteria['customer'] = {:sql => "c_helpdesk_tickets.contact_id",
|
||||
:kclass => Contact,
|
||||
:joins => "LEFT OUTER JOIN helpdesk_tickets c_helpdesk_tickets ON c_helpdesk_tickets.issue_id = issues.id",
|
||||
:label => :label_helpdesk_contact} if User.current.allowed_to?(:view_helpdesk_tickets, @project, :global => true)
|
||||
@available_criteria['helpdesk_contact_company'] = {
|
||||
:sql => "hcc_contacts.company",
|
||||
:kclass => Contact,
|
||||
:joins => "LEFT OUTER JOIN helpdesk_tickets hcc_helpdesk_tickets ON hcc_helpdesk_tickets.issue_id = issues.id LEFT OUTER JOIN contacts hcc_contacts on hcc_helpdesk_tickets.contact_id = hcc_contacts.id",
|
||||
:label => :label_helpdesk_contact_company} if User.current.allowed_to?(:view_helpdesk_tickets, @project, :global => true)
|
||||
|
||||
@available_criteria
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless Redmine::Helpers::TimeReport.included_modules.include?(RedmineHelpdesk::Patches::TimeReportPatch)
|
||||
Redmine::Helpers::TimeReport.send(:include, RedmineHelpdesk::Patches::TimeReportPatch)
|
||||
end
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
module RedmineHelpdesk
|
||||
module WikiMacros
|
||||
|
||||
Redmine::WikiFormatting::Macros.register do
|
||||
desc "Mail icon Macro"
|
||||
macro :mail do |obj, args|
|
||||
"<span class=\"icon icon-email\"/>"
|
||||
end
|
||||
|
||||
desc "Helpdesk send_file macro"
|
||||
macro :send_file do |obj, args|
|
||||
return "" unless obj.is_a?(Issue) || obj.is_a?(Journal)
|
||||
issue = obj.is_a?(Journal) ? obj.issue : obj
|
||||
return "" unless issue.respond_to?(:customer) || (issue.respond_to?(:customer) && issue.customer.blank?)
|
||||
args, options = extract_macro_options(args, :parent)
|
||||
raise 'No or bad arguments.' if args.size < 1
|
||||
attachment = issue.attachments.where(:filename => args.first).first
|
||||
|
||||
link_to_attachment attachment if attachment
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
namespace :redmine do
|
||||
namespace :plugins do
|
||||
namespace :helpdesk do
|
||||
|
||||
desc <<-END_DESC
|
||||
Update Helpdesk tickets from issue contacts
|
||||
|
||||
Issue attributes control options:
|
||||
project=PROJECT identifier of the target project
|
||||
status=STATUS name of the target status
|
||||
tracker=TRACKER name of the target tracker
|
||||
category=CATEGORY name of the target category
|
||||
priority=PRIORITY name of the target priority
|
||||
|
||||
Examples:
|
||||
|
||||
rake redmine:plugins:helpdesk:update_tickets RAILS_ENV="production" \\
|
||||
project=foo
|
||||
END_DESC
|
||||
|
||||
task :update_tickets => :environment do
|
||||
return "project should be selected" unless ENV['project']
|
||||
|
||||
project = Project.find(ENV['project'])
|
||||
issues = project.issues.includes(:contacts).where("contacts.id IS NOT NULL")
|
||||
issues.each do |issue|
|
||||
if issue.helpdesk_ticket.blank? && issue.contacts && contact = issue.contacts.first
|
||||
helpdesk_ticket = HelpdeskTicket.new(:from_address => contact.primary_email,
|
||||
:to_address => HelpdeskSettings["helpdesk_answer_from", project.id],
|
||||
:ticket_date => issue.created_on,
|
||||
:customer => contact,
|
||||
:issue => issue,
|
||||
:source => HelpdeskTicket::HELPDESK_EMAIL_SOURCE)
|
||||
message_file = issue.attachments.where(:filename => 'message.eml').first
|
||||
helpdesk_ticket.message_file = message_file if message_file
|
||||
helpdesk_ticket.save
|
||||
end
|
||||
end
|
||||
|
||||
JournalMessage.where(:message_date => nil).each do |message|
|
||||
message.message_date = message.journal.created_on if message.journal
|
||||
message.save
|
||||
end
|
||||
Attachment.where(:container_type => 'ContactJournal').update_all(:container_type => 'JournalMessage')
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,136 @@
|
||||
namespace :redmine do
|
||||
namespace :email do
|
||||
namespace :helpdesk do
|
||||
|
||||
desc <<-END_DESC
|
||||
Read an email from standard input.
|
||||
|
||||
Issue attributes control options:
|
||||
project=PROJECT identifier of the target project
|
||||
status=STATUS name of the target status
|
||||
tracker=TRACKER name of the target tracker
|
||||
category=CATEGORY name of the target category
|
||||
priority=PRIORITY name of the target priority
|
||||
|
||||
Examples:
|
||||
|
||||
rake redmine:email:helpdesk:read RAILS_ENV="production" \\
|
||||
project=foo \\
|
||||
tracker=bug < raw_email
|
||||
END_DESC
|
||||
|
||||
task :read => :environment do
|
||||
options = { :issue => {} }
|
||||
%w(project project_id status assigned_to tracker category priority due_date).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
|
||||
options[:reopen_status] = ENV['reopen_status'] if ENV['reopen_status']
|
||||
options[:issue][:project_id] = options[:issue][:project]
|
||||
HelpdeskMailer.receive(STDIN.read, options)
|
||||
end
|
||||
|
||||
desc <<-END_DESC
|
||||
Read emails from an IMAP server.
|
||||
|
||||
|
||||
Available IMAP options:
|
||||
host=HOST IMAP server host (default: 127.0.0.1)
|
||||
port=PORT IMAP server port (default: 143)
|
||||
ssl=SSL Use SSL? (default: false)
|
||||
username=USERNAME IMAP account
|
||||
password=PASSWORD IMAP password
|
||||
folder=FOLDER IMAP folder to read (default: INBOX)
|
||||
|
||||
Issue attributes control options:
|
||||
project_id=PROJECT_ID identifier of the target project
|
||||
status=STATUS name of the target status
|
||||
tracker=TRACKER name of the target tracker
|
||||
category=CATEGORY name of the target category
|
||||
priority=PRIORITY name of the target priority
|
||||
reopen_status=STATUS name of the target status afret receive response
|
||||
allow_override=ATTRS allow email content to override attributes
|
||||
specified by previous options
|
||||
ATTRS is a comma separated list of attributes
|
||||
|
||||
Processed emails control options:
|
||||
move_on_success=MAILBOX move emails that were successfully received
|
||||
to MAILBOX instead of deleting them
|
||||
move_on_failure=MAILBOX move emails that were ignored to MAILBOX
|
||||
|
||||
Examples:
|
||||
rake redmine:email:helpdesk:receive_imap RAILS_ENV="production" \\
|
||||
host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\
|
||||
project=foo \\
|
||||
tracker=bug
|
||||
END_DESC
|
||||
|
||||
task :receive_imap => :environment do
|
||||
imap_options = {:host => ENV['host'],
|
||||
:port => ENV['port'],
|
||||
:ssl => ENV['ssl'],
|
||||
:username => ENV['username'],
|
||||
:password => ENV['password'],
|
||||
:folder => ENV['folder'],
|
||||
:move_on_success => ENV['move_on_success'],
|
||||
:move_on_failure => ENV['move_on_failure']}
|
||||
|
||||
options = { :issue => {} }
|
||||
%w(project_id project status assigned_to tracker category priority due_date).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
|
||||
options[:reopen_status] = ENV['reopen_status'] if ENV['reopen_status']
|
||||
options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
|
||||
options[:issue][:project_id] = options[:issue][:project]
|
||||
|
||||
RedmineContacts::Mailer.check_imap(HelpdeskMailer, imap_options, options)
|
||||
end
|
||||
|
||||
desc <<-END_DESC
|
||||
Read emails from an POP3 server.
|
||||
|
||||
Available POP3 options:
|
||||
host=HOST POP3 server host (default: 127.0.0.1)
|
||||
port=PORT POP3 server port (default: 110)
|
||||
username=USERNAME POP3 account
|
||||
password=PASSWORD POP3 password
|
||||
apop=1 use APOP authentication (default: false)
|
||||
delete_unprocessed=1 delete messages that could not be processed
|
||||
successfully from the server (default
|
||||
behaviour is to leave them on the server)
|
||||
|
||||
See redmine:email:helpdesk:receive_pop3 for more options and examples.
|
||||
END_DESC
|
||||
|
||||
task :receive_pop3 => :environment do
|
||||
pop_options = {:host => ENV['host'],
|
||||
:port => ENV['port'],
|
||||
:apop => ENV['apop'],
|
||||
:username => ENV['username'],
|
||||
:password => ENV['password'],
|
||||
:delete_unprocessed => ENV['delete_unprocessed']}
|
||||
|
||||
options = { :issue => {} }
|
||||
%w(project_id project status assigned_to tracker category priority due_date).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] }
|
||||
options[:reopen_status] = ENV['reopen_status'] if ENV['reopen_status']
|
||||
options[:allow_override] = ENV['allow_override'] if ENV['allow_override']
|
||||
options[:issue][:project_id] = options[:issue][:project]
|
||||
|
||||
RedmineContacts::Mailer.check_pop3(HelpdeskMailer, pop_options, options)
|
||||
end
|
||||
|
||||
desc <<-END_DESC
|
||||
Receive emails using project settings.
|
||||
|
||||
rake redmine:email:helpdesk:receive RAILS_ENV="production"
|
||||
|
||||
END_DESC
|
||||
|
||||
task :receive => :environment do
|
||||
Project.active.has_module(:contacts_helpdesk).each do |project|
|
||||
begin
|
||||
HelpdeskMailer.check_project(project.id)
|
||||
rescue Exception => e
|
||||
puts "Helpdesk MailHandler: can't get mail for project #{project.name} with error: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user