2014-02-03 19:23:00 +00:00
|
|
|
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
2013-06-12 15:59:58 +00:00
|
|
|
|
2012-07-29 20:25:31 +00:00
|
|
|
class Ticket < ApplicationModel
|
2013-09-29 21:37:49 +00:00
|
|
|
include Ticket::Escalation
|
|
|
|
include Ticket::Subject
|
|
|
|
include Ticket::Permission
|
2014-10-17 12:15:54 +00:00
|
|
|
load 'ticket/assets.rb'
|
2013-09-29 21:37:49 +00:00
|
|
|
include Ticket::Assets
|
2014-10-17 12:15:54 +00:00
|
|
|
load 'ticket/history_log.rb'
|
2013-09-29 21:37:49 +00:00
|
|
|
include Ticket::HistoryLog
|
2014-10-17 12:15:54 +00:00
|
|
|
load 'ticket/activity_stream_log.rb'
|
2013-09-29 21:37:49 +00:00
|
|
|
include Ticket::ActivityStreamLog
|
2014-10-17 12:15:54 +00:00
|
|
|
load 'ticket/search_index.rb'
|
2014-01-27 22:59:41 +00:00
|
|
|
include Ticket::SearchIndex
|
2013-09-29 21:37:49 +00:00
|
|
|
extend Ticket::Search
|
|
|
|
|
2015-12-14 09:23:14 +00:00
|
|
|
store :preferences
|
2015-01-07 21:28:15 +00:00
|
|
|
before_create :check_generate, :check_defaults, :check_title
|
2015-02-11 22:05:31 +00:00
|
|
|
before_update :check_defaults, :check_title, :reset_pending_time
|
2012-07-20 08:08:31 +00:00
|
|
|
before_destroy :destroy_dependencies
|
2015-02-01 12:08:11 +00:00
|
|
|
|
|
|
|
notify_clients_support
|
2013-09-29 16:40:42 +00:00
|
|
|
|
2015-02-25 20:52:14 +00:00
|
|
|
latest_change_support
|
|
|
|
|
2015-04-27 13:42:53 +00:00
|
|
|
activity_stream_support ignore_attributes: {
|
|
|
|
create_article_type_id: true,
|
|
|
|
create_article_sender_id: true,
|
|
|
|
article_count: true,
|
2015-09-22 23:49:27 +00:00
|
|
|
first_response: true,
|
|
|
|
first_response_escal_date: true,
|
|
|
|
first_response_sla_time: true,
|
|
|
|
first_response_in_min: true,
|
|
|
|
first_response_diff_in_min: true,
|
|
|
|
close_time: true,
|
|
|
|
close_time_escal_date: true,
|
|
|
|
close_time_sla_time: true,
|
|
|
|
close_time_in_min: true,
|
|
|
|
close_time_diff_in_min: true,
|
|
|
|
update_time_escal_date: true,
|
2016-02-04 13:35:05 +00:00
|
|
|
update_time_sla_time: true,
|
2015-09-22 23:49:27 +00:00
|
|
|
update_time_in_min: true,
|
|
|
|
update_time_diff_in_min: true,
|
|
|
|
last_contact: true,
|
|
|
|
last_contact_agent: true,
|
|
|
|
last_contact_customer: true,
|
2013-10-05 12:56:03 +00:00
|
|
|
}
|
2013-09-29 21:37:49 +00:00
|
|
|
|
2015-04-27 13:42:53 +00:00
|
|
|
history_support ignore_attributes: {
|
|
|
|
create_article_type_id: true,
|
|
|
|
create_article_sender_id: true,
|
|
|
|
article_count: true,
|
2013-09-29 21:37:49 +00:00
|
|
|
}
|
2013-01-01 20:29:26 +00:00
|
|
|
|
2015-10-27 07:41:55 +00:00
|
|
|
search_index_support
|
2014-01-27 22:59:41 +00:00
|
|
|
|
2012-04-16 08:04:49 +00:00
|
|
|
belongs_to :group
|
2015-04-27 13:42:53 +00:00
|
|
|
has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update
|
2012-11-13 10:34:45 +00:00
|
|
|
belongs_to :organization
|
2015-04-27 13:42:53 +00:00
|
|
|
belongs_to :state, class_name: 'Ticket::State'
|
|
|
|
belongs_to :priority, class_name: 'Ticket::Priority'
|
|
|
|
belongs_to :owner, class_name: 'User'
|
|
|
|
belongs_to :customer, class_name: 'User'
|
|
|
|
belongs_to :created_by, class_name: 'User'
|
|
|
|
belongs_to :updated_by, class_name: 'User'
|
|
|
|
belongs_to :create_article_type, class_name: 'Ticket::Article::Type'
|
|
|
|
belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender'
|
2012-04-10 14:06:46 +00:00
|
|
|
|
2014-09-09 23:42:20 +00:00
|
|
|
self.inheritance_column = nil
|
|
|
|
|
2013-03-28 23:13:15 +00:00
|
|
|
attr_accessor :callback_loop
|
|
|
|
|
2013-08-17 21:10:36 +00:00
|
|
|
=begin
|
|
|
|
|
2013-08-17 21:13:34 +00:00
|
|
|
list of agents in group of ticket
|
2013-08-17 21:10:36 +00:00
|
|
|
|
|
|
|
ticket = Ticket.find(123)
|
2013-08-17 21:13:34 +00:00
|
|
|
result = ticket.agent_of_group
|
2013-08-17 21:10:36 +00:00
|
|
|
|
|
|
|
returns
|
|
|
|
|
2013-08-17 21:13:34 +00:00
|
|
|
result = [user1, user2, ...]
|
2013-08-17 21:10:36 +00:00
|
|
|
|
|
|
|
=end
|
|
|
|
|
2012-04-10 14:06:46 +00:00
|
|
|
def agent_of_group
|
2016-02-07 13:00:29 +00:00
|
|
|
Group.find(group_id)
|
|
|
|
.users.where(active: true)
|
|
|
|
.joins(:roles)
|
|
|
|
.where('roles.name' => Z_ROLENAME_AGENT, 'roles.active' => true)
|
2016-02-08 07:27:02 +00:00
|
|
|
.order('users.login')
|
2016-02-07 13:00:29 +00:00
|
|
|
.uniq()
|
2012-04-10 14:06:46 +00:00
|
|
|
end
|
|
|
|
|
2013-08-16 14:30:51 +00:00
|
|
|
=begin
|
|
|
|
|
2014-11-10 07:34:20 +00:00
|
|
|
get user access conditions
|
|
|
|
|
2015-02-15 09:23:55 +00:00
|
|
|
conditions = Ticket.access_condition( User.find(1) )
|
2014-11-10 07:34:20 +00:00
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
result = [user1, user2, ...]
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.access_condition(user)
|
|
|
|
access_condition = []
|
2015-05-08 08:15:45 +00:00
|
|
|
if user.role?(Z_ROLENAME_AGENT)
|
2015-04-30 17:51:31 +00:00
|
|
|
group_ids = Group.select( 'groups.id' ).joins(:users)
|
2016-01-15 17:22:57 +00:00
|
|
|
.where( 'groups_users.user_id = ?', user.id )
|
|
|
|
.where( 'groups.active = ?', true )
|
|
|
|
.map( &:id )
|
2014-11-10 07:34:20 +00:00
|
|
|
access_condition = [ 'group_id IN (?)', group_ids ]
|
|
|
|
else
|
2016-01-15 17:22:57 +00:00
|
|
|
access_condition = if !user.organization || ( !user.organization.shared || user.organization.shared == false )
|
|
|
|
[ 'tickets.customer_id = ?', user.id ]
|
|
|
|
else
|
|
|
|
[ '( tickets.customer_id = ? OR tickets.organization_id = ? )', user.id, user.organization.id ]
|
|
|
|
end
|
2014-11-10 07:34:20 +00:00
|
|
|
end
|
|
|
|
access_condition
|
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
2015-05-21 14:40:04 +00:00
|
|
|
processes tickets which have reached their pending time and sets next state_id
|
|
|
|
|
2016-02-22 23:28:13 +00:00
|
|
|
processed_tickets = Ticket.process_pending
|
2015-05-21 14:40:04 +00:00
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
processed_tickets = [<Ticket>, ...]
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.process_pending
|
2016-02-20 10:12:15 +00:00
|
|
|
result = []
|
2015-05-21 14:40:04 +00:00
|
|
|
|
2016-02-20 10:12:15 +00:00
|
|
|
# process pending action tickets
|
|
|
|
pending_action = Ticket::StateType.find_by(name: 'pending action')
|
|
|
|
ticket_states_pending_action = Ticket::State.where(state_type_id: pending_action)
|
|
|
|
.where.not(next_state_id: nil)
|
|
|
|
if !ticket_states_pending_action.empty?
|
|
|
|
next_state_map = {}
|
|
|
|
ticket_states_pending_action.each { |state|
|
|
|
|
next_state_map[state.id] = state.next_state_id
|
|
|
|
}
|
|
|
|
|
|
|
|
tickets = where(state_id: next_state_map.keys)
|
|
|
|
.where('pending_time <= ?', Time.zone.now)
|
|
|
|
|
|
|
|
tickets.each { |ticket|
|
|
|
|
ticket.state_id = next_state_map[ticket.state_id]
|
|
|
|
ticket.updated_at = Time.zone.now
|
|
|
|
ticket.updated_by_id = 1
|
|
|
|
ticket.save!
|
|
|
|
|
|
|
|
# we do not have an destructor at this point, so we need to
|
2016-04-14 07:17:13 +00:00
|
|
|
# execute object transaction manually
|
|
|
|
Observer::Transaction.commit
|
2016-02-20 10:12:15 +00:00
|
|
|
|
|
|
|
result.push ticket
|
|
|
|
}
|
|
|
|
end
|
2015-05-21 14:40:04 +00:00
|
|
|
|
2016-02-20 10:12:15 +00:00
|
|
|
# process pending reminder tickets
|
|
|
|
pending_reminder = Ticket::StateType.find_by(name: 'pending reminder')
|
|
|
|
ticket_states_pending_reminder = Ticket::State.where(state_type_id: pending_reminder)
|
2015-05-21 14:40:04 +00:00
|
|
|
|
2016-02-20 10:12:15 +00:00
|
|
|
if !ticket_states_pending_reminder.empty?
|
|
|
|
reminder_state_map = {}
|
|
|
|
ticket_states_pending_reminder.each { |state|
|
|
|
|
reminder_state_map[state.id] = state.next_state_id
|
|
|
|
}
|
2015-05-21 14:40:04 +00:00
|
|
|
|
2016-02-20 10:12:15 +00:00
|
|
|
tickets = where(state_id: reminder_state_map.keys)
|
|
|
|
.where('pending_time <= ?', Time.zone.now)
|
2015-05-21 14:40:04 +00:00
|
|
|
|
2016-02-20 10:12:15 +00:00
|
|
|
tickets.each { |ticket|
|
2015-05-21 14:40:04 +00:00
|
|
|
|
2016-02-20 10:12:15 +00:00
|
|
|
# send notification
|
2016-04-15 21:56:10 +00:00
|
|
|
Transaction::BackgroundJob.run(
|
|
|
|
object: 'Ticket',
|
|
|
|
type: 'reminder_reached',
|
2016-02-20 10:12:15 +00:00
|
|
|
ticket_id: ticket.id,
|
|
|
|
article_id: ticket.articles.last.id,
|
|
|
|
)
|
|
|
|
|
|
|
|
result.push ticket
|
|
|
|
}
|
|
|
|
end
|
2015-05-22 06:58:32 +00:00
|
|
|
|
2015-05-21 14:40:04 +00:00
|
|
|
result
|
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
2016-02-22 23:28:13 +00:00
|
|
|
processes escalated tickets
|
|
|
|
|
|
|
|
processed_tickets = Ticket.process_escalation
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
processed_tickets = [<Ticket>, ...]
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def self.process_escalation
|
|
|
|
result = []
|
|
|
|
|
|
|
|
# get max warning diff
|
|
|
|
|
|
|
|
tickets = where('escalation_time <= ?', Time.zone.now + 15.minutes)
|
|
|
|
|
|
|
|
tickets.each {|ticket|
|
|
|
|
|
|
|
|
# get sla
|
|
|
|
sla = ticket.escalation_calculation_get_sla
|
|
|
|
|
|
|
|
# send escalation
|
|
|
|
if ticket.escalation_time < Time.zone.now
|
2016-04-15 21:56:10 +00:00
|
|
|
Transaction::BackgroundJob.run(
|
|
|
|
object: 'Ticket',
|
|
|
|
type: 'escalation',
|
2016-02-22 23:28:13 +00:00
|
|
|
ticket_id: ticket.id,
|
|
|
|
article_id: ticket.articles.last.id,
|
|
|
|
)
|
|
|
|
result.push ticket
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
# check if warning need to be sent
|
2016-04-15 21:56:10 +00:00
|
|
|
Transaction::BackgroundJob.run(
|
|
|
|
object: 'Ticket',
|
|
|
|
type: 'escalation_warning',
|
2016-02-22 23:28:13 +00:00
|
|
|
ticket_id: ticket.id,
|
|
|
|
article_id: ticket.articles.last.id,
|
|
|
|
)
|
|
|
|
result.push ticket
|
|
|
|
}
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
2013-08-16 14:30:51 +00:00
|
|
|
merge tickets
|
|
|
|
|
2013-08-17 20:04:57 +00:00
|
|
|
ticket = Ticket.find(123)
|
|
|
|
result = ticket.merge_to(
|
2015-09-03 09:14:09 +00:00
|
|
|
ticket_id: 123,
|
|
|
|
user_id: 123,
|
2013-08-16 14:30:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
result = true|false
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2012-07-03 13:24:31 +00:00
|
|
|
def merge_to(data)
|
2012-11-07 23:47:05 +00:00
|
|
|
|
2012-07-03 13:24:31 +00:00
|
|
|
# update articles
|
2015-05-07 12:10:38 +00:00
|
|
|
Ticket::Article.where( ticket_id: id ).each(&:touch)
|
2015-03-09 01:31:09 +00:00
|
|
|
|
|
|
|
# quiet update of reassign of articles
|
2015-05-07 12:10:38 +00:00
|
|
|
Ticket::Article.where( ticket_id: id ).update_all( ['ticket_id = ?', data[:ticket_id] ] )
|
2012-11-07 23:47:05 +00:00
|
|
|
|
2015-01-31 13:33:05 +00:00
|
|
|
# touch new ticket (to broadcast change)
|
|
|
|
Ticket.find( data[:ticket_id] ).touch
|
|
|
|
|
2012-07-03 13:24:31 +00:00
|
|
|
# update history
|
2012-11-07 23:47:05 +00:00
|
|
|
|
2012-07-03 13:24:31 +00:00
|
|
|
# create new merge article
|
|
|
|
Ticket::Article.create(
|
2015-05-07 12:10:38 +00:00
|
|
|
ticket_id: id,
|
2015-04-27 13:42:53 +00:00
|
|
|
type_id: Ticket::Article::Type.lookup( name: 'note' ).id,
|
|
|
|
sender_id: Ticket::Article::Sender.lookup( name: Z_ROLENAME_AGENT ).id,
|
|
|
|
body: 'merged',
|
|
|
|
internal: false,
|
|
|
|
created_by_id: data[:user_id],
|
|
|
|
updated_by_id: data[:user_id],
|
2012-07-03 13:24:31 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# add history to both
|
|
|
|
|
|
|
|
# link tickets
|
2012-08-21 10:28:41 +00:00
|
|
|
Link.add(
|
2015-04-27 13:42:53 +00:00
|
|
|
link_type: 'parent',
|
|
|
|
link_object_source: 'Ticket',
|
|
|
|
link_object_source_value: data[:ticket_id],
|
|
|
|
link_object_target: 'Ticket',
|
2015-05-07 12:10:38 +00:00
|
|
|
link_object_target_value: id
|
2012-08-21 10:28:41 +00:00
|
|
|
)
|
2012-07-03 13:24:31 +00:00
|
|
|
|
|
|
|
# set state to 'merged'
|
2015-04-27 13:42:53 +00:00
|
|
|
self.state_id = Ticket::State.lookup( name: 'merged' ).id
|
2012-07-03 13:24:31 +00:00
|
|
|
|
|
|
|
# rest owner
|
2015-05-07 10:15:40 +00:00
|
|
|
self.owner_id = User.find_by( login: '-' ).id
|
2012-07-03 13:24:31 +00:00
|
|
|
|
|
|
|
# save ticket
|
2015-05-07 12:10:38 +00:00
|
|
|
save
|
2015-04-01 14:33:24 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
=begin
|
|
|
|
|
2015-09-03 09:14:09 +00:00
|
|
|
check if online notifcation should be shown in general as already seen with current state
|
2015-04-01 14:33:24 +00:00
|
|
|
|
|
|
|
ticket = Ticket.find(1)
|
2015-09-03 09:14:09 +00:00
|
|
|
seen = ticket.online_notification_seen_state(user_id_check)
|
|
|
|
|
|
|
|
returns
|
|
|
|
|
|
|
|
result = true # or false
|
|
|
|
|
|
|
|
check if online notifcation should be shown for this user as already seen with current state
|
|
|
|
|
|
|
|
ticket = Ticket.find(1)
|
|
|
|
seen = ticket.online_notification_seen_state(check_user_id)
|
2015-04-01 11:47:10 +00:00
|
|
|
|
2015-04-01 14:33:24 +00:00
|
|
|
returns
|
|
|
|
|
2015-07-26 21:21:16 +00:00
|
|
|
result = true # or false
|
2015-04-01 14:33:24 +00:00
|
|
|
|
|
|
|
=end
|
2015-04-01 11:47:10 +00:00
|
|
|
|
2015-09-03 09:14:09 +00:00
|
|
|
def online_notification_seen_state(user_id_check = nil)
|
2016-02-20 12:36:59 +00:00
|
|
|
state = Ticket::State.lookup(id: state_id)
|
|
|
|
state_type = Ticket::StateType.lookup(id: state.state_type_id)
|
2015-07-26 21:21:16 +00:00
|
|
|
|
2016-02-20 13:09:56 +00:00
|
|
|
# always to set unseen for ticket owner
|
|
|
|
if state_type.name != 'merged'
|
|
|
|
if user_id_check
|
|
|
|
return false if user_id_check == owner_id && user_id_check != updated_by_id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-26 21:21:16 +00:00
|
|
|
# set all to seen if pending action state is a closed or merged state
|
|
|
|
if state_type.name == 'pending action' && state.next_state_id
|
2016-02-20 12:36:59 +00:00
|
|
|
state = Ticket::State.lookup(id: state.next_state_id)
|
|
|
|
state_type = Ticket::StateType.lookup(id: state.state_type_id)
|
2015-07-26 21:21:16 +00:00
|
|
|
end
|
|
|
|
|
2015-08-31 09:13:40 +00:00
|
|
|
# set all to seen if new state is pending reminder state
|
2015-09-03 09:14:09 +00:00
|
|
|
if state_type.name == 'pending reminder'
|
|
|
|
if user_id_check
|
|
|
|
return false if owner_id == 1
|
|
|
|
return false if updated_by_id != owner_id && user_id_check == owner_id
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
2015-08-31 09:13:40 +00:00
|
|
|
|
2015-07-26 21:21:16 +00:00
|
|
|
# set all to seen if new state is a closed or merged state
|
2015-04-01 14:33:24 +00:00
|
|
|
return true if state_type.name == 'closed'
|
|
|
|
return true if state_type.name == 'merged'
|
|
|
|
false
|
2012-07-03 13:24:31 +00:00
|
|
|
end
|
|
|
|
|
2015-09-23 14:25:06 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
get count of tickets and tickets which match on selector
|
|
|
|
|
2015-10-12 13:44:34 +00:00
|
|
|
ticket_count, tickets = Ticket.selectors(params[:condition], limit, current_user)
|
2015-09-23 14:25:06 +00:00
|
|
|
|
|
|
|
=end
|
|
|
|
|
2015-10-12 13:44:34 +00:00
|
|
|
def self.selectors(selectors, limit = 10, current_user = nil)
|
2016-03-01 14:26:46 +00:00
|
|
|
raise 'no selectors given' if !selectors
|
2015-10-14 14:33:26 +00:00
|
|
|
query, bind_params, tables = selector2sql(selectors, current_user)
|
2015-09-17 18:39:51 +00:00
|
|
|
return [] if !query
|
2015-10-12 13:44:34 +00:00
|
|
|
|
|
|
|
if !current_user
|
|
|
|
ticket_count = Ticket.where(query, *bind_params).joins(tables).count
|
|
|
|
tickets = Ticket.where(query, *bind_params).joins(tables).limit(limit)
|
|
|
|
return [ticket_count, tickets]
|
|
|
|
end
|
|
|
|
|
|
|
|
access_condition = Ticket.access_condition(current_user)
|
|
|
|
ticket_count = Ticket.where(access_condition).where(query, *bind_params).joins(tables).count
|
|
|
|
tickets = Ticket.where(access_condition).where(query, *bind_params).joins(tables).limit(limit)
|
2015-09-17 01:04:16 +00:00
|
|
|
[ticket_count, tickets]
|
|
|
|
end
|
|
|
|
|
2015-09-23 14:25:06 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
generate condition query to search for tickets based on condition
|
|
|
|
|
2015-10-14 14:33:26 +00:00
|
|
|
query_condition, bind_condition = selector2sql(params[:condition], current_user)
|
2015-09-23 14:25:06 +00:00
|
|
|
|
|
|
|
condition example
|
|
|
|
|
|
|
|
{
|
|
|
|
'ticket.state_id' => {
|
|
|
|
operator: 'is',
|
|
|
|
value: [1,2,5]
|
2015-10-12 13:44:34 +00:00
|
|
|
},
|
|
|
|
'ticket.created_at' => {
|
|
|
|
operator: 'after (absolute)', # after,before
|
|
|
|
value: '2015-10-17T06:00:00.000Z',
|
|
|
|
},
|
|
|
|
'ticket.created_at' => {
|
|
|
|
operator: 'within next (relative)', # before,within,in,after
|
|
|
|
range: 'day', # minute|hour|day|month|year
|
|
|
|
value: '25',
|
|
|
|
},
|
2015-10-14 14:33:26 +00:00
|
|
|
'ticket.owner_id' => {
|
|
|
|
operator: 'is', # is not
|
|
|
|
pre_condition: 'current_user.id',
|
|
|
|
},
|
|
|
|
'ticket.owner_id' => {
|
|
|
|
operator: 'is', # is not
|
|
|
|
pre_condition: 'specific',
|
|
|
|
value: 4711,
|
|
|
|
},
|
2016-02-26 12:19:57 +00:00
|
|
|
'ticket.escalation_time' => {
|
|
|
|
operator: 'is not', # not
|
|
|
|
value: nil,
|
|
|
|
}
|
2015-09-23 14:25:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
2015-10-14 14:33:26 +00:00
|
|
|
def self.selector2sql(selectors, current_user = nil)
|
|
|
|
current_user_id = UserInfo.current_user_id
|
|
|
|
if current_user
|
|
|
|
current_user_id = current_user.id
|
|
|
|
end
|
2015-09-17 01:04:16 +00:00
|
|
|
return if !selectors
|
2015-10-14 14:33:26 +00:00
|
|
|
|
|
|
|
# remember query and bind params
|
2015-09-17 01:04:16 +00:00
|
|
|
query = ''
|
|
|
|
bind_params = []
|
2016-01-19 22:30:23 +00:00
|
|
|
like = Rails.application.config.db_like
|
2015-09-17 01:04:16 +00:00
|
|
|
|
2015-10-14 14:33:26 +00:00
|
|
|
# get tables to join
|
2015-10-12 13:44:34 +00:00
|
|
|
tables = ''
|
2015-09-17 18:39:51 +00:00
|
|
|
selectors.each {|attribute, selector|
|
|
|
|
selector = attribute.split(/\./)
|
|
|
|
next if !selector[1]
|
|
|
|
next if selector[0] == 'ticket'
|
|
|
|
next if tables.include?(selector[0])
|
2015-10-12 13:44:34 +00:00
|
|
|
if query != ''
|
|
|
|
query += ' AND '
|
|
|
|
end
|
|
|
|
if selector[0] == 'customer'
|
|
|
|
tables += ', users customers'
|
|
|
|
query += 'tickets.customer_id = customers.id'
|
|
|
|
elsif selector[0] == 'organization'
|
|
|
|
tables += ', organizations'
|
|
|
|
query += 'tickets.organization_id = organizations.id'
|
|
|
|
elsif selector[0] == 'owner'
|
|
|
|
tables += ', users owners'
|
|
|
|
query += 'tickets.owner_id = owners.id'
|
|
|
|
else
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "invalid selector #{attribute.inspect}->#{selector.inspect}"
|
2015-10-12 13:44:34 +00:00
|
|
|
end
|
2015-09-17 18:39:51 +00:00
|
|
|
}
|
|
|
|
|
2015-10-14 14:33:26 +00:00
|
|
|
# add conditions
|
2015-09-23 14:25:06 +00:00
|
|
|
selectors.each {|attribute, selector_raw|
|
2015-10-14 14:33:26 +00:00
|
|
|
|
|
|
|
# validation
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Invalid selector #{selector_raw.inspect}" if !selector_raw
|
|
|
|
raise "Invalid selector #{selector_raw.inspect}" if !selector_raw.respond_to?(:key?)
|
2015-09-23 14:25:06 +00:00
|
|
|
selector = selector_raw.stringify_keys
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Invalid selector, operator missing #{selector.inspect}" if !selector['operator']
|
2015-10-14 14:33:26 +00:00
|
|
|
|
2016-02-26 12:19:57 +00:00
|
|
|
# validate value / allow empty but only if pre_condition exists
|
2016-03-18 02:04:49 +00:00
|
|
|
if !selector.key?('value') || ((selector['value'].class == String || selector['value'].class == Array) && (selector['value'].respond_to?(:empty?) && selector['value'].empty?))
|
2015-10-14 14:33:26 +00:00
|
|
|
return nil if selector['pre_condition'].nil? || (selector['pre_condition'].respond_to?(:empty?) && selector['pre_condition'].empty?)
|
|
|
|
end
|
|
|
|
|
|
|
|
# validate pre_condition values
|
2016-04-14 07:17:13 +00:00
|
|
|
return nil if selector['pre_condition'] && selector['pre_condition'] !~ /^(not_set|current_user\.|specific)/
|
2015-10-14 14:33:26 +00:00
|
|
|
|
|
|
|
# get attributes
|
2015-09-17 18:39:51 +00:00
|
|
|
attributes = attribute.split(/\./)
|
|
|
|
attribute = "#{attributes[0]}s.#{attributes[1]}"
|
2015-10-14 14:33:26 +00:00
|
|
|
|
|
|
|
if query != ''
|
|
|
|
query += ' AND '
|
|
|
|
end
|
|
|
|
|
2015-09-17 01:04:16 +00:00
|
|
|
if selector['operator'] == 'is'
|
2016-04-14 07:17:13 +00:00
|
|
|
if selector['pre_condition'] == 'not_set'
|
2015-10-14 14:33:26 +00:00
|
|
|
if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
|
2016-04-14 09:39:54 +00:00
|
|
|
query += "#{attribute} IN (?)"
|
2015-10-14 14:33:26 +00:00
|
|
|
bind_params.push 1
|
|
|
|
else
|
|
|
|
query += "#{attribute} IS NOT NULL"
|
|
|
|
end
|
|
|
|
elsif selector['pre_condition'] == 'current_user.id'
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
|
2015-10-14 14:33:26 +00:00
|
|
|
query += "#{attribute} IN (?)"
|
|
|
|
bind_params.push current_user_id
|
|
|
|
elsif selector['pre_condition'] == 'current_user.organization_id'
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
|
2015-10-14 14:33:26 +00:00
|
|
|
query += "#{attribute} IN (?)"
|
|
|
|
user = User.lookup(id: current_user_id)
|
|
|
|
bind_params.push user.organization_id
|
|
|
|
else
|
2016-02-26 12:19:57 +00:00
|
|
|
# rubocop:disable Style/IfInsideElse
|
|
|
|
if selector['value'].nil?
|
2016-03-09 07:17:49 +00:00
|
|
|
query += "#{attribute} IS NOT NULL"
|
2016-02-26 12:19:57 +00:00
|
|
|
else
|
|
|
|
query += "#{attribute} IN (?)"
|
|
|
|
bind_params.push selector['value']
|
|
|
|
end
|
|
|
|
# rubocop:enable Style/IfInsideElse
|
2015-10-14 14:33:26 +00:00
|
|
|
end
|
2015-09-17 01:04:16 +00:00
|
|
|
elsif selector['operator'] == 'is not'
|
2016-04-14 07:17:13 +00:00
|
|
|
if selector['pre_condition'] == 'not_set'
|
2015-10-14 14:33:26 +00:00
|
|
|
if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
|
2016-04-14 09:39:54 +00:00
|
|
|
query += "#{attribute} NOT IN (?)"
|
2015-10-14 14:33:26 +00:00
|
|
|
bind_params.push 1
|
|
|
|
else
|
|
|
|
query += "#{attribute} IS NULL"
|
|
|
|
end
|
|
|
|
elsif selector['pre_condition'] == 'current_user.id'
|
|
|
|
query += "#{attribute} NOT IN (?)"
|
|
|
|
bind_params.push current_user_id
|
|
|
|
elsif selector['pre_condition'] == 'current_user.organization_id'
|
|
|
|
query += "#{attribute} NOT IN (?)"
|
|
|
|
user = User.lookup(id: current_user_id)
|
|
|
|
bind_params.push user.organization_id
|
|
|
|
else
|
2016-02-26 12:19:57 +00:00
|
|
|
# rubocop:disable Style/IfInsideElse
|
|
|
|
if selector['value'].nil?
|
|
|
|
query += "#{attribute} IS NOT NULL"
|
|
|
|
else
|
|
|
|
query += "#{attribute} NOT IN (?)"
|
|
|
|
bind_params.push selector['value']
|
|
|
|
end
|
|
|
|
# rubocop:enable Style/IfInsideElse
|
2015-10-14 14:33:26 +00:00
|
|
|
end
|
2015-09-17 01:04:16 +00:00
|
|
|
elsif selector['operator'] == 'contains'
|
2016-01-19 22:30:23 +00:00
|
|
|
query += "#{attribute} #{like} (?)"
|
2015-09-17 01:04:16 +00:00
|
|
|
value = "%#{selector['value']}%"
|
|
|
|
bind_params.push value
|
|
|
|
elsif selector['operator'] == 'contains not'
|
2016-01-19 22:30:23 +00:00
|
|
|
query += "#{attribute} NOT #{like} (?)"
|
2015-09-17 01:04:16 +00:00
|
|
|
value = "%#{selector['value']}%"
|
|
|
|
bind_params.push value
|
2015-09-17 18:39:51 +00:00
|
|
|
elsif selector['operator'] == 'before (absolute)'
|
|
|
|
query += "#{attribute} <= ?"
|
|
|
|
bind_params.push selector['value']
|
|
|
|
elsif selector['operator'] == 'after (absolute)'
|
|
|
|
query += "#{attribute} >= ?"
|
2015-09-17 01:04:16 +00:00
|
|
|
bind_params.push selector['value']
|
2015-10-12 13:44:34 +00:00
|
|
|
elsif selector['operator'] == 'within last (relative)'
|
|
|
|
query += "#{attribute} >= ?"
|
|
|
|
time = nil
|
|
|
|
if selector['range'] == 'minute'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.minutes
|
|
|
|
elsif selector['range'] == 'hour'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.hours
|
|
|
|
elsif selector['range'] == 'day'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.days
|
|
|
|
elsif selector['range'] == 'month'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.months
|
|
|
|
elsif selector['range'] == 'year'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.years
|
|
|
|
else
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Unknown selector attributes '#{selector.inspect}'"
|
2015-10-12 13:44:34 +00:00
|
|
|
end
|
|
|
|
bind_params.push time
|
|
|
|
elsif selector['operator'] == 'within next (relative)'
|
2015-10-12 21:31:33 +00:00
|
|
|
query += "#{attribute} <= ?"
|
2015-10-12 13:44:34 +00:00
|
|
|
time = nil
|
|
|
|
if selector['range'] == 'minute'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.minutes
|
|
|
|
elsif selector['range'] == 'hour'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.hours
|
|
|
|
elsif selector['range'] == 'day'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.days
|
|
|
|
elsif selector['range'] == 'month'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.months
|
|
|
|
elsif selector['range'] == 'year'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.years
|
|
|
|
else
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Unknown selector attributes '#{selector.inspect}'"
|
2015-10-12 13:44:34 +00:00
|
|
|
end
|
|
|
|
bind_params.push time
|
2015-09-17 18:39:51 +00:00
|
|
|
elsif selector['operator'] == 'before (relative)'
|
|
|
|
query += "#{attribute} <= ?"
|
2015-10-12 13:44:34 +00:00
|
|
|
time = nil
|
|
|
|
if selector['range'] == 'minute'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.minutes
|
|
|
|
elsif selector['range'] == 'hour'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.hours
|
|
|
|
elsif selector['range'] == 'day'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.days
|
|
|
|
elsif selector['range'] == 'month'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.months
|
|
|
|
elsif selector['range'] == 'year'
|
|
|
|
time = Time.zone.now - selector['value'].to_i.years
|
|
|
|
else
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Unknown selector attributes '#{selector.inspect}'"
|
2015-10-12 13:44:34 +00:00
|
|
|
end
|
|
|
|
bind_params.push time
|
2015-09-17 18:39:51 +00:00
|
|
|
elsif selector['operator'] == 'after (relative)'
|
|
|
|
query += "#{attribute} >= ?"
|
2015-10-12 13:44:34 +00:00
|
|
|
time = nil
|
|
|
|
if selector['range'] == 'minute'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.minutes
|
|
|
|
elsif selector['range'] == 'hour'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.hours
|
|
|
|
elsif selector['range'] == 'day'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.days
|
|
|
|
elsif selector['range'] == 'month'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.months
|
|
|
|
elsif selector['range'] == 'year'
|
|
|
|
time = Time.zone.now + selector['value'].to_i.years
|
|
|
|
else
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Unknown selector attributes '#{selector.inspect}'"
|
2015-10-12 13:44:34 +00:00
|
|
|
end
|
|
|
|
bind_params.push time
|
2015-09-17 01:04:16 +00:00
|
|
|
else
|
2016-03-01 14:26:46 +00:00
|
|
|
raise "Invalid operator '#{selector['operator']}' for '#{selector['value'].inspect}'"
|
2015-09-17 01:04:16 +00:00
|
|
|
end
|
|
|
|
}
|
2015-09-17 18:39:51 +00:00
|
|
|
[query, bind_params, tables]
|
2015-09-17 01:04:16 +00:00
|
|
|
end
|
|
|
|
|
2016-02-20 07:14:51 +00:00
|
|
|
=begin
|
|
|
|
|
|
|
|
get all email references headers of a ticket, to exclude some, parse it as array into method
|
|
|
|
|
|
|
|
references = ticket.get_references
|
|
|
|
|
|
|
|
result
|
|
|
|
|
|
|
|
['message-id-1234', 'message-id-5678']
|
|
|
|
|
|
|
|
ignore references header(s)
|
|
|
|
|
|
|
|
references = ticket.get_references(['message-id-5678'])
|
|
|
|
|
|
|
|
result
|
|
|
|
|
|
|
|
['message-id-1234']
|
|
|
|
|
|
|
|
=end
|
|
|
|
|
|
|
|
def get_references(ignore = [])
|
|
|
|
references = []
|
|
|
|
Ticket::Article.select('in_reply_to, message_id').where(ticket_id: id).each {|article|
|
|
|
|
if !article.in_reply_to.empty?
|
|
|
|
references.push article.in_reply_to
|
|
|
|
end
|
|
|
|
next if !article.message_id
|
|
|
|
next if article.message_id.empty?
|
|
|
|
references.push article.message_id
|
|
|
|
}
|
|
|
|
ignore.each {|item|
|
|
|
|
references.delete(item)
|
|
|
|
}
|
|
|
|
references
|
|
|
|
end
|
|
|
|
|
2012-11-28 10:03:17 +00:00
|
|
|
private
|
2012-11-28 09:46:26 +00:00
|
|
|
|
2013-08-15 22:16:38 +00:00
|
|
|
def check_generate
|
2015-05-07 12:10:38 +00:00
|
|
|
return if number
|
2013-08-15 22:16:38 +00:00
|
|
|
self.number = Ticket::Number.generate
|
2013-06-12 15:59:58 +00:00
|
|
|
end
|
2013-08-15 22:16:38 +00:00
|
|
|
|
2015-01-07 21:28:15 +00:00
|
|
|
def check_title
|
2015-05-07 12:10:38 +00:00
|
|
|
return if !title
|
|
|
|
title.gsub!(/\s|\t|\r/, ' ')
|
2015-01-07 21:28:15 +00:00
|
|
|
end
|
|
|
|
|
2013-06-12 15:59:58 +00:00
|
|
|
def check_defaults
|
2015-05-07 12:10:38 +00:00
|
|
|
if !owner_id
|
2013-06-12 15:59:58 +00:00
|
|
|
self.owner_id = 1
|
|
|
|
end
|
2015-04-30 15:25:04 +00:00
|
|
|
|
2015-05-07 12:10:38 +00:00
|
|
|
return if !customer_id
|
2015-04-30 15:25:04 +00:00
|
|
|
|
2016-03-18 02:04:49 +00:00
|
|
|
customer = User.find(customer_id)
|
2015-05-07 12:10:38 +00:00
|
|
|
return if organization_id == customer.organization_id
|
2015-04-30 15:25:04 +00:00
|
|
|
|
|
|
|
self.organization_id = customer.organization_id
|
2013-06-12 15:59:58 +00:00
|
|
|
end
|
2013-06-12 14:57:29 +00:00
|
|
|
|
2015-02-11 22:05:31 +00:00
|
|
|
def reset_pending_time
|
|
|
|
|
|
|
|
# ignore if no state has changed
|
2015-05-07 12:10:38 +00:00
|
|
|
return if !changes['state_id']
|
2015-02-11 22:05:31 +00:00
|
|
|
|
|
|
|
# check if new state isn't pending*
|
2016-03-18 02:04:49 +00:00
|
|
|
current_state = Ticket::State.lookup(id: state_id)
|
|
|
|
current_state_type = Ticket::StateType.lookup(id: current_state.state_type_id)
|
2015-02-11 22:05:31 +00:00
|
|
|
|
|
|
|
# in case, set pending_time to nil
|
2015-04-30 15:25:04 +00:00
|
|
|
return if current_state_type.name =~ /^pending/i
|
|
|
|
|
|
|
|
self.pending_time = nil
|
2015-02-11 22:05:31 +00:00
|
|
|
end
|
|
|
|
|
2013-08-16 14:30:51 +00:00
|
|
|
def destroy_dependencies
|
2013-06-13 15:03:08 +00:00
|
|
|
|
2013-08-16 14:30:51 +00:00
|
|
|
# delete articles
|
2015-05-07 12:10:38 +00:00
|
|
|
articles.destroy_all
|
2015-04-01 11:14:46 +00:00
|
|
|
|
|
|
|
# destroy online notifications
|
2016-03-18 02:04:49 +00:00
|
|
|
OnlineNotification.remove(self.class.to_s, id)
|
2013-08-16 14:30:51 +00:00
|
|
|
end
|
2012-04-10 14:06:46 +00:00
|
|
|
|
2015-04-27 14:15:29 +00:00
|
|
|
end
|