2016-10-19 03:11:36 +00:00
# Copyright (C) 2012-2016 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
2017-05-02 15:21:13 +00:00
include HasActivityStreamLog
include ChecksClientNotification
include ChecksLatestChangeObserved
include HasHistory
include HasTags
include HasSearchIndexBackend
2017-05-05 09:16:47 +00:00
include HasOnlineNotifications
include HasKarmaActivityLog
include HasLinks
2017-06-16 20:43:09 +00:00
include Ticket :: ChecksAccess
2017-01-31 17:13:45 +00:00
2013-09-29 21:37:49 +00:00
include Ticket :: Escalation
include Ticket :: Subject
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/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
2017-01-31 17:13:45 +00:00
store :preferences
2017-04-11 11:11:44 +00:00
before_create :check_generate , :check_defaults , :check_title , :set_default_state , :set_default_priority
after_create :check_escalation_update
before_update :check_defaults , :check_title , :reset_pending_time
after_update :check_escalation_update
2015-02-01 12:08:11 +00:00
2016-09-09 11:29:33 +00:00
validates :group_id , presence : true
2017-01-31 17:13:45 +00:00
activity_stream_permission 'ticket.agent'
activity_stream_attributes_ignored :organization_id , # organization_id will channge automatically on user update
:create_article_type_id ,
:create_article_sender_id ,
:article_count ,
:first_response_at ,
:first_response_escalation_at ,
:first_response_in_min ,
:first_response_diff_in_min ,
:close_at ,
:close_escalation_at ,
:close_in_min ,
:close_diff_in_min ,
:update_escalation_at ,
:update_in_min ,
:update_diff_in_min ,
:last_contact_at ,
:last_contact_agent_at ,
:last_contact_customer_at ,
:preferences
history_attributes_ignored :create_article_type_id ,
:create_article_sender_id ,
:article_count ,
:preferences
2014-01-27 22:59:41 +00:00
2017-04-07 14:44:34 +00:00
belongs_to :group , class_name : 'Group'
2017-05-05 09:16:47 +00:00
has_many :articles , class_name : 'Ticket::Article' , after_add : :cache_update , after_remove : :cache_update , dependent : :destroy
2017-04-07 14:44:34 +00:00
has_many :ticket_time_accounting , class_name : 'Ticket::TimeAccounting' , dependent : :destroy
belongs_to :organization , class_name : 'Organization'
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
2014-11-10 07:34:20 +00:00
get user access conditions
2017-06-16 20:43:09 +00:00
conditions = Ticket . access_condition ( User . find ( 1 ) , 'full' )
2014-11-10 07:34:20 +00:00
returns
result = [ user1 , user2 , ... ]
= end
2017-06-16 20:43:09 +00:00
def self . access_condition ( user , access )
2016-08-12 16:39:09 +00:00
if user . permissions? ( 'ticket.agent' )
2017-06-16 20:43:09 +00:00
[ 'group_id IN (?)' , user . group_ids_access ( access ) ]
elsif ! user . organization || ( ! user . organization . shared || user . organization . shared == false )
[ 'tickets.customer_id = ?' , user . id ]
2014-11-10 07:34:20 +00:00
else
2017-06-16 20:43:09 +00:00
[ '(tickets.customer_id = ? OR tickets.organization_id = ?)' , user . id , user . organization . id ]
2014-11-10 07:34:20 +00:00
end
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 |
2016-09-06 05:51:12 +00:00
Transaction . execute do
ticket . state_id = next_state_map [ ticket . state_id ]
ticket . updated_at = Time . zone . now
ticket . updated_by_id = 1
ticket . save!
end
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-05-19 08:20:38 +00:00
article_id = nil
article = Ticket :: Article . last_customer_agent_article ( ticket . id )
if article
article_id = article . id
end
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-04-22 07:58:28 +00:00
object_id : ticket . id ,
2016-05-19 08:20:38 +00:00
article_id : article_id ,
2016-04-27 07:31:11 +00:00
user_id : 1 ,
2016-02-20 10:12:15 +00:00
)
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
2016-09-14 07:15:30 +00:00
tickets = where ( 'escalation_at <= ?' , Time . zone . now + 15 . minutes )
2016-02-22 23:28:13 +00:00
2016-06-30 20:04:48 +00:00
tickets . each { | ticket |
2016-02-22 23:28:13 +00:00
# get sla
sla = ticket . escalation_calculation_get_sla
2016-05-19 08:20:38 +00:00
article_id = nil
article = Ticket :: Article . last_customer_agent_article ( ticket . id )
if article
article_id = article . id
end
2016-02-22 23:28:13 +00:00
# send escalation
2016-09-14 07:15:30 +00:00
if ticket . escalation_at < Time . zone . now
2016-04-15 21:56:10 +00:00
Transaction :: BackgroundJob . run (
object : 'Ticket' ,
type : 'escalation' ,
2016-04-22 07:58:28 +00:00
object_id : ticket . id ,
2016-05-19 08:20:38 +00:00
article_id : article_id ,
2016-04-27 07:31:11 +00:00
user_id : 1 ,
2016-02-22 23:28:13 +00:00
)
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-04-22 07:58:28 +00:00
object_id : ticket . id ,
2016-05-19 08:20:38 +00:00
article_id : article_id ,
2016-04-27 07:31:11 +00:00
user_id : 1 ,
2016-02-22 23:28:13 +00:00
)
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
2017-07-19 10:03:17 +00:00
# prevent cross merging tickets
2017-07-25 11:53:01 +00:00
target_ticket = Ticket . find_by ( id : data [ :ticket_id ] )
2017-07-19 10:03:17 +00:00
raise 'no target ticket given' if ! target_ticket
2017-07-25 11:53:01 +00:00
raise Exceptions :: UnprocessableEntity , 'ticket already merged, no merge into merged ticket possible' if target_ticket . state . state_type . name == 'merged'
# check different ticket ids
raise Exceptions :: UnprocessableEntity , 'Can\'t merge ticket with it self!' if id == target_ticket . id
2017-07-19 10:03:17 +00:00
2012-07-03 13:24:31 +00:00
# update articles
2016-09-06 05:51:12 +00:00
Transaction . execute do
Ticket :: Article . where ( ticket_id : id ) . each ( & :touch )
# quiet update of reassign of articles
Ticket :: Article . where ( ticket_id : id ) . update_all ( [ 'ticket_id = ?' , data [ :ticket_id ] ] )
# update history
# create new merge article
Ticket :: Article . create (
ticket_id : id ,
type_id : Ticket :: Article :: Type . lookup ( name : 'note' ) . id ,
sender_id : Ticket :: Article :: Sender . lookup ( name : 'Agent' ) . id ,
body : 'merged' ,
internal : false ,
created_by_id : data [ :user_id ] ,
updated_by_id : data [ :user_id ] ,
)
# add history to both
2017-05-02 11:37:20 +00:00
# reassign links to the new ticket
Link . where (
link_object_source_id : Link :: Object . find_by ( name : 'Ticket' ) . id ,
link_object_source_value : id ,
) . update_all ( link_object_source_value : data [ :ticket_id ] )
Link . where (
link_object_target_id : Link :: Object . find_by ( name : 'Ticket' ) . id ,
link_object_target_value : id ,
) . update_all ( link_object_target_value : data [ :ticket_id ] )
2016-09-06 05:51:12 +00:00
# link tickets
Link . add (
link_type : 'parent' ,
link_object_source : 'Ticket' ,
link_object_source_value : data [ :ticket_id ] ,
link_object_target : 'Ticket' ,
link_object_target_value : id
)
# set state to 'merged'
self . state_id = Ticket :: State . lookup ( name : 'merged' ) . id
# rest owner
2017-04-18 07:52:39 +00:00
self . owner_id = 1
2016-09-06 05:51:12 +00:00
# save ticket
save!
# touch new ticket (to broadcast change)
2017-07-19 10:03:17 +00:00
target_ticket . touch
2016-09-06 05:51:12 +00:00
end
true
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
2017-07-26 18:46:31 +00:00
# always to set unseen for ticket owner and users which did not the update
2016-02-20 13:09:56 +00:00
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
2017-06-16 20:43:09 +00:00
ticket_count , tickets = Ticket . selectors ( params [ :condition ] , limit , current_user , 'full' )
2015-09-23 14:25:06 +00:00
= end
2017-06-16 20:43:09 +00:00
def self . selectors ( selectors , limit = 10 , current_user = nil , access = 'full' )
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
2017-08-13 09:54:57 +00:00
ActiveRecord :: Base . transaction ( requires_new : true ) do
begin
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
2015-10-12 13:44:34 +00:00
2017-08-13 09:54:57 +00:00
access_condition = Ticket . access_condition ( current_user , access )
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 )
2017-04-12 13:41:54 +00:00
2017-08-13 09:54:57 +00:00
return [ ticket_count , tickets ]
rescue ActiveRecord :: StatementInvalid = > e
Rails . logger . error e . inspect
Rails . logger . error e . backtrace
raise ActiveRecord :: Rollback
end
end
[ ]
2015-09-17 01:04:16 +00:00
end
2015-09-23 14:25:06 +00:00
= begin
generate condition query to search for tickets based on condition
2016-12-20 23:07:47 +00:00
query_condition , bind_condition , tables = selector2sql ( params [ :condition ] , current_user )
2015-09-23 14:25:06 +00:00
condition example
{
2016-09-11 08:46:01 +00:00
'ticket.title' = > {
operator : 'contains' , # contains not
value : 'some value' ,
} ,
2015-09-23 14:25:06 +00:00
'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-09-14 07:15:30 +00:00
'ticket.escalation_at' = > {
2016-02-26 12:19:57 +00:00
operator : 'is not' , # not
value : nil ,
2017-04-12 13:41:54 +00:00
} ,
'ticket.tags' = > {
operator : 'contains all' , # contains all|contains one|contains all not|contains one not
value : 'tag1, tag2' ,
} ,
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 = ''
2016-06-30 20:04:48 +00:00
selectors . each { | attribute , selector |
2015-09-17 18:39:51 +00:00
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'
2016-05-03 00:36:44 +00:00
elsif selector [ 0 ] == 'article'
tables += ', ticket_articles articles'
query += 'tickets.id = articles.ticket_id'
2015-10-12 13:44:34 +00:00
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
2016-06-30 20:04:48 +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-11-10 15:30:36 +00:00
# validate value / allow empty but only if pre_condition exists and is not specific
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? ) )
2016-11-10 15:30:36 +00:00
return nil if selector [ 'pre_condition' ] . nil?
return nil if selector [ 'pre_condition' ] . respond_to? ( :empty? ) && selector [ 'pre_condition' ] . empty?
return nil if selector [ 'pre_condition' ] == 'specific'
2015-10-14 14:33:26 +00:00
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 ] } "
2017-04-12 13:41:54 +00:00
if attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
selector [ 'value' ] = selector [ 'value' ] . split ( / , / ) . collect ( & :strip )
end
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
2017-07-06 13:58:10 +00:00
query += " #{ attribute } IS NULL "
2015-10-14 14:33:26 +00:00
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?
2017-07-06 13:58:10 +00:00
query += " #{ attribute } IS 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
2017-07-06 13:58:10 +00:00
query += " #{ attribute } IS NOT NULL "
2015-10-14 14:33:26 +00:00
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
2017-04-12 13:41:54 +00:00
elsif selector [ 'operator' ] == 'contains all' && attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
2017-04-12 14:27:00 +00:00
query += " ? = (
2017-04-12 13:41:54 +00:00
SELECT
COUNT ( * )
FROM
tag_objects ,
tag_items ,
tags
WHERE
tickets . id = tags . o_id AND
tag_objects . id = tags . tag_object_id AND
tag_objects . name = 'Ticket' AND
tag_items . id = tags . tag_item_id AND
tag_items . name IN ( ?)
) "
2017-04-12 14:27:00 +00:00
bind_params . push selector [ 'value' ] . count
2017-04-12 13:41:54 +00:00
bind_params . push selector [ 'value' ]
elsif selector [ 'operator' ] == 'contains one' && attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
query += " 1 <= (
SELECT
COUNT ( * )
FROM
tag_objects ,
tag_items ,
tags
WHERE
tickets . id = tags . o_id AND
tag_objects . id = tags . tag_object_id AND
tag_objects . name = 'Ticket' AND
tag_items . id = tags . tag_item_id AND
tag_items . name IN ( ?)
) "
bind_params . push selector [ 'value' ]
elsif selector [ 'operator' ] == 'contains all not' && attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
query += " 0 = (
SELECT
COUNT ( * )
FROM
tag_objects ,
tag_items ,
tags
WHERE
tickets . id = tags . o_id AND
tag_objects . id = tags . tag_object_id AND
tag_objects . name = 'Ticket' AND
tag_items . id = tags . tag_item_id AND
tag_items . name IN ( ?)
) "
bind_params . push selector [ 'value' ]
elsif selector [ 'operator' ] == 'contains one not' && attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
query += " (
SELECT
COUNT ( * )
FROM
tag_objects ,
tag_items ,
tags
WHERE
tickets . id = tags . o_id AND
tag_objects . id = tags . tag_object_id AND
tag_objects . name = 'Ticket' AND
tag_items . id = tags . tag_item_id AND
tag_items . name IN ( ?)
2017-04-12 14:27:00 +00:00
) BETWEEN ? AND ?"
2017-04-12 14:28:12 +00:00
bind_params . push selector [ 'value' ]
2017-04-12 14:27:00 +00:00
bind_params . push selector [ 'value' ] . count - 1
bind_params . push selector [ 'value' ] . count
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
}
2017-04-12 13:41:54 +00:00
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
2016-05-03 00:36:44 +00:00
perform changes on ticket
2017-03-03 08:28:45 +00:00
ticket . perform_changes ( { } , 'trigger' , item , current_user_id )
2016-05-03 00:36:44 +00:00
= end
2017-03-03 08:28:45 +00:00
def perform_changes ( perform , perform_origin , item = nil , current_user_id = nil )
2016-06-27 11:24:48 +00:00
logger . debug " Perform #{ perform_origin } #{ perform . inspect } on Ticket.find( #{ id } ) "
2017-05-05 09:16:47 +00:00
# if the configuration contains the deletion of the ticket then
# we skip all other ticket changes because they does not matter
if perform [ 'ticket.action' ] . present? && perform [ 'ticket.action' ] [ 'value' ] == 'delete'
perform . each do | key , _value |
( object_name , attribute ) = key . split ( '.' , 2 )
next if object_name != 'ticket'
next if attribute == 'action'
perform . delete ( key )
end
end
2016-05-03 00:36:44 +00:00
changed = false
perform . each do | key , value |
( object_name , attribute ) = key . split ( '.' , 2 )
raise " Unable to update object #{ object_name } . #{ attribute } , only can update tickets and send notifications! " if object_name != 'ticket' && object_name != 'notification'
# send notification
if object_name == 'notification'
2017-05-02 11:34:18 +00:00
# value['recipient'] was a string in the past (single-select) so we convert it to array if needed
value_recipient = value [ 'recipient' ]
if ! value_recipient . is_a? ( Array )
value_recipient = [ value_recipient ]
2016-05-03 00:36:44 +00:00
end
2017-05-02 11:34:18 +00:00
recipients_raw = [ ]
value_recipient . each { | recipient |
if recipient == 'article_last_sender'
if item && item [ :article_id ]
article = Ticket :: Article . lookup ( id : item [ :article_id ] )
if article . reply_to . present?
recipients_raw . push ( article . reply_to )
elsif article . from . present?
recipients_raw . push ( article . from )
elsif article . origin_by_id
email = User . lookup ( id : article . origin_by_id ) . email
recipients_raw . push ( email )
elsif article . created_by_id
email = User . lookup ( id : article . created_by_id ) . email
recipients_raw . push ( email )
end
end
elsif recipient == 'ticket_customer'
email = User . lookup ( id : customer_id ) . email
recipients_raw . push ( email )
elsif recipient == 'ticket_owner'
email = User . lookup ( id : owner_id ) . email
recipients_raw . push ( email )
elsif recipient == 'ticket_agents'
2017-06-20 15:13:42 +00:00
User . group_access ( group_id , 'full' ) . sort_by ( & :login ) . each do | user |
2017-05-02 11:34:18 +00:00
recipients_raw . push ( user . email )
2017-06-16 20:43:09 +00:00
end
2017-05-02 11:34:18 +00:00
else
logger . error " Unknown email notification recipient ' #{ recipient } ' "
next
end
}
recipients_checked = [ ]
recipients_raw . each { | recipient_email |
2016-05-03 11:03:10 +00:00
2017-05-30 06:55:51 +00:00
skip_user = false
users = User . where ( email : recipient_email )
users . each { | user |
next if user . preferences [ :mail_delivery_failed ] != true
next if ! user . preferences [ :mail_delivery_failed_data ]
till_blocked = ( ( user . preferences [ :mail_delivery_failed_data ] - Time . zone . now - 60 . days ) / 60 / 60 / 24 ) . round
next if till_blocked . positive?
logger . info " Send no trigger based notification to #{ recipient_email } because email is marked as mail_delivery_failed for #{ till_blocked } days "
skip_user = true
break
}
next if skip_user
2016-05-03 11:03:10 +00:00
# send notifications only to email adresses
2017-05-02 11:34:18 +00:00
next if ! recipient_email
next if recipient_email !~ / @ /
# check if address is valid
begin
recipient_email = Mail :: Address . new ( recipient_email ) . address
rescue
next # because unable to parse
end
2016-05-03 11:03:10 +00:00
# do not sent notifications to this recipients
2016-05-06 07:57:04 +00:00
send_no_auto_response_reg_exp = Setting . get ( 'send_no_auto_response_reg_exp' )
begin
2017-05-02 11:34:18 +00:00
next if recipient_email =~ / #{ send_no_auto_response_reg_exp } /i
2016-05-06 07:57:04 +00:00
rescue = > e
logger . error " ERROR: Invalid regex ' #{ send_no_auto_response_reg_exp } ' in setting send_no_auto_response_reg_exp "
logger . error 'ERROR: ' + e . inspect
2017-05-02 11:34:18 +00:00
next if recipient_email =~ / (mailer-daemon|postmaster|abuse|root)@.+? \ ..+? /i
2016-05-06 07:57:04 +00:00
end
2016-05-03 11:03:10 +00:00
2016-08-23 06:34:15 +00:00
# check if notification should be send because of customer emails
if item && item [ :article_id ]
article = Ticket :: Article . lookup ( id : item [ :article_id ] )
2017-05-02 11:34:18 +00:00
if article && article . preferences [ 'is-auto-response' ] == true && article . from && article . from =~ / #{ Regexp . quote ( recipient_email ) } /i
2017-05-24 10:57:00 +00:00
logger . info " Send no trigger based notification to #{ recipient_email } because of auto response tagged incoming email "
2016-08-23 06:34:15 +00:00
next
end
end
2017-05-24 10:57:00 +00:00
# loop protection / check if maximal count of trigger mail has reached
map = {
2017-06-23 22:09:51 +00:00
10 = > 10 ,
2017-05-24 10:57:00 +00:00
30 = > 15 ,
60 = > 25 ,
180 = > 50 ,
2017-06-23 22:09:51 +00:00
600 = > 100 ,
2017-05-24 10:57:00 +00:00
}
skip = false
map . each { | minutes , count |
already_sent = Ticket :: Article . where (
ticket_id : id ,
sender : Ticket :: Article :: Sender . find_by ( name : 'System' ) ,
type : Ticket :: Article :: Type . find_by ( name : 'email' ) ,
) . where ( " ticket_articles.created_at > ? AND ticket_articles.to LIKE '% #{ recipient_email . strip } %' " , Time . zone . now - minutes . minutes ) . count
next if already_sent < count
logger . info " Send no trigger based notification to #{ recipient_email } because already sent #{ count } for this ticket within last #{ minutes } minutes (loop protection) "
skip = true
break
}
next if skip
map = {
2017-06-23 22:09:51 +00:00
10 = > 30 ,
30 = > 60 ,
60 = > 120 ,
180 = > 240 ,
600 = > 360 ,
2017-05-24 10:57:00 +00:00
}
skip = false
2017-06-23 22:09:51 +00:00
map . each { | minutes , count |
2017-05-24 10:57:00 +00:00
already_sent = Ticket :: Article . where (
sender : Ticket :: Article :: Sender . find_by ( name : 'System' ) ,
type : Ticket :: Article :: Type . find_by ( name : 'email' ) ,
2017-06-23 22:09:51 +00:00
) . where ( " ticket_articles.created_at > ? AND ticket_articles.to LIKE '% #{ recipient_email . strip } %' " , Time . zone . now - minutes . minutes ) . count
2017-05-24 10:57:00 +00:00
next if already_sent < count
2017-06-23 22:09:51 +00:00
logger . info " Send no trigger based notification to #{ recipient_email } because already sent #{ count } in total within last #{ minutes } minutes (loop protection) "
2017-05-24 10:57:00 +00:00
skip = true
break
}
next if skip
2017-05-02 11:34:18 +00:00
email = recipient_email . downcase . strip
next if recipients_checked . include? ( email )
recipients_checked . push ( email )
2016-05-03 00:36:44 +00:00
}
2017-05-02 11:34:18 +00:00
next if recipients_checked . blank?
recipient_string = recipients_checked . join ( ', ' )
2016-05-03 00:36:44 +00:00
group = self . group
next if ! group
email_address = group . email_address
2017-03-24 23:00:41 +00:00
if ! email_address
logger . info " Unable to send trigger based notification to #{ recipient_string } because no email address is set for group ' #{ group . name } ' "
next
end
if ! email_address . channel_id
logger . info " Unable to send trigger based notification to #{ recipient_string } because no channel is set for email address ' #{ email_address . email } ' (id: #{ email_address . id } ) "
next
end
2016-05-03 00:36:44 +00:00
objects = {
ticket : self ,
article : articles . last ,
}
# get subject
subject = NotificationFactory :: Mailer . template (
templateInline : value [ 'subject' ] ,
locale : 'en-en' ,
objects : objects ,
2016-11-13 18:33:12 +00:00
quote : false ,
2016-05-03 00:36:44 +00:00
)
subject = subject_build ( subject )
body = NotificationFactory :: Mailer . template (
templateInline : value [ 'body' ] ,
locale : 'en-en' ,
objects : objects ,
2016-11-13 18:33:12 +00:00
quote : true ,
2016-05-03 00:36:44 +00:00
)
2016-11-13 18:33:12 +00:00
2016-05-03 00:36:44 +00:00
Ticket :: Article . create (
ticket_id : id ,
to : recipient_string ,
subject : subject ,
content_type : 'text/html' ,
body : body ,
internal : false ,
sender : Ticket :: Article :: Sender . find_by ( name : 'System' ) ,
type : Ticket :: Article :: Type . find_by ( name : 'email' ) ,
2016-06-27 11:24:48 +00:00
preferences : {
perform_origin : perform_origin ,
} ,
2016-05-03 00:36:44 +00:00
updated_by_id : 1 ,
created_by_id : 1 ,
)
next
end
# update tags
if key == 'ticket.tags'
2017-04-12 07:34:49 +00:00
next if value [ 'value' ] . blank?
2016-05-03 00:36:44 +00:00
tags = value [ 'value' ] . split ( / , / )
if value [ 'operator' ] == 'add'
2016-06-30 20:04:48 +00:00
tags . each { | tag |
2017-04-12 07:34:49 +00:00
tag_add ( tag )
2016-05-03 00:36:44 +00:00
}
elsif value [ 'operator' ] == 'remove'
2016-06-30 20:04:48 +00:00
tags . each { | tag |
2017-04-12 07:34:49 +00:00
tag_remove ( tag )
2016-05-03 00:36:44 +00:00
}
else
logger . error " Unknown #{ attribute } operator #{ value [ 'operator' ] } "
end
next
end
2017-05-05 09:16:47 +00:00
# delete ticket
if key == 'ticket.action'
next if value [ 'value' ] . blank?
next if value [ 'value' ] != 'delete'
destroy
next
end
2017-03-03 08:28:45 +00:00
# lookup pre_condition
if value [ 'pre_condition' ]
if value [ 'pre_condition' ] =~ / ^not_set /
value [ 'value' ] = 1
elsif value [ 'pre_condition' ] =~ / ^current_user \ . /
raise 'Unable to use current_user, got no current_user_id for ticket.perform_changes' if ! current_user_id
value [ 'value' ] = current_user_id
end
end
2016-05-03 00:36:44 +00:00
# update ticket
next if self [ attribute ] . to_s == value [ 'value' ] . to_s
changed = true
self [ attribute ] = value [ 'value' ]
logger . debug " set #{ object_name } . #{ attribute } = #{ value [ 'value' ] . inspect } "
end
return if ! changed
save
end
= begin
2016-02-20 07:14:51 +00:00
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 = [ ]
2016-06-30 20:04:48 +00:00
Ticket :: Article . select ( 'in_reply_to, message_id' ) . where ( ticket_id : id ) . each { | article |
2016-02-20 07:14:51 +00:00
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
}
2016-06-30 20:04:48 +00:00
ignore . each { | item |
2016-02-20 07:14:51 +00:00
references . delete ( item )
}
references
end
2016-11-18 07:25:07 +00:00
= begin
get all articles of a ticket in correct order ( overwrite active record default method )
artilces = ticket . articles
result
[ article1 , articl2 ]
= end
def articles
Ticket :: Article . where ( ticket_id : id ) . order ( :created_at , :id )
end
2017-01-31 17:13:45 +00:00
def history_get ( fulldata = false )
list = History . list ( self . class . name , self [ 'id' ] , 'Ticket::Article' )
return list if ! fulldata
# get related objects
assets = { }
list . each { | item |
record = Kernel . const_get ( item [ 'object' ] ) . find ( item [ 'o_id' ] )
assets = record . assets ( assets )
if item [ 'related_object' ]
record = Kernel . const_get ( item [ 'related_object' ] ) . find ( item [ 'related_o_id' ] )
assets = record . assets ( assets )
end
}
{
history : list ,
assets : assets ,
}
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
2017-06-16 22:53:20 +00:00
return true if number
2013-08-15 22:16:38 +00:00
self . number = Ticket :: Number . generate
2017-06-16 22:53:20 +00:00
true
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
2017-06-16 22:53:20 +00:00
return true if ! title
2015-05-07 12:10:38 +00:00
title . gsub! ( / \ s| \ t| \ r / , ' ' )
2017-06-16 22:53:20 +00:00
true
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
2017-06-16 22:53:20 +00:00
return true if ! customer_id
2016-12-20 23:07:47 +00:00
customer = User . find_by ( id : customer_id )
2017-06-16 22:53:20 +00:00
return true if ! customer
return true if organization_id == customer . organization_id
2015-04-30 15:25:04 +00:00
self . organization_id = customer . organization_id
2017-06-16 22:53:20 +00:00
true
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
2017-06-16 22:53:20 +00:00
return true if ! changes [ 'state_id' ]
2015-02-11 22:05:31 +00:00
2017-06-28 08:25:25 +00:00
# ignore if new state is blank and
# let handle ActiveRecord the error
return if state_id . blank?
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
2017-06-16 22:53:20 +00:00
return true if current_state_type . name =~ / ^pending /i
2015-04-30 15:25:04 +00:00
self . pending_time = nil
2017-06-16 22:53:20 +00:00
true
2015-02-11 22:05:31 +00:00
end
2016-09-02 10:54:13 +00:00
def check_escalation_update
2017-04-11 11:11:44 +00:00
escalation_calculation
2016-09-02 10:54:13 +00:00
true
end
2017-02-12 17:21:03 +00:00
def set_default_state
2017-06-16 22:53:20 +00:00
return true if state_id
2017-02-12 17:21:03 +00:00
default_ticket_state = Ticket :: State . find_by ( default_create : true )
2017-06-16 22:53:20 +00:00
return true if ! default_ticket_state
2017-02-12 17:21:03 +00:00
self . state_id = default_ticket_state . id
2017-06-16 22:53:20 +00:00
true
2017-02-12 17:21:03 +00:00
end
def set_default_priority
2017-06-16 22:53:20 +00:00
return true if priority_id
2017-02-12 17:21:03 +00:00
default_ticket_priority = Ticket :: Priority . find_by ( default_create : true )
2017-06-16 22:53:20 +00:00
return true if ! default_ticket_priority
2017-02-12 17:21:03 +00:00
self . priority_id = default_ticket_priority . id
2017-06-16 22:53:20 +00:00
true
2017-02-12 17:21:03 +00:00
end
2015-04-27 14:15:29 +00:00
end