class HelpdeskTicket < ActiveRecord::Base HELPDESK_EMAIL_SOURCE = 0 HELPDESK_WEB_SOURCE = 1 HELPDESK_PHONE_SOURCE = 2 HELPDESK_TWITTER_SOURCE = 3 HELPDESK_CONVERSATION_SOURCE = 4 SEND_AS_NOTIFICATION = 1 SEND_AS_MESSAGE = 2 attr_accessible :vote, :vote_comment,:from_address, :to_address, :cc_address, :ticket_date, :message_id, :is_incoming, :customer, :issue, :source, :contact_id, :ticket_time attr_accessor :ticket_time unloadable belongs_to :customer, :class_name => 'Contact', :foreign_key => 'contact_id' belongs_to :issue has_one :message_file, :class_name => "Attachment", :as => :container, :dependent => :destroy acts_as_attachable :view_permission => :view_issues, :delete_permission => :edit_issues if ActiveRecord::VERSION::MAJOR >= 4 acts_as_activity_provider :type => 'helpdesk_tickets', :permission => :view_helpdesk_tickets, :timestamp => "#{table_name}.ticket_date", :author_key => "#{Issue.table_name}.author_id", :scope => eager_load(:issue => :project) else acts_as_activity_provider :type => 'helpdesk_tickets', :permission => :view_helpdesk_tickets, :timestamp => "#{table_name}.ticket_date", :author_key => "#{Issue.table_name}.author_id", :find_options => {:include => {:issue => :project}} end acts_as_event :datetime => :ticket_date, :project_key => "#{Project.table_name}.id", :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.issue_id}}, :type => Proc.new {|o| 'icon icon-email' + (o.issue.closed? ? ' closed' : '') if o.issue }, :title => Proc.new {|o| "##{o.issue.id} (#{o.issue.status}): #{o.issue.subject}" if o.issue }, :author => Proc.new {|o| o.customer}, :description => Proc.new{|o| o.issue.description if o.issue} accepts_nested_attributes_for :customer after_create :set_ticket_private before_save :calculate_metrics validates_presence_of :customer, :ticket_date def initialize(attributes=nil, *args) super if new_record? # set default values for new records only self.ticket_date ||= Time.now self.source ||= HelpdeskTicket::HELPDESK_EMAIL_SOURCE end end def ticket_time self.ticket_date.to_s(:time) unless self.ticket_date.blank? end def ticket_time=(val) if !self.ticket_date.blank? && val.to_s.gsub(/\s/, "").match(/^(\d{1,2}):(\d{1,2})$/) timezone = ticket_date.try(:time_zone).try(:name) || Time.zone.name self.ticket_date = ActiveSupport::TimeZone.new(timezone).local_to_utc(self.ticket_date.utc). in_time_zone(timezone). change({:hour => $1.to_i % 24, :min => $2.to_i % 60}) end end def recalculate_events unless issue.closed? close_journal_id = nil end end def available_addresses @available_addresses ||= ([self.default_to_address] | self.customer.emails.map{|e| e} | [self.from_address.blank? ? nil : self.from_address.downcase.strip]).compact.uniq if self.customer end def default_to_address return last_response_address if last_journal_message && last_journal_message.is_incoming? address = self.from_address.blank? ? "" : self.from_address.downcase.strip self.customer.emails.include?(address) ? address : self.customer.primary_email end def last_reply_customer return customer unless default_to_address customer.primary_email == default_to_address ? customer : Contact.find_by_emails([default_to_address]).first end def cc_addresses @cc_addresses = ((self.issue.contacts ? self.issue.contacts.map(&:primary_email) : []) | cc_address.to_s.split(',')).compact.uniq end def project issue.project if issue end def author issue.author if issue end def customer_name customer.name if customer end def responses @responses ||= JournalMessage. joins(:journal). where(:journals => {:journalized_id => self.issue_id}). order("#{JournalMessage.table_name}.message_date ASC") end def reaction_date @reaction_date ||= self.issue.journals. joins(:journal_message). where("#{JournalMessage.table_name}.journal_id IS NULL OR #{JournalMessage.table_name}.is_incoming = ?", false). order("#{Journal.table_name}.created_on ASC"). first. try(:created_on).try(:utc) end def response_addresses responses.where(:is_incoming => true).map { |response| response.from_address }.uniq end def first_response_date @first_response_date ||= responses.select {|r| !r.is_incoming? }.first.try(:message_date).try(:utc) end def last_response_time @last_response_time ||= last_journal_message && last_journal_message.is_incoming? && !self.issue.closed? ? last_journal_message.message_date.utc : nil end def last_response_address response_addresses.last end def last_agent_response @last_agent_response ||= responses.select { |r| !r.is_incoming? }.last end def last_journal_message @last_journal_message ||= responses.last end def last_customer_response @last_customer_response ||= responses.select { |r| r.is_incoming? }.last end def average_response_time end def ticket_source_name case self.source when HelpdeskTicket::HELPDESK_EMAIL_SOURCE then l(:label_helpdesk_tickets_email) when HelpdeskTicket::HELPDESK_PHONE_SOURCE then l(:label_helpdesk_tickets_phone) when HelpdeskTicket::HELPDESK_WEB_SOURCE then l(:label_helpdesk_tickets_web) when HelpdeskTicket::HELPDESK_TWITTER_SOURCE then l(:label_helpdesk_tickets_twitter) when HelpdeskTicket::HELPDESK_CONVERSATION_SOURCE then l(:label_helpdesk_tickets_conversation) else "" end end def ticket_source_icon case self.source when HelpdeskTicket::HELPDESK_EMAIL_SOURCE then "icon-email" when HelpdeskTicket::HELPDESK_PHONE_SOURCE then "icon-call" when HelpdeskTicket::HELPDESK_WEB_SOURCE then "icon-web" when HelpdeskTicket::HELPDESK_TWITTER_SOURCE then "icon-twitter" else "icon-helpdesk" end end def content issue.description if issue end def customer_email customer.primary_email if customer end def last_message @last_message ||= JournalMessage.eager_load(:journal => :issue).where(:issues => {:id => issue.id}).order("#{Journal.table_name}.created_on ASC").last || self end def last_message_date last_message.is_a?(HelpdeskTicket) ? self.ticket_date : last_message.message_date if last_message end def ticket_date return nil if super.blank? zone = User.current.time_zone zone ? super.in_time_zone(zone) : (super.utc? ? super.localtime : super) end def token Digest::MD5.hexdigest("#{issue.id}:#{self.ticket_date.utc}:#{Rails.application.config.secret_token}") end def calculate_metrics self.reaction_time = reaction_date - ticket_date.utc if reaction_date && ticket_date self.first_response_time = first_response_date - ticket_date.utc if first_response_date && ticket_date self.resolve_time = self.issue.closed? ? self.issue.closed_on - ticket_date.utc : nil if ticket_date && self.issue.closed_on && last_agent_response self.last_agent_response_at = last_agent_response.message_date if last_agent_response self.last_customer_response_at = last_customer_response.message_date if last_customer_response end def self.vote_message(vote) case vote.to_i when 0 l(:label_helpdesk_mark_notgood) when 1 l(:label_helpdesk_mark_justok) when 2 l(:label_helpdesk_mark_awesome) else "" end end def update_vote(new_vote, comment = nil) old_vote = vote old_vote_comment = vote_comment if update_attributes(:vote => new_vote, :vote_comment => comment ) if old_vote != vote || old_vote_comment != vote_comment journal = Journal.new(:journalized => issue, :user => User.current) journal.details << JournalDetail.new(:property => 'attr', :prop_key => 'vote', :old_value => old_vote, :value => vote) if old_vote != vote journal.details << JournalDetail.new(:property => 'attr', :prop_key => 'vote_comment', :old_value => old_vote_comment, :value => vote_comment) if old_vote_comment != vote_comment journal.save end end end def self.autoclose(project) return unless RedmineHelpdesk.autoclose_tickets_after > 0 issues = Issue.includes(:helpdesk_ticket).where(:project_id => project.id). where(:status_id => RedmineHelpdesk.autoclose_from_status). where('created_on < ?', Time.now - RedmineHelpdesk.autoclose_time_interval) issues.find_each do |issue| issue.init_journal(User.anonymous) issue.current_journal.notes = I18n.t('label_helpdesk_autoclosed_ticket') issue.status_id = RedmineHelpdesk.autoclose_to_status issue.save end end private def set_ticket_private return unless RedmineHelpdesk.settings["helpdesk_assign_contact_user"].to_i > 0 issue.assign_attributes(:is_private => true) if RedmineHelpdesk.settings["helpdesk_create_private_tickets"].to_i > 0 issue.save unless issue.new_record? end end