trabajo-afectivo/app/models/ticket.rb

1109 lines
36 KiB
Ruby
Raw Normal View History

2016-10-19 03:11:36 +00:00
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
2012-07-29 20:25:31 +00:00
class Ticket < ApplicationModel
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
include Ticket::ChecksAccess
2013-09-29 21:37:49 +00:00
include Ticket::Escalation
include Ticket::Subject
load 'ticket/assets.rb'
2013-09-29 21:37:49 +00:00
include Ticket::Assets
load 'ticket/search_index.rb'
include Ticket::SearchIndex
2013-09-29 21:37:49 +00:00
extend Ticket::Search
store :preferences
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
validates :group_id, presence: true
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
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
attr_accessor :callback_loop
=begin
2014-11-10 07:34:20 +00:00
get user access conditions
conditions = Ticket.access_condition( User.find(1) , 'full')
2014-11-10 07:34:20 +00:00
returns
result = [user1, user2, ...]
=end
def self.access_condition(user, access)
if user.permissions?('ticket.agent')
['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
['(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
result = []
2015-05-21 14:40:04 +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|
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
result.push ticket
}
end
2015-05-21 14:40:04 +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
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
tickets = where(state_id: reminder_state_map.keys)
.where('pending_time <= ?', Time.zone.now)
2015-05-21 14:40:04 +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
# 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,
user_id: 1,
)
result.push ticket
}
end
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_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
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,
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,
user_id: 1,
2016-02-22 23:28:13 +00:00
)
result.push ticket
}
result
end
=begin
merge tickets
ticket = Ticket.find(123)
result = ticket.merge_to(
ticket_id: 123,
user_id: 123,
)
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
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
# 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])
# 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
self.owner_id = 1
# save ticket
save!
# touch new ticket (to broadcast change)
Ticket.find(data[:ticket_id]).touch
end
true
end
=begin
check if online notifcation should be shown in general as already seen with current state
ticket = Ticket.find(1)
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)
returns
result = true # or false
=end
def online_notification_seen_state(user_id_check = nil)
state = Ticket::State.lookup(id: state_id)
state_type = Ticket::StateType.lookup(id: state.state_type_id)
# 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
# set all to seen if pending action state is a closed or merged state
if state_type.name == 'pending action' && state.next_state_id
state = Ticket::State.lookup(id: state.next_state_id)
state_type = Ticket::StateType.lookup(id: state.state_type_id)
end
# set all to seen if new state is pending reminder state
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
# set all to seen if new state is a closed or merged state
return true if state_type.name == 'closed'
return true if state_type.name == 'merged'
false
2012-07-03 13:24:31 +00:00
end
=begin
get count of tickets and tickets which match on selector
ticket_count, tickets = Ticket.selectors(params[:condition], limit, current_user, 'full')
=end
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
query, bind_params, tables = selector2sql(selectors, current_user)
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, access)
2015-10-12 13:44:34 +00:00
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
=begin
generate condition query to search for tickets based on condition
query_condition, bind_condition, tables = selector2sql(params[:condition], current_user)
condition example
{
2016-09-11 08:46:01 +00:00
'ticket.title' => {
operator: 'contains', # contains not
value: 'some value',
},
'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',
},
'ticket.owner_id' => {
operator: 'is', # is not
pre_condition: 'current_user.id',
},
'ticket.owner_id' => {
operator: 'is', # is not
pre_condition: 'specific',
value: 4711,
},
'ticket.escalation_at' => {
2016-02-26 12:19:57 +00:00
operator: 'is not', # not
value: nil,
},
'ticket.tags' => {
operator: 'contains all', # contains all|contains one|contains all not|contains one not
value: 'tag1, tag2',
},
}
=end
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
# 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
# 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|
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
}
# add conditions
2016-06-30 20:04:48 +00:00
selectors.each { |attribute, selector_raw|
# 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?)
selector = selector_raw.stringify_keys
2016-03-01 14:26:46 +00:00
raise "Invalid selector, operator missing #{selector.inspect}" if !selector['operator']
# 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?))
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'
end
# validate pre_condition values
return nil if selector['pre_condition'] && selector['pre_condition'] !~ /^(not_set|current_user\.|specific)/
# get attributes
attributes = attribute.split(/\./)
attribute = "#{attributes[0]}s.#{attributes[1]}"
if attributes[0] == 'ticket' && attributes[1] == 'tags'
selector['value'] = selector['value'].split(/,/).collect(&:strip)
end
if query != ''
query += ' AND '
end
2015-09-17 01:04:16 +00:00
if selector['operator'] == 'is'
if selector['pre_condition'] == 'not_set'
if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
query += "#{attribute} IN (?)"
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
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
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
end
2015-09-17 01:04:16 +00:00
elsif selector['operator'] == 'is not'
if selector['pre_condition'] == 'not_set'
if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
query += "#{attribute} NOT IN (?)"
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
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
elsif selector['operator'] == 'contains all' && 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 (?)
)"
bind_params.push selector['value'].count
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 (?)
) BETWEEN ? AND ?"
bind_params.push selector['value']
bind_params.push selector['value'].count - 1
bind_params.push selector['value'].count
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
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
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
}
[query, bind_params, tables]
2015-09-17 01:04:16 +00:00
end
=begin
2016-05-03 00:36:44 +00:00
perform changes on ticket
ticket.perform_changes({}, 'trigger', item, current_user_id)
2016-05-03 00:36:44 +00:00
=end
def perform_changes(perform, perform_origin, item = nil, current_user_id = nil)
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'
# 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
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'
User.group_access(group_id, 'full').order(:login).each do |user|
recipients_raw.push(user.email)
end
else
logger.error "Unknown email notification recipient '#{recipient}'"
next
end
}
recipients_checked = []
recipients_raw.each { |recipient_email|
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
# send notifications only to email adresses
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
# do not sent notifications to this recipients
send_no_auto_response_reg_exp = Setting.get('send_no_auto_response_reg_exp')
begin
next if recipient_email =~ /#{send_no_auto_response_reg_exp}/i
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
next if recipient_email =~ /(mailer-daemon|postmaster|abuse|root)@.+?\..+?/i
end
# check if notification should be send because of customer emails
if item && item[:article_id]
article = Ticket::Article.lookup(id: item[:article_id])
if article && article.preferences['is-auto-response'] == true && article.from && article.from =~ /#{Regexp.quote(recipient_email)}/i
logger.info "Send no trigger based notification to #{recipient_email} because of auto response tagged incoming email"
next
end
end
# loop protection / check if maximal count of trigger mail has reached
map = {
30 => 15,
60 => 25,
180 => 50,
}
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 = {
1 => 150,
3 => 250,
6 => 450,
}
skip = false
map.each { |hours, count|
already_sent = Ticket::Article.where(
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 - hours.hours).count
next if already_sent < count
logger.info "Send no trigger based notification to #{recipient_email} because already sent #{count} in total within last #{hours} hour(s) (loop protection)"
skip = true
break
}
next if skip
email = recipient_email.downcase.strip
next if recipients_checked.include?(email)
recipients_checked.push(email)
2016-05-03 00:36:44 +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
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,
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,
quote: true,
2016-05-03 00:36:44 +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'),
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
# 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
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|
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|
references.delete(item)
}
references
end
=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
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
def check_generate
return if number
self.number = Ticket::Number.generate
end
def check_title
return if !title
title.gsub!(/\s|\t|\r/, ' ')
end
def check_defaults
if !owner_id
self.owner_id = 1
end
return if !customer_id
customer = User.find_by(id: customer_id)
return if !customer
return if organization_id == customer.organization_id
self.organization_id = customer.organization_id
end
2013-06-12 14:57:29 +00:00
def reset_pending_time
# ignore if no state has changed
return if !changes['state_id']
# 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)
# in case, set pending_time to nil
return if current_state_type.name =~ /^pending/i
self.pending_time = nil
end
def check_escalation_update
escalation_calculation
true
end
def set_default_state
return if state_id
default_ticket_state = Ticket::State.find_by(default_create: true)
return if !default_ticket_state
self.state_id = default_ticket_state.id
end
def set_default_priority
return if priority_id
default_ticket_priority = Ticket::Priority.find_by(default_create: true)
return if !default_ticket_priority
self.priority_id = default_ticket_priority.id
end
end