Merge branch 'develop' into feature/ui2
This commit is contained in:
commit
88cc1cc401
43 changed files with 1789 additions and 945 deletions
|
@ -99,6 +99,9 @@ class _i18nSingleton extends Spine.Module
|
|||
locale = 'en'
|
||||
@locale = locale
|
||||
|
||||
# set lang attribute of html tag
|
||||
$('html').prop( 'lang', locale.substr(0, 2) )
|
||||
|
||||
@map = {}
|
||||
App.Ajax.request(
|
||||
id: 'i18n-set-' + locale,
|
||||
|
|
|
@ -71,3 +71,8 @@ jQuery.event.special.remove = {
|
|||
if (e.handler) e.handler();
|
||||
}
|
||||
};
|
||||
|
||||
// start application
|
||||
jQuery(function(){
|
||||
new App.Run();
|
||||
});
|
|
@ -1,7 +1,5 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'geoip'
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
# http_basic_authenticate_with :name => "test", :password => "ttt"
|
||||
|
||||
|
@ -82,7 +80,7 @@ class ApplicationController < ActionController::Base
|
|||
# check if remote ip need to be updated
|
||||
if !session[:remote_id] || session[:remote_id] != request.remote_ip
|
||||
session[:remote_id] = request.remote_ip
|
||||
session[:geo] = Geoip.location( request.remote_ip )
|
||||
session[:geo] = GeoIp.location( request.remote_ip )
|
||||
end
|
||||
|
||||
# fill user agent
|
||||
|
|
|
@ -8,7 +8,7 @@ class TicketOverviewsController < ApplicationController
|
|||
|
||||
# get navbar overview data
|
||||
if !params[:view]
|
||||
result = Ticket.overview(
|
||||
result = Ticket::Overview.list(
|
||||
:current_user => current_user,
|
||||
)
|
||||
render :json => result
|
||||
|
@ -17,7 +17,7 @@ class TicketOverviewsController < ApplicationController
|
|||
|
||||
# get real overview data
|
||||
if params[:array]
|
||||
overview = Ticket.overview(
|
||||
overview = Ticket::Overview.list(
|
||||
:view => params[:view],
|
||||
:current_user => current_user,
|
||||
:array => true,
|
||||
|
@ -36,7 +36,7 @@ class TicketOverviewsController < ApplicationController
|
|||
}
|
||||
return
|
||||
end
|
||||
overview = Ticket.overview(
|
||||
overview = Ticket::Overview.list(
|
||||
:view => params[:view],
|
||||
# :view_mode => params[:view_mode],
|
||||
:current_user => User.find( current_user.id ),
|
||||
|
@ -70,7 +70,7 @@ class TicketOverviewsController < ApplicationController
|
|||
group_ids.push group.id
|
||||
}
|
||||
agents = {}
|
||||
Ticket.agents.each { |user|
|
||||
Ticket::ScreenOptions.agents.each { |user|
|
||||
agents[ user.id ] = 1
|
||||
}
|
||||
groups_users = {}
|
||||
|
|
|
@ -111,7 +111,7 @@ class TicketsController < ApplicationController
|
|||
def ticket_customer
|
||||
|
||||
# return result
|
||||
result = Ticket.list_by_customer(
|
||||
result = Ticket::ScreenOptions.list_by_customer(
|
||||
:customer_id => params[:customer_id],
|
||||
:limit => 15,
|
||||
)
|
||||
|
@ -217,6 +217,9 @@ class TicketsController < ApplicationController
|
|||
if !users[ data['created_by_id'] ]
|
||||
users[ data['created_by_id'] ] = User.user_data_full( data['created_by_id'] )
|
||||
end
|
||||
if !users[ data['updated_by_id'] ]
|
||||
users[ data['updated_by_id'] ] = User.user_data_full( data['updated_by_id'] )
|
||||
end
|
||||
}
|
||||
|
||||
recent_viewed = RecentView.list_fulldata( current_user, 8 )
|
||||
|
@ -326,7 +329,7 @@ class TicketsController < ApplicationController
|
|||
end
|
||||
|
||||
# get attributes to update
|
||||
attributes_to_change = Ticket.attributes_to_change( :user => current_user, :ticket => ticket )
|
||||
attributes_to_change = Ticket::ScreenOptions.attributes_to_change( :user => current_user, :ticket => ticket )
|
||||
|
||||
attributes_to_change[:owner_id].each { |user_id|
|
||||
if !users[user_id]
|
||||
|
@ -384,7 +387,7 @@ class TicketsController < ApplicationController
|
|||
def ticket_create
|
||||
|
||||
# get attributes to update
|
||||
attributes_to_change = Ticket.attributes_to_change(
|
||||
attributes_to_change = Ticket::ScreenOptions.attributes_to_change(
|
||||
:user => current_user,
|
||||
# :ticket_id => params[:ticket_id],
|
||||
# :article_id => params[:article_id]
|
||||
|
|
|
@ -25,6 +25,18 @@ class ApplicationModel < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
remove all not used model attributes of params
|
||||
|
||||
result = Model.param_cleanup(params)
|
||||
|
||||
returns
|
||||
|
||||
result = params # params with valid attributes of model
|
||||
|
||||
=end
|
||||
|
||||
def self.param_cleanup(params)
|
||||
|
||||
# only use object attributes
|
||||
|
@ -40,6 +52,18 @@ class ApplicationModel < ActiveRecord::Base
|
|||
self.param_validation(data)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
remove all not used params of object (per default :updated_at, :created_at, :updated_by_id and :created_by_id)
|
||||
|
||||
result = Model.param_validation(params)
|
||||
|
||||
returns
|
||||
|
||||
result = params # params without listed attributes
|
||||
|
||||
=end
|
||||
|
||||
def self.param_validation(data)
|
||||
|
||||
# we do want to set this via database
|
||||
|
@ -51,7 +75,20 @@ class ApplicationModel < ActiveRecord::Base
|
|||
data
|
||||
end
|
||||
|
||||
# set created_by_id & updated_by_id if not given based on UserInfo
|
||||
=begin
|
||||
|
||||
set created_by_id & updated_by_id if not given based on UserInfo (current session)
|
||||
|
||||
Used as before_create callback, no own use needed
|
||||
|
||||
result = Model.fill_up_user_create(params)
|
||||
|
||||
returns
|
||||
|
||||
result = params # params with updated_by_id & created_by_id if not given based on UserInfo (current session)
|
||||
|
||||
=end
|
||||
|
||||
def fill_up_user_create
|
||||
if self.class.column_names.include? 'updated_by_id'
|
||||
if UserInfo.current_user_id
|
||||
|
@ -71,7 +108,20 @@ class ApplicationModel < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# set updated_by_id if not given based on UserInfo
|
||||
=begin
|
||||
|
||||
set updated_by_id if not given based on UserInfo (current session)
|
||||
|
||||
Used as before_update callback, no own use needed
|
||||
|
||||
result = Model.fill_up_user_update(params)
|
||||
|
||||
returns
|
||||
|
||||
result = params # params with updated_by_id & created_by_id if not given based on UserInfo (current session)
|
||||
|
||||
=end
|
||||
|
||||
def fill_up_user_update
|
||||
return if !self.class.column_names.include? 'updated_by_id'
|
||||
if UserInfo.current_user_id
|
||||
|
@ -129,6 +179,20 @@ class ApplicationModel < ActiveRecord::Base
|
|||
Cache.get( key.to_s )
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
lookup model from cache (if exists) or retrieve it from db, id, name or login possible
|
||||
|
||||
result = Model.lookup( :id => 123 )
|
||||
result = Model.lookup( :name => 'some name' )
|
||||
result = Model.lookup( :login => 'some login' )
|
||||
|
||||
returns
|
||||
|
||||
result = model # with all attributes
|
||||
|
||||
=end
|
||||
|
||||
def self.lookup(data)
|
||||
if data[:id]
|
||||
# puts "GET- + #{self.to_s}.#{data[:id].to_s}"
|
||||
|
@ -168,6 +232,18 @@ class ApplicationModel < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
create model if not exists (check exists based on id, name, login or locale)
|
||||
|
||||
result = Model.create_if_not_exists( attributes )
|
||||
|
||||
returns
|
||||
|
||||
result = model # with all attributes
|
||||
|
||||
=end
|
||||
|
||||
def self.create_if_not_exists(data)
|
||||
if data[:id]
|
||||
record = self.where( :id => data[:id] ).first
|
||||
|
@ -191,6 +267,18 @@ class ApplicationModel < ActiveRecord::Base
|
|||
self.create(data)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
create or update model (check exists based on name, login or locale)
|
||||
|
||||
result = Model.create_or_update( attributes )
|
||||
|
||||
returns
|
||||
|
||||
result = model # with all attributes
|
||||
|
||||
=end
|
||||
|
||||
def self.create_or_update(data)
|
||||
if data[:name]
|
||||
records = self.where( :name => data[:name] )
|
||||
|
@ -214,11 +302,37 @@ class ApplicationModel < ActiveRecord::Base
|
|||
record = self.new( data )
|
||||
record.save
|
||||
return record
|
||||
elsif data[:locale]
|
||||
records = self.where( :locale => data[:locale] )
|
||||
records.each {|record|
|
||||
if record.locale.downcase == data[:locale].downcase
|
||||
record.update_attributes( data )
|
||||
return record
|
||||
end
|
||||
}
|
||||
record = self.new( data )
|
||||
record.save
|
||||
return record
|
||||
else
|
||||
raise "Need name or login for create_or_update()"
|
||||
raise "Need name, login or locale for create_or_update()"
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
notify_clients_after_create after model got created
|
||||
|
||||
used as callback in model file
|
||||
|
||||
class OwnModel < ApplicationModel
|
||||
after_create :notify_clients_after_create
|
||||
after_update :notify_clients_after_update
|
||||
after_destroy :notify_clients_after_destroy
|
||||
|
||||
[...]
|
||||
|
||||
=end
|
||||
|
||||
def notify_clients_after_create
|
||||
|
||||
# return if we run import mode
|
||||
|
@ -232,6 +346,21 @@ class ApplicationModel < ActiveRecord::Base
|
|||
)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
notify_clients_after_update after model got updated
|
||||
|
||||
used as callback in model file
|
||||
|
||||
class OwnModel < ApplicationModel
|
||||
after_create :notify_clients_after_create
|
||||
after_update :notify_clients_after_update
|
||||
after_destroy :notify_clients_after_destroy
|
||||
|
||||
[...]
|
||||
|
||||
=end
|
||||
|
||||
def notify_clients_after_update
|
||||
|
||||
# return if we run import mode
|
||||
|
@ -245,6 +374,20 @@ class ApplicationModel < ActiveRecord::Base
|
|||
)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
notify_clients_after_destroy after model got destroyed
|
||||
|
||||
used as callback in model file
|
||||
|
||||
class OwnModel < ApplicationModel
|
||||
after_create :notify_clients_after_create
|
||||
after_update :notify_clients_after_update
|
||||
after_destroy :notify_clients_after_destroy
|
||||
|
||||
[...]
|
||||
|
||||
=end
|
||||
def notify_clients_after_destroy
|
||||
|
||||
# return if we run import mode
|
||||
|
|
|
@ -333,7 +333,7 @@ class Channel::EmailParser
|
|||
UserInfo.current_user_id = user.id
|
||||
|
||||
# get ticket# from subject
|
||||
ticket = Ticket.number_check( mail[:subject] )
|
||||
ticket = Ticket::Number.check( mail[:subject] )
|
||||
|
||||
# set ticket state to open if not new
|
||||
if ticket
|
||||
|
|
|
@ -53,13 +53,8 @@ class Observer::User::Geo < ActiveRecord::Observer
|
|||
# return if no address is given
|
||||
return if address == ''
|
||||
|
||||
# load adapter
|
||||
adapter = Setting.get('geo_backend')
|
||||
return if !adapter
|
||||
adapter_module = Object.const_get(adapter)
|
||||
|
||||
# db lookup
|
||||
latlng = adapter_module.geocode(address)
|
||||
# lookup
|
||||
latlng = GeoLocation.geocode( address )
|
||||
return if !latlng
|
||||
|
||||
# store data
|
||||
|
|
|
@ -14,6 +14,6 @@ class Sla < ApplicationModel
|
|||
private
|
||||
def escalation_calculation_rebuild
|
||||
Cache.delete( 'SLA::List::Active' )
|
||||
Ticket.escalation_calculation_rebuild
|
||||
Ticket::Escalation.rebuild_all
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ require 'time_calculation'
|
|||
require 'sla'
|
||||
|
||||
class Ticket < ApplicationModel
|
||||
before_create :number_generate, :check_defaults
|
||||
before_create :check_generate, :check_defaults
|
||||
before_update :check_defaults
|
||||
before_destroy :destroy_dependencies
|
||||
after_create :notify_clients_after_create
|
||||
|
@ -22,103 +22,44 @@ class Ticket < ApplicationModel
|
|||
belongs_to :create_article_type, :class_name => 'Ticket::Article::Type'
|
||||
belongs_to :create_article_sender, :class_name => 'Ticket::Article::Sender'
|
||||
|
||||
include Ticket::Escalation
|
||||
include Ticket::Subject
|
||||
include Ticket::Permission
|
||||
extend Ticket::Search
|
||||
|
||||
attr_accessor :callback_loop
|
||||
|
||||
def self.number_check (string)
|
||||
self.number_adapter.number_check_item(string)
|
||||
end
|
||||
=begin
|
||||
|
||||
list of agents in group of ticket
|
||||
|
||||
ticket = Ticket.find(123)
|
||||
result = ticket.agent_of_group
|
||||
|
||||
returns
|
||||
|
||||
result = [user1, user2, ...]
|
||||
|
||||
=end
|
||||
|
||||
def agent_of_group
|
||||
Group.find( self.group_id ).users.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
|
||||
end
|
||||
|
||||
def self.agents
|
||||
User.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
|
||||
end
|
||||
=begin
|
||||
|
||||
def self.attributes_to_change(params)
|
||||
if params[:ticket_id]
|
||||
params[:ticket] = self.find( params[:ticket_id] )
|
||||
end
|
||||
if params[:article_id]
|
||||
params[:article] = self.find( params[:article_id] )
|
||||
end
|
||||
merge tickets
|
||||
|
||||
# get ticket states
|
||||
ticket_state_ids = []
|
||||
if params[:ticket]
|
||||
ticket_state_type = params[:ticket].ticket_state.state_type
|
||||
end
|
||||
ticket_state_types = ['open', 'closed', 'pending action', 'pending reminder']
|
||||
if ticket_state_type && !ticket_state_types.include?(ticket_state_type.name)
|
||||
ticket_state_ids.push params[:ticket].ticket_state.id
|
||||
end
|
||||
ticket_state_types.each {|type|
|
||||
ticket_state_type = Ticket::StateType.where( :name => type ).first
|
||||
if ticket_state_type
|
||||
ticket_state_type.states.each {|ticket_state|
|
||||
ticket_state_ids.push ticket_state.id
|
||||
}
|
||||
end
|
||||
}
|
||||
ticket = Ticket.find(123)
|
||||
result = ticket.merge_to(
|
||||
:ticket_id => 123,
|
||||
)
|
||||
|
||||
# get owner
|
||||
owner_ids = []
|
||||
if params[:ticket]
|
||||
params[:ticket].agent_of_group.each { |user|
|
||||
owner_ids.push user.id
|
||||
}
|
||||
end
|
||||
returns
|
||||
|
||||
# get group
|
||||
group_ids = []
|
||||
Group.where( :active => true ).each { |group|
|
||||
group_ids.push group.id
|
||||
}
|
||||
result = true|false
|
||||
|
||||
# get group / user relations
|
||||
agents = {}
|
||||
Ticket.agents.each { |user|
|
||||
agents[ user.id ] = 1
|
||||
}
|
||||
groups_users = {}
|
||||
group_ids.each {|group_id|
|
||||
groups_users[ group_id ] = []
|
||||
Group.find( group_id ).users.each {|user|
|
||||
next if !agents[ user.id ]
|
||||
groups_users[ group_id ].push user.id
|
||||
}
|
||||
}
|
||||
|
||||
# get priorities
|
||||
ticket_priority_ids = []
|
||||
Ticket::Priority.where( :active => true ).each { |priority|
|
||||
ticket_priority_ids.push priority.id
|
||||
}
|
||||
|
||||
ticket_article_type_ids = []
|
||||
if params[:ticket]
|
||||
ticket_article_types = ['note', 'phone']
|
||||
if params[:ticket].group.email_address_id
|
||||
ticket_article_types.push 'email'
|
||||
end
|
||||
ticket_article_types.each {|ticket_article_type_name|
|
||||
ticket_article_type = Ticket::Article::Type.lookup( :name => ticket_article_type_name )
|
||||
if ticket_article_type
|
||||
ticket_article_type_ids.push ticket_article_type.id
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
:ticket_article_type_id => ticket_article_type_ids,
|
||||
:ticket_state_id => ticket_state_ids,
|
||||
:ticket_priority_id => ticket_priority_ids,
|
||||
:owner_id => owner_ids,
|
||||
:group_id => group_ids,
|
||||
:group_id__owner_id => groups_users,
|
||||
}
|
||||
end
|
||||
=end
|
||||
|
||||
def merge_to(data)
|
||||
|
||||
|
@ -157,724 +98,32 @@ class Ticket < ApplicationModel
|
|||
self.save
|
||||
end
|
||||
|
||||
# def self.agent
|
||||
# Role.where( :name => ['Agent'], :active => true ).first.users.where( :active => true ).uniq()
|
||||
# end
|
||||
|
||||
def subject_build (subject)
|
||||
|
||||
# clena subject
|
||||
subject = self.subject_clean(subject)
|
||||
|
||||
ticket_hook = Setting.get('ticket_hook')
|
||||
ticket_hook_divider = Setting.get('ticket_hook_divider')
|
||||
|
||||
# none position
|
||||
if Setting.get('ticket_hook_position') == 'none'
|
||||
return subject
|
||||
end
|
||||
|
||||
# right position
|
||||
if Setting.get('ticket_hook_position') == 'right'
|
||||
return subject + " [#{ticket_hook}#{ticket_hook_divider}#{self.number}] "
|
||||
end
|
||||
|
||||
# left position
|
||||
return "[#{ticket_hook}#{ticket_hook_divider}#{self.number}] " + subject
|
||||
end
|
||||
|
||||
def subject_clean (subject)
|
||||
ticket_hook = Setting.get('ticket_hook')
|
||||
ticket_hook_divider = Setting.get('ticket_hook_divider')
|
||||
ticket_subject_size = Setting.get('ticket_subject_size')
|
||||
|
||||
# remove all possible ticket hook formats with []
|
||||
subject = subject.gsub /\[#{ticket_hook}: #{self.number}\](\s+?|)/, ''
|
||||
subject = subject.gsub /\[#{ticket_hook}:#{self.number}\](\s+?|)/, ''
|
||||
subject = subject.gsub /\[#{ticket_hook}#{ticket_hook_divider}#{self.number}\](\s+?|)/, ''
|
||||
|
||||
# remove all possible ticket hook formats without []
|
||||
subject = subject.gsub /#{ticket_hook}: #{self.number}(\s+?|)/, ''
|
||||
subject = subject.gsub /#{ticket_hook}:#{self.number}(\s+?|)/, ''
|
||||
subject = subject.gsub /#{ticket_hook}#{ticket_hook_divider}#{self.number}(\s+?|)/, ''
|
||||
|
||||
# remove leading "..:\s" and "..[\d+]:\s" e. g. "Re: " or "Re[5]: "
|
||||
subject = subject.gsub /^(..(\[\d+\])?:\s)+/, ''
|
||||
|
||||
# resize subject based on config
|
||||
if subject.length > ticket_subject_size.to_i
|
||||
subject = subject[ 0, ticket_subject_size.to_i ] + '[...]'
|
||||
end
|
||||
|
||||
return subject
|
||||
end
|
||||
|
||||
# ticket.permission(
|
||||
# :current_user => 123
|
||||
# )
|
||||
def permission (data)
|
||||
|
||||
# check customer
|
||||
if data[:current_user].is_role('Customer')
|
||||
|
||||
# access ok if its own ticket
|
||||
return true if self.customer_id == data[:current_user].id
|
||||
|
||||
# access ok if its organization ticket
|
||||
if data[:current_user].organization_id && self.organization_id
|
||||
return true if self.organization_id == data[:current_user].organization_id
|
||||
end
|
||||
|
||||
# no access
|
||||
return false
|
||||
end
|
||||
|
||||
# check agent
|
||||
|
||||
# access if requestor is owner
|
||||
return true if self.owner_id == data[:current_user].id
|
||||
|
||||
# access if requestor is in group
|
||||
data[:current_user].groups.each {|group|
|
||||
return true if self.group.id == group.id
|
||||
}
|
||||
return false
|
||||
end
|
||||
|
||||
# Ticket.search(
|
||||
# :current_user => 123,
|
||||
# :query => 'search something',
|
||||
# :limit => 15,
|
||||
# )
|
||||
def self.search (params)
|
||||
|
||||
# get params
|
||||
query = params[:query]
|
||||
limit = params[:limit] || 12
|
||||
current_user = params[:current_user]
|
||||
|
||||
conditions = []
|
||||
if current_user.is_role('Agent')
|
||||
group_ids = Group.select( 'groups.id' ).joins(:users).
|
||||
where( 'groups_users.user_id = ?', current_user.id ).
|
||||
where( 'groups.active = ?', true ).
|
||||
map( &:id )
|
||||
conditions = [ 'group_id IN (?)', group_ids ]
|
||||
else
|
||||
if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
|
||||
conditions = [ 'customer_id = ?', current_user.id ]
|
||||
else
|
||||
conditions = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
|
||||
end
|
||||
end
|
||||
|
||||
# do query
|
||||
tickets_all = Ticket.select('DISTINCT(tickets.id)').
|
||||
where(conditions).
|
||||
where( '( `tickets`.`title` LIKE ? OR `tickets`.`number` LIKE ? OR `ticket_articles`.`body` LIKE ? OR `ticket_articles`.`from` LIKE ? OR `ticket_articles`.`to` LIKE ? OR `ticket_articles`.`subject` LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" ).
|
||||
joins(:articles).
|
||||
limit(limit).
|
||||
order('`tickets`.`created_at` DESC')
|
||||
|
||||
# build result list
|
||||
tickets = []
|
||||
users = {}
|
||||
tickets_all.each do |ticket|
|
||||
ticket_tmp = Ticket.lookup( :id => ticket.id )
|
||||
tickets.push ticket_tmp
|
||||
end
|
||||
|
||||
return tickets
|
||||
end
|
||||
# Ticket.overview_list(
|
||||
# :current_user => 123,
|
||||
# )
|
||||
def self.overview_list (data)
|
||||
|
||||
# get customer overviews
|
||||
if data[:current_user].is_role('Customer')
|
||||
role = data[:current_user].is_role( 'Customer' )
|
||||
if data[:current_user].organization_id && data[:current_user].organization.shared
|
||||
overviews = Overview.where( :role_id => role.id, :active => true )
|
||||
else
|
||||
overviews = Overview.where( :role_id => role.id, :organization_shared => false, :active => true )
|
||||
end
|
||||
return overviews
|
||||
end
|
||||
|
||||
# get agent overviews
|
||||
role = data[:current_user].is_role( 'Agent' )
|
||||
overviews = Overview.where( :role_id => role.id, :active => true )
|
||||
return overviews
|
||||
end
|
||||
|
||||
# Ticket.overview(
|
||||
# :view => 'some_view_url',
|
||||
# :current_user => OBJECT,
|
||||
# )
|
||||
def self.overview (data)
|
||||
|
||||
overviews = self.overview_list(data)
|
||||
|
||||
# build up attributes hash
|
||||
overview_selected = nil
|
||||
overview_selected_raw = nil
|
||||
|
||||
overviews.each { |overview|
|
||||
|
||||
# remember selected view
|
||||
if data[:view] && data[:view] == overview.link
|
||||
overview_selected = overview
|
||||
overview_selected_raw = Marshal.load( Marshal.dump(overview.attributes) )
|
||||
end
|
||||
|
||||
# replace e.g. 'current_user.id' with current_user.id
|
||||
overview.condition.each { |item, value |
|
||||
if value && value.class.to_s == 'String'
|
||||
parts = value.split( '.', 2 )
|
||||
if parts[0] && parts[1] && parts[0] == 'current_user'
|
||||
overview.condition[item] = data[:current_user][parts[1].to_sym]
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
if data[:view] && !overview_selected
|
||||
return
|
||||
end
|
||||
|
||||
# sortby
|
||||
# prio
|
||||
# state
|
||||
# group
|
||||
# customer
|
||||
|
||||
# order
|
||||
# asc
|
||||
# desc
|
||||
|
||||
# groupby
|
||||
# prio
|
||||
# state
|
||||
# group
|
||||
# customer
|
||||
|
||||
# all = attributes[:myopenassigned]
|
||||
# all.merge( { :group_id => groups } )
|
||||
|
||||
# @tickets = Ticket.where(:group_id => groups, attributes[:myopenassigned] ).limit(params[:limit])
|
||||
# get only tickets with permissions
|
||||
if data[:current_user].is_role('Customer')
|
||||
group_ids = Group.select( 'groups.id' ).
|
||||
where( 'groups.active = ?', true ).
|
||||
map( &:id )
|
||||
else
|
||||
group_ids = Group.select( 'groups.id' ).joins(:users).
|
||||
where( 'groups_users.user_id = ?', [ data[:current_user].id ] ).
|
||||
where( 'groups.active = ?', true ).
|
||||
map( &:id )
|
||||
end
|
||||
|
||||
# overview meta for navbar
|
||||
if !overview_selected
|
||||
|
||||
# loop each overview
|
||||
result = []
|
||||
overviews.each { |overview|
|
||||
|
||||
# get count
|
||||
count = Ticket.where( :group_id => group_ids ).where( self._condition( overview.condition ) ).count()
|
||||
|
||||
# get meta info
|
||||
all = {
|
||||
:name => overview.name,
|
||||
:prio => overview.prio,
|
||||
:link => overview.link,
|
||||
}
|
||||
|
||||
# push to result data
|
||||
result.push all.merge( { :count => count } )
|
||||
}
|
||||
return result
|
||||
end
|
||||
|
||||
# get result list
|
||||
if data[:array]
|
||||
order_by = overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s
|
||||
if overview_selected.group_by && !overview_selected.group_by.empty?
|
||||
order_by = overview_selected.group_by + '_id, ' + order_by
|
||||
end
|
||||
tickets = Ticket.select( 'id' ).
|
||||
where( :group_id => group_ids ).
|
||||
where( self._condition( overview_selected.condition ) ).
|
||||
order( order_by ).
|
||||
limit( 500 )
|
||||
|
||||
ticket_ids = []
|
||||
tickets.each { |ticket|
|
||||
ticket_ids.push ticket.id
|
||||
}
|
||||
|
||||
tickets_count = Ticket.where( :group_id => group_ids ).
|
||||
where( self._condition( overview_selected.condition ) ).
|
||||
count()
|
||||
|
||||
return {
|
||||
:ticket_list => ticket_ids,
|
||||
:tickets_count => tickets_count,
|
||||
:overview => overview_selected_raw,
|
||||
}
|
||||
end
|
||||
|
||||
# get tickets for overview
|
||||
data[:start_page] ||= 1
|
||||
tickets = Ticket.where( :group_id => group_ids ).
|
||||
where( self._condition( overview_selected.condition ) ).
|
||||
order( overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s )#.
|
||||
# limit( overview_selected.view[ data[:view_mode].to_sym ][:per_page] ).
|
||||
# offset( overview_selected.view[ data[:view_mode].to_sym ][:per_page].to_i * ( data[:start_page].to_i - 1 ) )
|
||||
|
||||
tickets_count = Ticket.where( :group_id => group_ids ).
|
||||
where( self._condition( overview_selected.condition ) ).
|
||||
count()
|
||||
|
||||
return {
|
||||
:tickets => tickets,
|
||||
:tickets_count => tickets_count,
|
||||
:overview => overview_selected_raw,
|
||||
}
|
||||
|
||||
end
|
||||
def self._condition(condition)
|
||||
sql = ''
|
||||
bind = [nil]
|
||||
condition.each {|key, value|
|
||||
if sql != ''
|
||||
sql += ' AND '
|
||||
end
|
||||
if value.class == Array
|
||||
sql += " #{key} IN (?)"
|
||||
bind.push value
|
||||
elsif value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
|
||||
time = Time.now
|
||||
if value['area'] == 'minute'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60
|
||||
else
|
||||
time += value['count'].to_i * 60
|
||||
end
|
||||
elsif value['area'] == 'hour'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60 * 60
|
||||
else
|
||||
time += value['count'].to_i * 60 * 60
|
||||
end
|
||||
elsif value['area'] == 'day'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60 * 60 * 24
|
||||
else
|
||||
time += value['count'].to_i * 60 * 60 * 24
|
||||
end
|
||||
elsif value['area'] == 'month'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60 * 60 * 24 * 31
|
||||
else
|
||||
time += value['count'].to_i * 60 * 60 * 24 * 31
|
||||
end
|
||||
elsif value['area'] == 'year'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60 * 60 * 24 * 365
|
||||
else
|
||||
time += value['count'].to_i * 60 * 60 * 24 * 365
|
||||
end
|
||||
end
|
||||
if value['direction'] == 'last'
|
||||
sql += " #{key} > ?"
|
||||
bind.push time
|
||||
else
|
||||
sql += " #{key} < ?"
|
||||
bind.push time
|
||||
end
|
||||
else
|
||||
sql += " #{key} = ?"
|
||||
bind.push value
|
||||
end
|
||||
}
|
||||
bind[0] = sql
|
||||
return bind
|
||||
end
|
||||
|
||||
def self.number_adapter
|
||||
|
||||
# load backend based on config
|
||||
adapter_name = Setting.get('ticket_number')
|
||||
adapter = nil
|
||||
case adapter_name
|
||||
when Symbol, String
|
||||
require "ticket/number/#{adapter_name.to_s.downcase}"
|
||||
adapter = Ticket::Number.const_get("#{adapter_name.to_s.capitalize}")
|
||||
else
|
||||
raise "Missing number_adapter '#{adapter_name}'"
|
||||
end
|
||||
return adapter
|
||||
end
|
||||
|
||||
def self.escalation_calculation_rebuild
|
||||
ticket_state_list_open = Ticket::State.by_category( 'open' )
|
||||
|
||||
tickets = Ticket.where( :ticket_state_id => ticket_state_list_open )
|
||||
tickets.each {|ticket|
|
||||
ticket.escalation_calculation
|
||||
}
|
||||
end
|
||||
|
||||
def _escalation_calculation_get_sla
|
||||
|
||||
sla_selected = nil
|
||||
sla_list = Cache.get( 'SLA::List::Active' )
|
||||
if sla_list == nil
|
||||
sla_list = Sla.where( :active => true ).all
|
||||
Cache.write( 'SLA::List::Active', sla_list, { :expires_in => 1.hour } )
|
||||
end
|
||||
sla_list.each {|sla|
|
||||
if !sla.condition || sla.condition.empty?
|
||||
sla_selected = sla
|
||||
elsif sla.condition
|
||||
hit = false
|
||||
map = [
|
||||
[ 'tickets.ticket_priority_id', 'ticket_priority_id' ],
|
||||
[ 'tickets.group_id', 'group_id' ]
|
||||
]
|
||||
map.each {|item|
|
||||
if sla.condition[ item[0] ]
|
||||
if sla.condition[ item[0] ].class == String
|
||||
sla.condition[ item[0] ] = [ sla.condition[ item[0] ] ]
|
||||
end
|
||||
if sla.condition[ item[0] ].include?( self[ item[1] ].to_s )
|
||||
hit = true
|
||||
else
|
||||
hit = false
|
||||
end
|
||||
end
|
||||
}
|
||||
if hit
|
||||
sla_selected = sla
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return sla_selected
|
||||
end
|
||||
|
||||
def _escalation_calculation_higher_time(escalation_time, check_time, done_time)
|
||||
return escalation_time if done_time
|
||||
return check_time if !escalation_time
|
||||
return escalation_time if !check_time
|
||||
return check_time if escalation_time > check_time
|
||||
return escalation_time
|
||||
end
|
||||
|
||||
def escalation_calculation
|
||||
|
||||
# set escalation off if ticket is already closed
|
||||
ticket_state = Ticket::State.lookup( :id => self.ticket_state_id )
|
||||
if ticket_state.ignore_escalation?
|
||||
self.escalation_time = nil
|
||||
# self.first_response_escal_date = nil
|
||||
# self.close_time_escal_date = nil
|
||||
self.callback_loop = true
|
||||
self.save
|
||||
return true
|
||||
end
|
||||
|
||||
# get sla for ticket
|
||||
sla_selected = self._escalation_calculation_get_sla
|
||||
|
||||
# reset escalation if no sla is set
|
||||
if !sla_selected
|
||||
self.escalation_time = nil
|
||||
# self.first_response_escal_date = nil
|
||||
# self.close_time_escal_date = nil
|
||||
self.callback_loop = true
|
||||
self.save
|
||||
return true
|
||||
end
|
||||
|
||||
# puts sla_selected.inspect
|
||||
# puts days.inspect
|
||||
self.escalation_time = nil
|
||||
self.first_response_escal_date = nil
|
||||
self.update_time_escal_date = nil
|
||||
self.close_time_escal_date = nil
|
||||
|
||||
# first response
|
||||
if sla_selected.first_response_time
|
||||
|
||||
# get escalation date without pending time
|
||||
self.first_response_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.first_response_time, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# get pending time between created and first response escal. time
|
||||
time_in_pending = escalation_suspend( self.created_at, self.first_response_escal_date, 'relative', sla_selected, sla_selected.first_response_time )
|
||||
|
||||
# get new escalation time (original escal_date + time_in_pending)
|
||||
self.first_response_escal_date = TimeCalculation.dest_time( self.first_response_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# set ticket escalation
|
||||
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response )
|
||||
end
|
||||
if self.first_response# && !self.first_response_in_min
|
||||
|
||||
# get response time in min between created and first response
|
||||
self.first_response_in_min = escalation_suspend( self.created_at, self.first_response, 'real', sla_selected )
|
||||
|
||||
end
|
||||
|
||||
# set time to show if sla is raised ot in
|
||||
if sla_selected.first_response_time && self.first_response_in_min
|
||||
self.first_response_diff_in_min = sla_selected.first_response_time - self.first_response_in_min
|
||||
end
|
||||
|
||||
|
||||
# update time
|
||||
last_update = self.last_contact_agent
|
||||
if !last_update
|
||||
last_update = self.created_at
|
||||
end
|
||||
if sla_selected.update_time
|
||||
self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# get pending time between created and update escal. time
|
||||
time_in_pending = escalation_suspend( last_update, self.update_time_escal_date, 'relative', sla_selected, sla_selected.update_time )
|
||||
|
||||
# get new escalation time (original escal_date + time_in_pending)
|
||||
self.update_time_escal_date = TimeCalculation.dest_time( self.update_time_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# set ticket escalation
|
||||
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.update_time_escal_date, false )
|
||||
end
|
||||
if self.last_contact_agent
|
||||
self.update_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.last_contact_agent, sla_selected.data, sla_selected.timezone )
|
||||
end
|
||||
|
||||
# set sla time
|
||||
if sla_selected.update_time && self.update_time_in_min
|
||||
self.update_time_diff_in_min = sla_selected.update_time - self.update_time_in_min
|
||||
end
|
||||
|
||||
|
||||
# close time
|
||||
if sla_selected.close_time
|
||||
|
||||
# get escalation date without pending time
|
||||
self.close_time_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.close_time, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# get pending time between created and close escal. time
|
||||
extended_escalation = escalation_suspend( self.created_at, self.close_time_escal_date, 'relative', sla_selected, sla_selected.close_time )
|
||||
|
||||
# get new escalation time (original escal_date + time_in_pending)
|
||||
self.close_time_escal_date = TimeCalculation.dest_time( self.close_time_escal_date, extended_escalation.to_i, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# set ticket escalation
|
||||
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time )
|
||||
end
|
||||
if self.close_time # && !self.close_time_in_min
|
||||
self.close_time_in_min = escalation_suspend( self.created_at, self.close_time, 'real', sla_selected )
|
||||
end
|
||||
# set sla time
|
||||
if sla_selected.close_time && self.close_time_in_min
|
||||
self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min
|
||||
end
|
||||
self.callback_loop = true
|
||||
self.save
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
list tickets by customer groupd in state categroie open and closed
|
||||
|
||||
result = Ticket.list_by_customer(
|
||||
:customer_id => 123,
|
||||
:limit => 15, # optional, default 15
|
||||
)
|
||||
|
||||
returns
|
||||
|
||||
result = {
|
||||
:open => tickets_open,
|
||||
:closed => tickets_closed,
|
||||
}
|
||||
|
||||
=end
|
||||
|
||||
def self.list_by_customer(data)
|
||||
|
||||
# get closed/open states
|
||||
ticket_state_list_open = Ticket::State.by_category( 'open' )
|
||||
ticket_state_list_closed = Ticket::State.by_category( 'closed' )
|
||||
|
||||
# get tickets
|
||||
tickets_open = Ticket.where(
|
||||
:customer_id => data[:customer_id],
|
||||
:ticket_state_id => ticket_state_list_open
|
||||
).limit( data[:limit] || 15 ).order('created_at DESC')
|
||||
|
||||
tickets_closed = Ticket.where(
|
||||
:customer_id => data[:customer_id],
|
||||
:ticket_state_id => ticket_state_list_closed
|
||||
).limit( data[:limit] || 15 ).order('created_at DESC')
|
||||
|
||||
return {
|
||||
:open => tickets_open,
|
||||
:closed => tickets_closed,
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def number_generate
|
||||
def check_generate
|
||||
return if self.number
|
||||
|
||||
# generate number
|
||||
(1..25_000).each do |i|
|
||||
number = Ticket.number_adapter.number_generate_item()
|
||||
ticket = Ticket.where( :number => number ).first
|
||||
if ticket != nil
|
||||
number = Ticket.number_adapter.number_generate_item()
|
||||
else
|
||||
self.number = number
|
||||
return number
|
||||
end
|
||||
end
|
||||
self.number = Ticket::Number.generate
|
||||
end
|
||||
|
||||
def check_defaults
|
||||
if !self.owner_id
|
||||
self.owner_id = 1
|
||||
end
|
||||
# if self.customer_id && ( !self.organization_id || self.organization_id.empty? )
|
||||
if self.customer_id
|
||||
customer = User.find( self.customer_id )
|
||||
if self.organization_id != customer.organization_id
|
||||
if self.organization_id != customer.organization_id
|
||||
self.organization_id = customer.organization_id
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
def destroy_dependencies
|
||||
|
||||
# delete history
|
||||
History.remove( 'Ticket', self.id )
|
||||
# delete history
|
||||
History.remove( 'Ticket', self.id )
|
||||
|
||||
# delete articles
|
||||
self.articles.destroy_all
|
||||
end
|
||||
|
||||
#type could be:
|
||||
# real - time without supsend state
|
||||
# relative - only suspend time
|
||||
|
||||
def escalation_suspend (start_time, end_time, type, sla_selected, sla_time = 0)
|
||||
if type == 'relative'
|
||||
end_time += sla_time * 60
|
||||
end
|
||||
total_time_without_pending = 0
|
||||
total_time = 0
|
||||
#get history for ticket
|
||||
history_list = History.list( 'Ticket', self.id )
|
||||
|
||||
#loop through hist. changes and get time
|
||||
last_state = nil
|
||||
last_state_change = nil
|
||||
last_state_is_pending = false
|
||||
history_list.each { |history_item|
|
||||
|
||||
# ignore if it isn't a state change
|
||||
next if !history_item.history_attribute_id
|
||||
history_attribute = History::Attribute.lookup( :id => history_item.history_attribute_id );
|
||||
next if history_attribute.name != 'ticket_state'
|
||||
|
||||
# ignore all newer state before start_time
|
||||
next if history_item.created_at < start_time
|
||||
|
||||
# ignore all older state changes after end_time
|
||||
next if last_state_change && last_state_change > end_time
|
||||
|
||||
# if created_at is later then end_time, use end_time as last time
|
||||
if history_item.created_at > end_time
|
||||
history_item.created_at = end_time
|
||||
end
|
||||
|
||||
# get initial state and time
|
||||
if !last_state
|
||||
last_state = history_item.value_from
|
||||
last_state_change = start_time
|
||||
end
|
||||
|
||||
# check if time need to be counted
|
||||
counted = true
|
||||
if history_item.value_from == 'pending'
|
||||
counted = false
|
||||
elsif history_item.value_from == 'close'
|
||||
counted = false
|
||||
end
|
||||
|
||||
diff = escalation_time_diff( last_state_change, history_item.created_at, sla_selected )
|
||||
if counted
|
||||
puts "Diff count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}"
|
||||
total_time_without_pending = total_time_without_pending + diff
|
||||
else
|
||||
puts "Diff not count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}"
|
||||
end
|
||||
total_time = total_time + diff
|
||||
|
||||
if history_item.value_to == 'pending'
|
||||
last_state_is_pending = true
|
||||
else
|
||||
last_state_is_pending = false
|
||||
end
|
||||
|
||||
# remember for next loop last state
|
||||
last_state = history_item.value_to
|
||||
last_state_change = history_item.created_at
|
||||
}
|
||||
|
||||
# if last state isnt pending, count rest
|
||||
if !last_state_is_pending && last_state_change && last_state_change < end_time
|
||||
diff = escalation_time_diff( last_state_change, end_time, sla_selected )
|
||||
puts "Diff count last state was not pending #{diff.to_s} - #{last_state_change} - #{end_time}"
|
||||
total_time_without_pending = total_time_without_pending + diff
|
||||
total_time = total_time + diff
|
||||
end
|
||||
|
||||
# if we have not had any state change
|
||||
if !last_state_change
|
||||
diff = escalation_time_diff( start_time, end_time, sla_selected )
|
||||
puts 'Diff state has not changed ' + diff.to_s
|
||||
total_time_without_pending = total_time_without_pending + diff
|
||||
total_time = total_time + diff
|
||||
end
|
||||
|
||||
#return sum
|
||||
if type == 'real'
|
||||
return total_time_without_pending
|
||||
elsif type == 'relative'
|
||||
relative = total_time - total_time_without_pending
|
||||
return relative
|
||||
else
|
||||
raise "ERROR: Unknown type #{type}"
|
||||
end
|
||||
end
|
||||
|
||||
def escalation_time_diff( start_time, end_time, sla_selected )
|
||||
if sla_selected
|
||||
diff = TimeCalculation.business_time_diff( start_time, end_time, sla_selected.data, sla_selected.timezone)
|
||||
else
|
||||
diff = TimeCalculation.business_time_diff( start_time, end_time )
|
||||
end
|
||||
diff
|
||||
end
|
||||
|
||||
class Number
|
||||
# delete articles
|
||||
self.articles.destroy_all
|
||||
end
|
||||
|
||||
end
|
||||
|
|
305
app/models/ticket/escalation.rb
Normal file
305
app/models/ticket/escalation.rb
Normal file
|
@ -0,0 +1,305 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Ticket::Escalation
|
||||
|
||||
=begin
|
||||
|
||||
rebuild escalations for all open tickets
|
||||
|
||||
result = Ticket::Escalation.rebuild_all
|
||||
|
||||
returns
|
||||
|
||||
result = true
|
||||
|
||||
=end
|
||||
|
||||
def self.rebuild_all
|
||||
ticket_state_list_open = Ticket::State.by_category( 'open' )
|
||||
|
||||
tickets = Ticket.where( :ticket_state_id => ticket_state_list_open )
|
||||
tickets.each {|ticket|
|
||||
ticket.escalation_calculation
|
||||
}
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
rebuild escalation for ticket
|
||||
|
||||
ticket = Ticket.find(123)
|
||||
result = ticket.escalation_calculation
|
||||
|
||||
returns
|
||||
|
||||
result = true
|
||||
|
||||
=end
|
||||
|
||||
def escalation_calculation
|
||||
|
||||
# set escalation off if ticket is already closed
|
||||
ticket_state = Ticket::State.lookup( :id => self.ticket_state_id )
|
||||
if ticket_state.ignore_escalation?
|
||||
self.escalation_time = nil
|
||||
# self.first_response_escal_date = nil
|
||||
# self.close_time_escal_date = nil
|
||||
self.callback_loop = true
|
||||
self.save
|
||||
return true
|
||||
end
|
||||
|
||||
# get sla for ticket
|
||||
sla_selected = escalation_calculation_get_sla
|
||||
|
||||
# reset escalation if no sla is set
|
||||
if !sla_selected
|
||||
self.escalation_time = nil
|
||||
# self.first_response_escal_date = nil
|
||||
# self.close_time_escal_date = nil
|
||||
self.callback_loop = true
|
||||
self.save
|
||||
return true
|
||||
end
|
||||
|
||||
# puts sla_selected.inspect
|
||||
# puts days.inspect
|
||||
self.escalation_time = nil
|
||||
self.first_response_escal_date = nil
|
||||
self.update_time_escal_date = nil
|
||||
self.close_time_escal_date = nil
|
||||
|
||||
# first response
|
||||
if sla_selected.first_response_time
|
||||
|
||||
# get escalation date without pending time
|
||||
self.first_response_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.first_response_time, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# get pending time between created and first response escal. time
|
||||
time_in_pending = escalation_suspend( self.created_at, self.first_response_escal_date, 'relative', sla_selected, sla_selected.first_response_time )
|
||||
|
||||
# get new escalation time (original escal_date + time_in_pending)
|
||||
self.first_response_escal_date = TimeCalculation.dest_time( self.first_response_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# set ticket escalation
|
||||
self.escalation_time = calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response )
|
||||
end
|
||||
if self.first_response# && !self.first_response_in_min
|
||||
|
||||
# get response time in min between created and first response
|
||||
self.first_response_in_min = escalation_suspend( self.created_at, self.first_response, 'real', sla_selected )
|
||||
|
||||
end
|
||||
|
||||
# set time to show if sla is raised ot in
|
||||
if sla_selected.first_response_time && self.first_response_in_min
|
||||
self.first_response_diff_in_min = sla_selected.first_response_time - self.first_response_in_min
|
||||
end
|
||||
|
||||
|
||||
# update time
|
||||
last_update = self.last_contact_agent
|
||||
if !last_update
|
||||
last_update = self.created_at
|
||||
end
|
||||
if sla_selected.update_time
|
||||
self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# get pending time between created and update escal. time
|
||||
time_in_pending = escalation_suspend( last_update, self.update_time_escal_date, 'relative', sla_selected, sla_selected.update_time )
|
||||
|
||||
# get new escalation time (original escal_date + time_in_pending)
|
||||
self.update_time_escal_date = TimeCalculation.dest_time( self.update_time_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# set ticket escalation
|
||||
self.escalation_time = calculation_higher_time( self.escalation_time, self.update_time_escal_date, false )
|
||||
end
|
||||
if self.last_contact_agent
|
||||
self.update_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.last_contact_agent, sla_selected.data, sla_selected.timezone )
|
||||
end
|
||||
|
||||
# set sla time
|
||||
if sla_selected.update_time && self.update_time_in_min
|
||||
self.update_time_diff_in_min = sla_selected.update_time - self.update_time_in_min
|
||||
end
|
||||
|
||||
|
||||
# close time
|
||||
if sla_selected.close_time
|
||||
|
||||
# get escalation date without pending time
|
||||
self.close_time_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.close_time, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# get pending time between created and close escal. time
|
||||
extended_escalation = escalation_suspend( self.created_at, self.close_time_escal_date, 'relative', sla_selected, sla_selected.close_time )
|
||||
|
||||
# get new escalation time (original escal_date + time_in_pending)
|
||||
self.close_time_escal_date = TimeCalculation.dest_time( self.close_time_escal_date, extended_escalation.to_i, sla_selected.data, sla_selected.timezone )
|
||||
|
||||
# set ticket escalation
|
||||
self.escalation_time = calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time )
|
||||
end
|
||||
if self.close_time # && !self.close_time_in_min
|
||||
self.close_time_in_min = escalation_suspend( self.created_at, self.close_time, 'real', sla_selected )
|
||||
end
|
||||
# set sla time
|
||||
if sla_selected.close_time && self.close_time_in_min
|
||||
self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min
|
||||
end
|
||||
self.callback_loop = true
|
||||
self.save
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#type could be:
|
||||
# real - time without supsend state
|
||||
# relative - only suspend time
|
||||
|
||||
def escalation_suspend (start_time, end_time, type, sla_selected, sla_time = 0)
|
||||
if type == 'relative'
|
||||
end_time += sla_time * 60
|
||||
end
|
||||
total_time_without_pending = 0
|
||||
total_time = 0
|
||||
#get history for ticket
|
||||
history_list = History.list( 'Ticket', self.id )
|
||||
|
||||
#loop through hist. changes and get time
|
||||
last_state = nil
|
||||
last_state_change = nil
|
||||
last_state_is_pending = false
|
||||
history_list.each { |history_item|
|
||||
|
||||
# ignore if it isn't a state change
|
||||
next if !history_item.history_attribute_id
|
||||
history_attribute = History::Attribute.lookup( :id => history_item.history_attribute_id );
|
||||
next if history_attribute.name != 'ticket_state'
|
||||
|
||||
# ignore all newer state before start_time
|
||||
next if history_item.created_at < start_time
|
||||
|
||||
# ignore all older state changes after end_time
|
||||
next if last_state_change && last_state_change > end_time
|
||||
|
||||
# if created_at is later then end_time, use end_time as last time
|
||||
if history_item.created_at > end_time
|
||||
history_item.created_at = end_time
|
||||
end
|
||||
|
||||
# get initial state and time
|
||||
if !last_state
|
||||
last_state = history_item.value_from
|
||||
last_state_change = start_time
|
||||
end
|
||||
|
||||
# check if time need to be counted
|
||||
counted = true
|
||||
if history_item.value_from == 'pending'
|
||||
counted = false
|
||||
elsif history_item.value_from == 'close'
|
||||
counted = false
|
||||
end
|
||||
|
||||
diff = escalation_time_diff( last_state_change, history_item.created_at, sla_selected )
|
||||
if counted
|
||||
puts "Diff count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}"
|
||||
total_time_without_pending = total_time_without_pending + diff
|
||||
else
|
||||
puts "Diff not count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}"
|
||||
end
|
||||
total_time = total_time + diff
|
||||
|
||||
if history_item.value_to == 'pending'
|
||||
last_state_is_pending = true
|
||||
else
|
||||
last_state_is_pending = false
|
||||
end
|
||||
|
||||
# remember for next loop last state
|
||||
last_state = history_item.value_to
|
||||
last_state_change = history_item.created_at
|
||||
}
|
||||
|
||||
# if last state isnt pending, count rest
|
||||
if !last_state_is_pending && last_state_change && last_state_change < end_time
|
||||
diff = escalation_time_diff( last_state_change, end_time, sla_selected )
|
||||
puts "Diff count last state was not pending #{diff.to_s} - #{last_state_change} - #{end_time}"
|
||||
total_time_without_pending = total_time_without_pending + diff
|
||||
total_time = total_time + diff
|
||||
end
|
||||
|
||||
# if we have not had any state change
|
||||
if !last_state_change
|
||||
diff = escalation_time_diff( start_time, end_time, sla_selected )
|
||||
puts 'Diff state has not changed ' + diff.to_s
|
||||
total_time_without_pending = total_time_without_pending + diff
|
||||
total_time = total_time + diff
|
||||
end
|
||||
|
||||
#return sum
|
||||
if type == 'real'
|
||||
return total_time_without_pending
|
||||
elsif type == 'relative'
|
||||
relative = total_time - total_time_without_pending
|
||||
return relative
|
||||
else
|
||||
raise "ERROR: Unknown type #{type}"
|
||||
end
|
||||
end
|
||||
|
||||
def escalation_time_diff( start_time, end_time, sla_selected )
|
||||
if sla_selected
|
||||
diff = TimeCalculation.business_time_diff( start_time, end_time, sla_selected.data, sla_selected.timezone)
|
||||
else
|
||||
diff = TimeCalculation.business_time_diff( start_time, end_time )
|
||||
end
|
||||
diff
|
||||
end
|
||||
|
||||
def escalation_calculation_get_sla
|
||||
|
||||
sla_selected = nil
|
||||
sla_list = Cache.get( 'SLA::List::Active' )
|
||||
if sla_list == nil
|
||||
sla_list = Sla.where( :active => true ).all
|
||||
Cache.write( 'SLA::List::Active', sla_list, { :expires_in => 1.hour } )
|
||||
end
|
||||
sla_list.each {|sla|
|
||||
if !sla.condition || sla.condition.empty?
|
||||
sla_selected = sla
|
||||
elsif sla.condition
|
||||
hit = false
|
||||
map = [
|
||||
[ 'tickets.ticket_priority_id', 'ticket_priority_id' ],
|
||||
[ 'tickets.group_id', 'group_id' ]
|
||||
]
|
||||
map.each {|item|
|
||||
if sla.condition[ item[0] ]
|
||||
if sla.condition[ item[0] ].class == String
|
||||
sla.condition[ item[0] ] = [ sla.condition[ item[0] ] ]
|
||||
end
|
||||
if sla.condition[ item[0] ].include?( self[ item[1] ].to_s )
|
||||
hit = true
|
||||
else
|
||||
hit = false
|
||||
end
|
||||
end
|
||||
}
|
||||
if hit
|
||||
sla_selected = sla
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return sla_selected
|
||||
end
|
||||
|
||||
def calculation_higher_time(escalation_time, check_time, done_time)
|
||||
return escalation_time if done_time
|
||||
return check_time if !escalation_time
|
||||
return escalation_time if !check_time
|
||||
return check_time if escalation_time > check_time
|
||||
return escalation_time
|
||||
end
|
||||
end
|
58
app/models/ticket/number.rb
Normal file
58
app/models/ticket/number.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Ticket::Number < ApplicationLib
|
||||
|
||||
=begin
|
||||
|
||||
generate new ticket number
|
||||
|
||||
result = Ticket::Number.generate
|
||||
|
||||
returns
|
||||
|
||||
result = "1234556" # new ticket number
|
||||
|
||||
=end
|
||||
|
||||
def self.generate
|
||||
|
||||
# generate number
|
||||
(1..50_000).each { |i|
|
||||
number = adapter.generate
|
||||
ticket = Ticket.where( :number => number ).first
|
||||
return number if !ticket
|
||||
}
|
||||
raise "Can't generate new ticket number!"
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
check if string contrains a valid ticket number
|
||||
|
||||
result = Ticket::Number.check('some string [Ticket#123456]')
|
||||
|
||||
returns
|
||||
|
||||
result = ticket # Ticket model of ticket with matching ticket number
|
||||
|
||||
=end
|
||||
|
||||
def self.check(string)
|
||||
adapter.check(string)
|
||||
end
|
||||
|
||||
def self.adapter
|
||||
|
||||
# load backend based on config
|
||||
adapter_name = Setting.get('ticket_number')
|
||||
if !adapter_name
|
||||
raise "Missing ticket_number setting option"
|
||||
end
|
||||
adapter = self.load_adapter(adapter_name)
|
||||
if !adapter
|
||||
raise "Can't load ticket_number adapter '#{adapter_name}'"
|
||||
end
|
||||
adapter
|
||||
end
|
||||
end
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
module Ticket::Number::Date
|
||||
extend self
|
||||
|
||||
def number_generate_item
|
||||
def generate
|
||||
|
||||
# get config
|
||||
config = Setting.get('ticket_number_date')
|
||||
|
@ -64,7 +64,7 @@ module Ticket::Number::Date
|
|||
end
|
||||
return number
|
||||
end
|
||||
def number_check_item (string)
|
||||
def check(string)
|
||||
|
||||
# get config
|
||||
system_id = Setting.get('system_id') || ''
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Ticket::Number::Increment
|
||||
extend self
|
||||
|
||||
def number_generate_item
|
||||
def generate
|
||||
|
||||
# get config
|
||||
config = Setting.get('ticket_number_increment')
|
||||
|
@ -68,7 +68,7 @@ module Ticket::Number::Increment
|
|||
return number
|
||||
end
|
||||
|
||||
def number_check_item (string)
|
||||
def check(string)
|
||||
|
||||
# get config
|
||||
system_id = Setting.get('system_id') || ''
|
||||
|
|
253
app/models/ticket/overview.rb
Normal file
253
app/models/ticket/overview.rb
Normal file
|
@ -0,0 +1,253 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'overview'
|
||||
|
||||
class Ticket::Overview
|
||||
|
||||
=begin
|
||||
|
||||
all overview by user
|
||||
|
||||
result = Ticket::Overview.all(
|
||||
:current_user => User.find(123),
|
||||
)
|
||||
|
||||
returns
|
||||
|
||||
result = [overview1, overview2]
|
||||
|
||||
=end
|
||||
|
||||
def self.all (data)
|
||||
|
||||
# get customer overviews
|
||||
if data[:current_user].is_role('Customer')
|
||||
role = data[:current_user].is_role( 'Customer' )
|
||||
if data[:current_user].organization_id && data[:current_user].organization.shared
|
||||
overviews = Overview.where( :role_id => role.id, :active => true )
|
||||
else
|
||||
overviews = Overview.where( :role_id => role.id, :organization_shared => false, :active => true )
|
||||
end
|
||||
return overviews
|
||||
end
|
||||
|
||||
# get agent overviews
|
||||
role = data[:current_user].is_role( 'Agent' )
|
||||
overviews = Overview.where( :role_id => role.id, :active => true )
|
||||
return overviews
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
selected overview by user
|
||||
|
||||
result = Ticket::Overview.list(
|
||||
:current_user => User.find(123),
|
||||
:view => 'some_view_url',
|
||||
)
|
||||
|
||||
returns
|
||||
|
||||
result = {
|
||||
:tickets => tickets, # [ticket1, ticket2, ticket3]
|
||||
:tickets_count => tickets_count, # count of tickets
|
||||
:overview => overview_selected_raw, # overview attributes
|
||||
}
|
||||
|
||||
=end
|
||||
|
||||
def self.list (data)
|
||||
|
||||
overviews = self.all(data)
|
||||
|
||||
# build up attributes hash
|
||||
overview_selected = nil
|
||||
overview_selected_raw = nil
|
||||
|
||||
overviews.each { |overview|
|
||||
|
||||
# remember selected view
|
||||
if data[:view] && data[:view] == overview.link
|
||||
overview_selected = overview
|
||||
overview_selected_raw = Marshal.load( Marshal.dump(overview.attributes) )
|
||||
end
|
||||
|
||||
# replace e.g. 'current_user.id' with current_user.id
|
||||
overview.condition.each { |item, value |
|
||||
if value && value.class.to_s == 'String'
|
||||
parts = value.split( '.', 2 )
|
||||
if parts[0] && parts[1] && parts[0] == 'current_user'
|
||||
overview.condition[item] = data[:current_user][parts[1].to_sym]
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
if data[:view] && !overview_selected
|
||||
raise "No such view '#{ data[:view] }'"
|
||||
end
|
||||
|
||||
# sortby
|
||||
# prio
|
||||
# state
|
||||
# group
|
||||
# customer
|
||||
|
||||
# order
|
||||
# asc
|
||||
# desc
|
||||
|
||||
# groupby
|
||||
# prio
|
||||
# state
|
||||
# group
|
||||
# customer
|
||||
|
||||
# all = attributes[:myopenassigned]
|
||||
# all.merge( { :group_id => groups } )
|
||||
|
||||
# @tickets = Ticket.where(:group_id => groups, attributes[:myopenassigned] ).limit(params[:limit])
|
||||
# get only tickets with permissions
|
||||
if data[:current_user].is_role('Customer')
|
||||
group_ids = Group.select( 'groups.id' ).
|
||||
where( 'groups.active = ?', true ).
|
||||
map( &:id )
|
||||
else
|
||||
group_ids = Group.select( 'groups.id' ).joins(:users).
|
||||
where( 'groups_users.user_id = ?', [ data[:current_user].id ] ).
|
||||
where( 'groups.active = ?', true ).
|
||||
map( &:id )
|
||||
end
|
||||
|
||||
# overview meta for navbar
|
||||
if !overview_selected
|
||||
|
||||
# loop each overview
|
||||
result = []
|
||||
overviews.each { |overview|
|
||||
|
||||
# get count
|
||||
count = Ticket.where( :group_id => group_ids ).where( _condition( overview.condition ) ).count()
|
||||
|
||||
# get meta info
|
||||
all = {
|
||||
:name => overview.name,
|
||||
:prio => overview.prio,
|
||||
:link => overview.link,
|
||||
}
|
||||
|
||||
# push to result data
|
||||
result.push all.merge( { :count => count } )
|
||||
}
|
||||
return result
|
||||
end
|
||||
|
||||
# get result list
|
||||
if data[:array]
|
||||
order_by = overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s
|
||||
if overview_selected.group_by && !overview_selected.group_by.empty?
|
||||
order_by = overview_selected.group_by + '_id, ' + order_by
|
||||
end
|
||||
tickets = Ticket.select( 'id' ).
|
||||
where( :group_id => group_ids ).
|
||||
where( _condition( overview_selected.condition ) ).
|
||||
order( order_by ).
|
||||
limit( 500 )
|
||||
|
||||
ticket_ids = []
|
||||
tickets.each { |ticket|
|
||||
ticket_ids.push ticket.id
|
||||
}
|
||||
|
||||
tickets_count = Ticket.where( :group_id => group_ids ).
|
||||
where( _condition( overview_selected.condition ) ).
|
||||
count()
|
||||
|
||||
return {
|
||||
:ticket_list => ticket_ids,
|
||||
:tickets_count => tickets_count,
|
||||
:overview => overview_selected_raw,
|
||||
}
|
||||
end
|
||||
|
||||
# get tickets for overview
|
||||
data[:start_page] ||= 1
|
||||
tickets = Ticket.where( :group_id => group_ids ).
|
||||
where( _condition( overview_selected.condition ) ).
|
||||
order( overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s )#.
|
||||
# limit( overview_selected.view[ data[:view_mode].to_sym ][:per_page] ).
|
||||
# offset( overview_selected.view[ data[:view_mode].to_sym ][:per_page].to_i * ( data[:start_page].to_i - 1 ) )
|
||||
|
||||
tickets_count = Ticket.where( :group_id => group_ids ).
|
||||
where( _condition( overview_selected.condition ) ).
|
||||
count()
|
||||
|
||||
return {
|
||||
:tickets => tickets,
|
||||
:tickets_count => tickets_count,
|
||||
:overview => overview_selected_raw,
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
def self._condition(condition)
|
||||
sql = ''
|
||||
bind = [nil]
|
||||
condition.each {|key, value|
|
||||
if sql != ''
|
||||
sql += ' AND '
|
||||
end
|
||||
if value.class == Array
|
||||
sql += " #{key} IN (?)"
|
||||
bind.push value
|
||||
elsif value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
|
||||
time = Time.now
|
||||
if value['area'] == 'minute'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60
|
||||
else
|
||||
time += value['count'].to_i * 60
|
||||
end
|
||||
elsif value['area'] == 'hour'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60 * 60
|
||||
else
|
||||
time += value['count'].to_i * 60 * 60
|
||||
end
|
||||
elsif value['area'] == 'day'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60 * 60 * 24
|
||||
else
|
||||
time += value['count'].to_i * 60 * 60 * 24
|
||||
end
|
||||
elsif value['area'] == 'month'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60 * 60 * 24 * 31
|
||||
else
|
||||
time += value['count'].to_i * 60 * 60 * 24 * 31
|
||||
end
|
||||
elsif value['area'] == 'year'
|
||||
if value['direction'] == 'last'
|
||||
time -= value['count'].to_i * 60 * 60 * 24 * 365
|
||||
else
|
||||
time += value['count'].to_i * 60 * 60 * 24 * 365
|
||||
end
|
||||
end
|
||||
if value['direction'] == 'last'
|
||||
sql += " #{key} > ?"
|
||||
bind.push time
|
||||
else
|
||||
sql += " #{key} < ?"
|
||||
bind.push time
|
||||
end
|
||||
else
|
||||
sql += " #{key} = ?"
|
||||
bind.push value
|
||||
end
|
||||
}
|
||||
bind[0] = sql
|
||||
return bind
|
||||
end
|
||||
|
||||
end
|
47
app/models/ticket/permission.rb
Normal file
47
app/models/ticket/permission.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Ticket::Permission
|
||||
|
||||
=begin
|
||||
|
||||
check if user has access to ticket
|
||||
|
||||
ticket = Ticket.find(123)
|
||||
result = ticket.permission( :current_user => User.find(123) )
|
||||
|
||||
returns
|
||||
|
||||
result = true|false
|
||||
|
||||
=end
|
||||
|
||||
def permission (data)
|
||||
|
||||
# check customer
|
||||
if data[:current_user].is_role('Customer')
|
||||
|
||||
# access ok if its own ticket
|
||||
return true if self.customer_id == data[:current_user].id
|
||||
|
||||
# access ok if its organization ticket
|
||||
if data[:current_user].organization_id && self.organization_id
|
||||
return true if self.organization_id == data[:current_user].organization_id
|
||||
end
|
||||
|
||||
# no access
|
||||
return false
|
||||
end
|
||||
|
||||
# check agent
|
||||
|
||||
# access if requestor is owner
|
||||
return true if self.owner_id == data[:current_user].id
|
||||
|
||||
# access if requestor is in group
|
||||
data[:current_user].groups.each {|group|
|
||||
return true if self.group.id == group.id
|
||||
}
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
170
app/models/ticket/screen_options.rb
Normal file
170
app/models/ticket/screen_options.rb
Normal file
|
@ -0,0 +1,170 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Ticket::ScreenOptions
|
||||
|
||||
=begin
|
||||
|
||||
list of active agents
|
||||
|
||||
result = Ticket::ScreenOptions.agents()
|
||||
|
||||
returns
|
||||
|
||||
result = [user1, user2]
|
||||
|
||||
=end
|
||||
|
||||
def self.agents
|
||||
User.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
list attributes
|
||||
|
||||
result = Ticket::ScreenOptions.attributes_to_change(
|
||||
:ticket_id => 123,
|
||||
:article_id => 123,
|
||||
|
||||
:ticket => ticket_model,
|
||||
)
|
||||
|
||||
returns
|
||||
|
||||
result = {
|
||||
:ticket_article_type_id => ticket_article_type_ids,
|
||||
:ticket_state_id => ticket_state_ids,
|
||||
:ticket_priority_id => ticket_priority_ids,
|
||||
:owner_id => owner_ids,
|
||||
:group_id => group_ids,
|
||||
:group_id__owner_id => groups_users,
|
||||
}
|
||||
|
||||
=end
|
||||
|
||||
def self.attributes_to_change(params)
|
||||
if params[:ticket_id]
|
||||
params[:ticket] = self.find( params[:ticket_id] )
|
||||
end
|
||||
if params[:article_id]
|
||||
params[:article] = self.find( params[:article_id] )
|
||||
end
|
||||
|
||||
# get ticket states
|
||||
ticket_state_ids = []
|
||||
if params[:ticket]
|
||||
ticket_state_type = params[:ticket].ticket_state.state_type
|
||||
end
|
||||
ticket_state_types = ['open', 'closed', 'pending action', 'pending reminder']
|
||||
if ticket_state_type && !ticket_state_types.include?(ticket_state_type.name)
|
||||
ticket_state_ids.push params[:ticket].ticket_state.id
|
||||
end
|
||||
ticket_state_types.each {|type|
|
||||
ticket_state_type = Ticket::StateType.where( :name => type ).first
|
||||
if ticket_state_type
|
||||
ticket_state_type.states.each {|ticket_state|
|
||||
ticket_state_ids.push ticket_state.id
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
# get owner
|
||||
owner_ids = []
|
||||
if params[:ticket]
|
||||
params[:ticket].agent_of_group.each { |user|
|
||||
owner_ids.push user.id
|
||||
}
|
||||
end
|
||||
|
||||
# get group
|
||||
group_ids = []
|
||||
Group.where( :active => true ).each { |group|
|
||||
group_ids.push group.id
|
||||
}
|
||||
|
||||
# get group / user relations
|
||||
agents = {}
|
||||
Ticket::ScreenOptions.agents.each { |user|
|
||||
agents[ user.id ] = 1
|
||||
}
|
||||
groups_users = {}
|
||||
group_ids.each {|group_id|
|
||||
groups_users[ group_id ] = []
|
||||
Group.find( group_id ).users.each {|user|
|
||||
next if !agents[ user.id ]
|
||||
groups_users[ group_id ].push user.id
|
||||
}
|
||||
}
|
||||
|
||||
# get priorities
|
||||
ticket_priority_ids = []
|
||||
Ticket::Priority.where( :active => true ).each { |priority|
|
||||
ticket_priority_ids.push priority.id
|
||||
}
|
||||
|
||||
ticket_article_type_ids = []
|
||||
if params[:ticket]
|
||||
ticket_article_types = ['note', 'phone']
|
||||
if params[:ticket].group.email_address_id
|
||||
ticket_article_types.push 'email'
|
||||
end
|
||||
ticket_article_types.each {|ticket_article_type_name|
|
||||
ticket_article_type = Ticket::Article::Type.lookup( :name => ticket_article_type_name )
|
||||
if ticket_article_type
|
||||
ticket_article_type_ids.push ticket_article_type.id
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
:ticket_article_type_id => ticket_article_type_ids,
|
||||
:ticket_state_id => ticket_state_ids,
|
||||
:ticket_priority_id => ticket_priority_ids,
|
||||
:owner_id => owner_ids,
|
||||
:group_id => group_ids,
|
||||
:group_id__owner_id => groups_users,
|
||||
}
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
list tickets by customer groupd in state categroie open and closed
|
||||
|
||||
result = Ticket::ScreenOptions.list_by_customer(
|
||||
:customer_id => 123,
|
||||
:limit => 15, # optional, default 15
|
||||
)
|
||||
|
||||
returns
|
||||
|
||||
result = {
|
||||
:open => tickets_open,
|
||||
:closed => tickets_closed,
|
||||
}
|
||||
|
||||
=end
|
||||
|
||||
def self.list_by_customer(data)
|
||||
|
||||
# get closed/open states
|
||||
ticket_state_list_open = Ticket::State.by_category( 'open' )
|
||||
ticket_state_list_closed = Ticket::State.by_category( 'closed' )
|
||||
|
||||
# get tickets
|
||||
tickets_open = Ticket.where(
|
||||
:customer_id => data[:customer_id],
|
||||
:ticket_state_id => ticket_state_list_open
|
||||
).limit( data[:limit] || 15 ).order('created_at DESC')
|
||||
|
||||
tickets_closed = Ticket.where(
|
||||
:customer_id => data[:customer_id],
|
||||
:ticket_state_id => ticket_state_list_closed
|
||||
).limit( data[:limit] || 15 ).order('created_at DESC')
|
||||
|
||||
return {
|
||||
:open => tickets_open,
|
||||
:closed => tickets_closed,
|
||||
}
|
||||
end
|
||||
|
||||
end
|
62
app/models/ticket/search.rb
Normal file
62
app/models/ticket/search.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Ticket::Search
|
||||
|
||||
=begin
|
||||
|
||||
search tickets
|
||||
|
||||
result = Ticket.search(
|
||||
:current_user => User.find(123),
|
||||
:query => 'search something',
|
||||
:limit => 15,
|
||||
)
|
||||
|
||||
returns
|
||||
|
||||
result = [ticket_model1, ticket_model2]
|
||||
|
||||
=end
|
||||
|
||||
def search (params)
|
||||
|
||||
# get params
|
||||
query = params[:query]
|
||||
limit = params[:limit] || 12
|
||||
current_user = params[:current_user]
|
||||
|
||||
conditions = []
|
||||
if current_user.is_role('Agent')
|
||||
group_ids = Group.select( 'groups.id' ).joins(:users).
|
||||
where( 'groups_users.user_id = ?', current_user.id ).
|
||||
where( 'groups.active = ?', true ).
|
||||
map( &:id )
|
||||
conditions = [ 'group_id IN (?)', group_ids ]
|
||||
else
|
||||
if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
|
||||
conditions = [ 'customer_id = ?', current_user.id ]
|
||||
else
|
||||
conditions = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
|
||||
end
|
||||
end
|
||||
|
||||
# do query
|
||||
tickets_all = Ticket.select('DISTINCT(tickets.id)').
|
||||
where(conditions).
|
||||
where( '( `tickets`.`title` LIKE ? OR `tickets`.`number` LIKE ? OR `ticket_articles`.`body` LIKE ? OR `ticket_articles`.`from` LIKE ? OR `ticket_articles`.`to` LIKE ? OR `ticket_articles`.`subject` LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" ).
|
||||
joins(:articles).
|
||||
limit(limit).
|
||||
order('`tickets`.`created_at` DESC')
|
||||
|
||||
# build result list
|
||||
tickets = []
|
||||
users = {}
|
||||
tickets_all.each do |ticket|
|
||||
ticket_tmp = Ticket.lookup( :id => ticket.id )
|
||||
tickets.push ticket_tmp
|
||||
end
|
||||
|
||||
return tickets
|
||||
end
|
||||
|
||||
end
|
78
app/models/ticket/subject.rb
Normal file
78
app/models/ticket/subject.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Ticket::Subject
|
||||
|
||||
=begin
|
||||
|
||||
build new subject with ticket number in there
|
||||
|
||||
ticket = Ticket.find(123)
|
||||
result = ticket.subject_build('some subject')
|
||||
|
||||
returns
|
||||
|
||||
result = "[Ticket#1234567] some subject"
|
||||
|
||||
=end
|
||||
|
||||
def subject_build (subject)
|
||||
|
||||
# clena subject
|
||||
subject = self.subject_clean(subject)
|
||||
|
||||
ticket_hook = Setting.get('ticket_hook')
|
||||
ticket_hook_divider = Setting.get('ticket_hook_divider')
|
||||
|
||||
# none position
|
||||
if Setting.get('ticket_hook_position') == 'none'
|
||||
return subject
|
||||
end
|
||||
|
||||
# right position
|
||||
if Setting.get('ticket_hook_position') == 'right'
|
||||
return subject + " [#{ticket_hook}#{ticket_hook_divider}#{self.number}] "
|
||||
end
|
||||
|
||||
# left position
|
||||
return "[#{ticket_hook}#{ticket_hook_divider}#{self.number}] " + subject
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
clean subject remove ticket number and other not needed chars
|
||||
|
||||
ticket = Ticket.find(123)
|
||||
result = ticket.subject_clean('[Ticket#1234567] some subject')
|
||||
|
||||
returns
|
||||
|
||||
result = "some subject"
|
||||
|
||||
=end
|
||||
|
||||
def subject_clean (subject)
|
||||
ticket_hook = Setting.get('ticket_hook')
|
||||
ticket_hook_divider = Setting.get('ticket_hook_divider')
|
||||
ticket_subject_size = Setting.get('ticket_subject_size')
|
||||
|
||||
# remove all possible ticket hook formats with []
|
||||
subject = subject.gsub /\[#{ticket_hook}: #{self.number}\](\s+?|)/, ''
|
||||
subject = subject.gsub /\[#{ticket_hook}:#{self.number}\](\s+?|)/, ''
|
||||
subject = subject.gsub /\[#{ticket_hook}#{ticket_hook_divider}#{self.number}\](\s+?|)/, ''
|
||||
|
||||
# remove all possible ticket hook formats without []
|
||||
subject = subject.gsub /#{ticket_hook}: #{self.number}(\s+?|)/, ''
|
||||
subject = subject.gsub /#{ticket_hook}:#{self.number}(\s+?|)/, ''
|
||||
subject = subject.gsub /#{ticket_hook}#{ticket_hook_divider}#{self.number}(\s+?|)/, ''
|
||||
|
||||
# remove leading "..:\s" and "..[\d+]:\s" e. g. "Re: " or "Re[5]: "
|
||||
subject = subject.gsub /^(..(\[\d+\])?:\s)+/, ''
|
||||
|
||||
# resize subject based on config
|
||||
if subject.length > ticket_subject_size.to_i
|
||||
subject = subject[ 0, ticket_subject_size.to_i ] + '[...]'
|
||||
end
|
||||
|
||||
return subject
|
||||
end
|
||||
end
|
|
@ -19,6 +19,19 @@ class User < ApplicationModel
|
|||
|
||||
store :preferences
|
||||
|
||||
=begin
|
||||
|
||||
fullname of user
|
||||
|
||||
user = User.find(123)
|
||||
result = user.fulename
|
||||
|
||||
returns
|
||||
|
||||
result = "Bob Smith"
|
||||
|
||||
=end
|
||||
|
||||
def fullname
|
||||
fullname = ''
|
||||
if self.firstname
|
||||
|
@ -33,6 +46,19 @@ class User < ApplicationModel
|
|||
return fullname
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
check if user is in role
|
||||
|
||||
user = User.find(123)
|
||||
result = user.is_role('Customer')
|
||||
|
||||
returns
|
||||
|
||||
result = true|false
|
||||
|
||||
=end
|
||||
|
||||
def is_role( role_name )
|
||||
self.roles.each { |role|
|
||||
return role if role.name == role_name
|
||||
|
@ -40,6 +66,18 @@ class User < ApplicationModel
|
|||
return false
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
authenticate user
|
||||
|
||||
result = User.authenticate(username, password)
|
||||
|
||||
returns
|
||||
|
||||
result = user_model # user model if authentication was successfully
|
||||
|
||||
=end
|
||||
|
||||
def self.authenticate( username, password )
|
||||
|
||||
# do not authenticate with nothing
|
||||
|
@ -60,98 +98,52 @@ class User < ApplicationModel
|
|||
return false
|
||||
end
|
||||
|
||||
# use auth backends
|
||||
config = [
|
||||
{
|
||||
:adapter => 'internal',
|
||||
},
|
||||
{
|
||||
:adapter => 'test',
|
||||
},
|
||||
]
|
||||
Setting.where( :area => 'Security::Authentication' ).each {|setting|
|
||||
if setting.state[:value]
|
||||
config.push setting.state[:value]
|
||||
end
|
||||
}
|
||||
|
||||
# try to login against configure auth backends
|
||||
user_auth = nil
|
||||
config.each {|config_item|
|
||||
next if !config_item[:adapter]
|
||||
next if config_item.class == TrueClass
|
||||
file = "auth/#{config_item[:adapter]}"
|
||||
require file
|
||||
user_auth = Auth.const_get("#{config_item[:adapter].to_s.upcase}").check( username, password, config_item, user )
|
||||
|
||||
# auth ok
|
||||
if user_auth
|
||||
|
||||
# remember last login date
|
||||
user_auth.update_last_login
|
||||
|
||||
# reset login failed
|
||||
user_auth.login_failed = 0
|
||||
user_auth.save
|
||||
|
||||
return user_auth
|
||||
end
|
||||
}
|
||||
user_auth = Auth.check( username, password, user )
|
||||
|
||||
# set login failed +1
|
||||
if !user_auth && user
|
||||
sleep 1
|
||||
user.login_failed = user.login_failed + 1
|
||||
user.save
|
||||
end
|
||||
|
||||
# auth failed
|
||||
sleep 1
|
||||
# auth ok
|
||||
return user_auth
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
authenticate user agains sso
|
||||
|
||||
result = User.sso(sso_params)
|
||||
|
||||
returns
|
||||
|
||||
result = user_model # user model if authentication was successfully
|
||||
|
||||
=end
|
||||
|
||||
def self.sso(params)
|
||||
|
||||
# use auth backends
|
||||
config = [
|
||||
{
|
||||
:adapter => 'env',
|
||||
},
|
||||
{
|
||||
:adapter => 'otrs',
|
||||
},
|
||||
]
|
||||
# Setting.where( :area => 'Security::Authentication' ).each {|setting|
|
||||
# if setting.state[:value]
|
||||
# config.push setting.state[:value]
|
||||
# end
|
||||
# }
|
||||
|
||||
# try to login against configure auth backends
|
||||
user_auth = nil
|
||||
config.each {|config_item|
|
||||
next if !config_item[:adapter]
|
||||
next if config_item.class == TrueClass
|
||||
file = "sso/#{config_item[:adapter]}"
|
||||
require file
|
||||
user_auth = SSO.const_get("#{config_item[:adapter].to_s.upcase}").check( params, config_item )
|
||||
user_auth = Sso.check( params, user )
|
||||
return if !user_auth
|
||||
|
||||
# auth ok
|
||||
if user_auth
|
||||
|
||||
# remember last login date
|
||||
user_auth.update_last_login
|
||||
|
||||
# reset login failed
|
||||
user_auth.login_failed = 0
|
||||
user_auth.save
|
||||
|
||||
return user_auth
|
||||
end
|
||||
}
|
||||
|
||||
return false
|
||||
return user_auth
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
create user from from omni auth hash
|
||||
|
||||
result = User.create_from_hash!(hash)
|
||||
|
||||
returns
|
||||
|
||||
result = user_model # user model if create was successfully
|
||||
|
||||
=end
|
||||
|
||||
def self.create_from_hash!(hash)
|
||||
url = ''
|
||||
if hash['info']['urls'] then
|
||||
|
@ -173,6 +165,18 @@ class User < ApplicationModel
|
|||
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
send reset password email with token to user
|
||||
|
||||
result = User.password_reset_send(username)
|
||||
|
||||
returns
|
||||
|
||||
result = true|false
|
||||
|
||||
=end
|
||||
|
||||
def self.password_reset_send(username)
|
||||
return if !username || username == ''
|
||||
|
||||
|
@ -230,7 +234,18 @@ class User < ApplicationModel
|
|||
return true
|
||||
end
|
||||
|
||||
# check token
|
||||
=begin
|
||||
|
||||
check reset password token
|
||||
|
||||
result = User.password_reset_check(token)
|
||||
|
||||
returns
|
||||
|
||||
result = user_model # user_model if token was verified
|
||||
|
||||
=end
|
||||
|
||||
def self.password_reset_check(token)
|
||||
user = Token.check( :action => 'PasswordReset', :name => token )
|
||||
|
||||
|
@ -242,6 +257,18 @@ class User < ApplicationModel
|
|||
return user
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
reset reset password with token and set new password
|
||||
|
||||
result = User.password_reset_via_token(token,password)
|
||||
|
||||
returns
|
||||
|
||||
result = user_model # user_model if token was verified
|
||||
|
||||
=end
|
||||
|
||||
def self.password_reset_via_token(token,password)
|
||||
|
||||
# check token
|
||||
|
@ -256,6 +283,22 @@ class User < ApplicationModel
|
|||
return user
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
search user
|
||||
|
||||
result = User.search(
|
||||
:query => 'some search term'
|
||||
:limit => 15,
|
||||
:current_user => user_model,
|
||||
)
|
||||
|
||||
returns
|
||||
|
||||
result = [user_model1, user_model2, ...]
|
||||
|
||||
=end
|
||||
|
||||
def self.search(params)
|
||||
|
||||
# get params
|
||||
|
@ -378,6 +421,19 @@ class User < ApplicationModel
|
|||
return user
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
update last login date (is automatically done by auth and sso backend)
|
||||
|
||||
user = User.find(123)
|
||||
result = user.update_last_login
|
||||
|
||||
returns
|
||||
|
||||
result = new_user_model
|
||||
|
||||
=end
|
||||
|
||||
def update_last_login
|
||||
self.last_login = Time.now
|
||||
self.save
|
||||
|
|
|
@ -6,8 +6,3 @@
|
|||
<div id="splash">
|
||||
<div class="logo">booting...</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
jQuery(function(){
|
||||
new App.Run();
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
<head>
|
||||
<title><%= Setting.get('product_name') %></title>
|
||||
<%= stylesheet_link_tag "application" %>
|
||||
<% if Rails.configuration.assets.debug %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<% else %>
|
||||
<%= javascript_include_tag "application", :defer => 'defer' %>
|
||||
<% end %>
|
||||
<%= csrf_meta_tags %>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
Zammad::Application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb
|
||||
|
||||
# The test environment is used exclusively to run your application's
|
||||
# test suite. You never need to work with it otherwise. Remember that
|
||||
# your test database is "scratch space" for the test suite and is wiped
|
||||
# and recreated between test runs. Don't rely on the data there!
|
||||
config.cache_classes = true
|
||||
|
||||
# Configure static asset server for tests with Cache-Control for performance
|
||||
config.serve_static_assets = true
|
||||
config.static_cache_control = "public, max-age=3600"
|
||||
|
||||
config.assets.compress = false
|
||||
config.assets.compile = true
|
||||
config.assets.digest = true
|
||||
|
||||
# Log error messages when you accidentally call methods on nil
|
||||
config.whiny_nils = true
|
||||
|
||||
# Show full error reports and disable caching
|
||||
config.consider_all_requests_local = true
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
# Raise exceptions instead of rendering exception templates
|
||||
config.action_dispatch.show_exceptions = false
|
||||
|
||||
# Tell Action Mailer not to deliver emails to the real world.
|
||||
# The :test delivery method accumulates sent emails in the
|
||||
# ActionMailer::Base.deliveries array.
|
||||
config.action_mailer.delivery_method = :test
|
||||
|
||||
# Raise exception on mass assignment protection for Active Record models
|
||||
config.active_record.mass_assignment_sanitizer = :strict
|
||||
|
||||
# Print deprecation notices to the stderr
|
||||
config.active_support.deprecation = :stderr
|
||||
|
||||
# Disable request forgery protection in test environment
|
||||
config.action_controller.allow_forgery_protection = false
|
||||
|
||||
# autoload on
|
||||
config.dependency_loading = true
|
||||
|
||||
end
|
52
db/migrate/20130815000001_update_geo2.rb
Normal file
52
db/migrate/20130815000001_update_geo2.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
class UpdateGeo2 < ActiveRecord::Migration
|
||||
def up
|
||||
Setting.where( :name => 'geo_backend' ).destroy_all
|
||||
Setting.create_if_not_exists(
|
||||
:title => 'Geo Location Backend',
|
||||
:name => 'geo_location_backend',
|
||||
:area => 'System::Geo',
|
||||
:description => 'Defines the backend for geo location lookups.',
|
||||
:options => {
|
||||
:form => [
|
||||
{
|
||||
:display => '',
|
||||
:null => true,
|
||||
:name => 'geo_location_backend',
|
||||
:tag => 'select',
|
||||
:options => {
|
||||
'' => '-',
|
||||
'GeoLocation::Gmaps' => 'Google Maps',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
:state => 'GeoLocation::Gmaps',
|
||||
:frontend => false
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
:title => 'Geo IP Backend',
|
||||
:name => 'geo_ip_backend',
|
||||
:area => 'System::Geo',
|
||||
:description => 'Defines the backend for geo ip lookups.',
|
||||
:options => {
|
||||
:form => [
|
||||
{
|
||||
:display => '',
|
||||
:null => true,
|
||||
:name => 'geo_ip_backend',
|
||||
:tag => 'select',
|
||||
:options => {
|
||||
'' => '-',
|
||||
'GeoIp::Freegeoip' => 'freegeoip.net',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
:state => 'GeoIp::Freegeoip',
|
||||
:frontend => false
|
||||
)
|
||||
end
|
||||
def down
|
||||
end
|
||||
end
|
||||
|
33
db/migrate/20130815000002_update_ticket_number.rb
Normal file
33
db/migrate/20130815000002_update_ticket_number.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
class UpdateTicketNumber < ActiveRecord::Migration
|
||||
def up
|
||||
Setting.create_or_update(
|
||||
:title => 'Ticket Number Format',
|
||||
:name => 'ticket_number',
|
||||
:area => 'Ticket::Number',
|
||||
:description => 'Selects the ticket number generator module. "Increment" increments the ticket
|
||||
number, the SystemID and the counter are used with SystemID.Counter format (e.g. 1010138, 1010139).
|
||||
With "Date" the ticket numbers will be generated by the current date, the SystemID and the counter.
|
||||
The format looks like Year.Month.Day.SystemID.counter (e.g. 201206231010138, 201206231010139).
|
||||
With param "Checksum => true" the counter will be appended as checksum to the string. The format
|
||||
looks like SystemID.Counter.CheckSum (e. g. 10101384, 10101392) or Year.Month.Day.SystemID.Counter.CheckSum (e.g. 2012070110101520, 2012070110101535).',
|
||||
:options => {
|
||||
:form => [
|
||||
{
|
||||
:display => '',
|
||||
:null => true,
|
||||
:name => 'ticket_number',
|
||||
:tag => 'select',
|
||||
:options => {
|
||||
'Ticket::Number::Increment' => 'Increment (SystemID.Counter)',
|
||||
'Ticket::Number::Date' => 'Date (Year.Month.Day.SystemID.Counter)',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
:state => 'Ticket::Number::Increment',
|
||||
:frontend => false
|
||||
)
|
||||
end
|
||||
def down
|
||||
end
|
||||
end
|
42
db/seeds.rb
42
db/seeds.rb
|
@ -135,7 +135,7 @@ Setting.create_if_not_exists(
|
|||
)
|
||||
Setting.create_if_not_exists(
|
||||
:title => 'Geo Location Backend',
|
||||
:name => 'geo_backend',
|
||||
:name => 'geo_location_backend',
|
||||
:area => 'System::Geo',
|
||||
:description => 'Defines the backend for geo location lookups.',
|
||||
:options => {
|
||||
|
@ -143,17 +143,39 @@ Setting.create_if_not_exists(
|
|||
{
|
||||
:display => '',
|
||||
:null => true,
|
||||
:name => 'geo_backend',
|
||||
:name => 'geo_location_backend',
|
||||
:tag => 'select',
|
||||
:options => {
|
||||
'' => '-',
|
||||
'Gmaps' => 'Google Maps',
|
||||
'GeoLocation::Gmaps' => 'Google Maps',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
:state => 'Gmaps',
|
||||
:frontend => true
|
||||
:state => 'GeoLocation::Gmaps',
|
||||
:frontend => false
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
:title => 'Geo IP Backend',
|
||||
:name => 'geo_ip_backend',
|
||||
:area => 'System::Geo',
|
||||
:description => 'Defines the backend for geo ip lookups.',
|
||||
:options => {
|
||||
:form => [
|
||||
{
|
||||
:display => '',
|
||||
:null => true,
|
||||
:name => 'geo_ip_backend',
|
||||
:tag => 'select',
|
||||
:options => {
|
||||
'' => '-',
|
||||
'GeoIp::Freegeoip' => 'freegeoip.net',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
:state => 'GeoIp::Freegeoip',
|
||||
:frontend => false
|
||||
)
|
||||
|
||||
Setting.create_if_not_exists(
|
||||
|
@ -228,7 +250,7 @@ Setting.create_if_not_exists(
|
|||
:area => 'Security::Authentication',
|
||||
:description => 'Enables user authentication via OTRS.',
|
||||
:state => {
|
||||
:adapter => 'otrs',
|
||||
:adapter => 'Auth::Otrs',
|
||||
:required_group_ro => 'stats',
|
||||
:group_rw_role_map => {
|
||||
'admin' => 'Admin',
|
||||
|
@ -249,7 +271,7 @@ Setting.create_if_not_exists(
|
|||
:area => 'Security::Authentication',
|
||||
:description => 'Enables user authentication via LDAP.',
|
||||
:state => {
|
||||
:adapter => 'ldap',
|
||||
:adapter => 'Auth::Ldap',
|
||||
:host => 'localhost',
|
||||
:port => 389,
|
||||
:bind_dn => 'cn=Manager,dc=example,dc=org',
|
||||
|
@ -690,13 +712,13 @@ Setting.create_if_not_exists(
|
|||
:name => 'ticket_number',
|
||||
:tag => 'select',
|
||||
:options => {
|
||||
'increment' => 'Increment (SystemID.Counter)',
|
||||
'date' => 'Date (Year.Month.Day.SystemID.Counter)',
|
||||
'Ticket::Number::Increment' => 'Increment (SystemID.Counter)',
|
||||
'Ticket::Number::Date' => 'Date (Year.Month.Day.SystemID.Counter)',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
:state => 'increment',
|
||||
:state => 'Ticket::Number::Increment',
|
||||
:frontend => false
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
|
|
50
lib/application_lib.rb
Normal file
50
lib/application_lib.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
class ApplicationLib
|
||||
|
||||
=begin
|
||||
|
||||
load adapter based on setting option
|
||||
|
||||
result = self.load_adapter_by_setting( 'some_setting_with_class_name' )
|
||||
|
||||
returns
|
||||
|
||||
result = Some::Classname
|
||||
|
||||
=end
|
||||
|
||||
def self.load_adapter_by_setting(setting)
|
||||
adapter = Setting.get( setting )
|
||||
return if !adapter
|
||||
|
||||
# load backend
|
||||
self.load_adapter(adapter)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
load adapter
|
||||
|
||||
result = self.load_adapter( 'Some::Classname' )
|
||||
|
||||
returns
|
||||
|
||||
result = Some::Classname
|
||||
|
||||
=end
|
||||
|
||||
def self.load_adapter(adapter)
|
||||
|
||||
# load adapter
|
||||
|
||||
# will only work on ruby 2.0
|
||||
# Object.const_get(adapter)
|
||||
|
||||
# will work on ruby 1.9 and 2.0
|
||||
# adapter.split('::').inject(Object) do |mod, class_name|
|
||||
# mod.const_get(class_name)
|
||||
# end
|
||||
|
||||
# will work with active_support
|
||||
adapter.constantize
|
||||
end
|
||||
end
|
63
lib/auth.rb
Normal file
63
lib/auth.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Auth < ApplicationLib
|
||||
|
||||
=begin
|
||||
|
||||
authenticate user via username and password
|
||||
|
||||
result = Auth.check( username, password, user )
|
||||
|
||||
returns
|
||||
|
||||
result = user_model # if authentication was successfully
|
||||
|
||||
=end
|
||||
|
||||
def self.check(username, password, user)
|
||||
|
||||
# use std. auth backends
|
||||
config = [
|
||||
{
|
||||
:adapter => 'Auth::Internal',
|
||||
},
|
||||
{
|
||||
:adapter => 'Auth::Test',
|
||||
},
|
||||
]
|
||||
|
||||
# added configured backends
|
||||
Setting.where( :area => 'Security::Authentication' ).each {|setting|
|
||||
if setting.state[:value]
|
||||
config.push setting.state[:value]
|
||||
end
|
||||
}
|
||||
|
||||
# try to login against configure auth backends
|
||||
user_auth = nil
|
||||
config.each {|config_item|
|
||||
next if !config_item[:adapter]
|
||||
|
||||
# load backend
|
||||
backend = self.load_adapter( config_item[:adapter] )
|
||||
return if !backend
|
||||
|
||||
user_auth = backend.check( username, password, config_item, user )
|
||||
|
||||
# auth ok
|
||||
if user_auth
|
||||
|
||||
# remember last login date
|
||||
user_auth.update_last_login
|
||||
|
||||
# reset login failed
|
||||
user_auth.login_failed = 0
|
||||
user_auth.save
|
||||
|
||||
return user_auth
|
||||
end
|
||||
}
|
||||
return
|
||||
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
module Auth
|
||||
end
|
||||
module Auth::INTERNAL
|
||||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Auth::Internal
|
||||
def self.check( username, password, config, user )
|
||||
|
||||
# return if no user exists
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'net/ldap'
|
||||
|
||||
module Auth
|
||||
end
|
||||
module Auth::LDAP
|
||||
module Auth::Ldap
|
||||
def self.check( username, password, config, user )
|
||||
|
||||
scope = Net::LDAP::SearchScope_WholeSubtree
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'import/otrs'
|
||||
module Auth
|
||||
end
|
||||
module Auth::OTRS
|
||||
|
||||
module Auth::Otrs
|
||||
def self.check( username, password, config, user )
|
||||
|
||||
endpoint = Setting.get('import_otrs_endpoint')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Auth
|
||||
end
|
||||
module Auth::TEST
|
||||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Auth::Test
|
||||
def self.check( username, password, config, user )
|
||||
|
||||
# development systems
|
||||
|
|
38
lib/geo_ip.rb
Normal file
38
lib/geo_ip.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class GeoIp < ApplicationLib
|
||||
|
||||
=begin
|
||||
|
||||
lookup location based on ip or hostname
|
||||
|
||||
result = GeoIp.location( '172.0.0.1' )
|
||||
|
||||
returns
|
||||
|
||||
result = {
|
||||
"ip" => "172.0.0.1"
|
||||
"country_code" => "DE",
|
||||
"country_name" => "Germany",
|
||||
"region_code" => "05",
|
||||
"region_name" => "Hessen",
|
||||
"city" => "Frankfurt Am Main"
|
||||
"zipcode" => "12345",
|
||||
"latitude" => 50.1167,
|
||||
"longitude" => 8.6833,
|
||||
"metro_code" => "",
|
||||
"areacode" => ""
|
||||
}
|
||||
|
||||
=end
|
||||
|
||||
def self.location(address)
|
||||
|
||||
# load backend
|
||||
backend = self.load_adapter_by_setting( 'geo_ip_backend' )
|
||||
return if !backend
|
||||
|
||||
# db lookup
|
||||
backend.location(address)
|
||||
end
|
||||
end
|
|
@ -1,11 +1,13 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'faraday'
|
||||
require 'cache'
|
||||
|
||||
module Geoip
|
||||
class GeoIp::Freegeoip
|
||||
def self.location(address)
|
||||
|
||||
# check cache
|
||||
cache_key = "geoip::#{address}"
|
||||
cache_key = "freegeoip::#{address}"
|
||||
cache = Cache.get( cache_key )
|
||||
return cache if cache
|
||||
|
48
lib/geo_location.rb
Normal file
48
lib/geo_location.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class GeoLocation < ApplicationLib
|
||||
|
||||
=begin
|
||||
|
||||
lookup lat and lng for address
|
||||
|
||||
result = GeoLocation.geocode( 'Marienstrasse 13, 10117 Berlin' )
|
||||
|
||||
returns
|
||||
|
||||
result = [ 4.21312, 1.3123 ]
|
||||
|
||||
=end
|
||||
|
||||
def self.geocode(address)
|
||||
|
||||
# load backend
|
||||
backend = self.load_adapter_by_setting( 'geo_location_backend' )
|
||||
return if !backend
|
||||
|
||||
# db lookup
|
||||
backend.geocode(address)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
lookup address for lat and lng
|
||||
|
||||
result = GeoLocation.reverse_geocode( 4.21312, 1.3123 )
|
||||
|
||||
returns
|
||||
|
||||
result = 'some address'
|
||||
|
||||
=end
|
||||
|
||||
def self.reverse_geocode(lat,lng)
|
||||
|
||||
# load backend
|
||||
backend = self.load_adapter_by_setting( 'geo_location_backend' )
|
||||
return if !backend
|
||||
|
||||
# db lookup
|
||||
backend.reverse_geocode(lat,lng)
|
||||
end
|
||||
end
|
|
@ -1,8 +1,11 @@
|
|||
module Gmaps
|
||||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class GeoLocation::Gmaps
|
||||
|
||||
def self.geocode(address)
|
||||
url = "http://maps.googleapis.com/maps/api/geocode/json?address=#{CGI::escape address}&sensor=true"
|
||||
response = Net::HTTP.get_response( URI.parse(url) )
|
||||
return if response.code.to_s != '200'
|
||||
return if ! response.kind_of? Net::HTTPSuccess
|
||||
|
||||
result = JSON.parse( response.body )
|
||||
|
||||
|
@ -14,7 +17,7 @@ module Gmaps
|
|||
def self.reverse_geocode(lat,lng)
|
||||
url = "http://maps.googleapis.com/maps/api/geocode/json?latlng=#{lat},#{lng}&sensor=true"
|
||||
response = Net::HTTP.get_response( URI.parse(url) )
|
||||
return if response.code.to_s != '200'
|
||||
return if ! response.kind_of? Net::HTTPSuccess
|
||||
|
||||
result = JSON.parse( response.body )
|
||||
|
|
@ -9,11 +9,11 @@ module RSS
|
|||
response = Net::HTTP.get_response( URI.parse(url) )
|
||||
|
||||
# check if redirect is needed
|
||||
if response.code.to_s == '301' || response.code.to_s == '302'
|
||||
if response.kind_of? Net::HTTPRedirection
|
||||
url = response.header['location']
|
||||
response = Net::HTTP.get_response( URI.parse( url ) )
|
||||
end
|
||||
if response.code.to_s != '200'
|
||||
if ! response.kind_of? Net::HTTPSuccess
|
||||
raise "Can't fetch '#{url}', http code: #{response.code.to_s}"
|
||||
return
|
||||
end
|
||||
|
|
|
@ -395,7 +395,7 @@ class UserState
|
|||
# overview
|
||||
cache_key = @cache_key + '_overview'
|
||||
if CacheIn.expired(cache_key)
|
||||
overview = Ticket.overview(
|
||||
overview = Ticket::Overview.list(
|
||||
:current_user => user,
|
||||
)
|
||||
overview_cache = CacheIn.get( cache_key, { :re_expire => true } )
|
||||
|
@ -405,20 +405,19 @@ class UserState
|
|||
# puts overview.inspect
|
||||
# puts '------'
|
||||
# puts overview_cache.inspect
|
||||
CacheIn.set( cache_key, overview, { :expires_in => 3.seconds } )
|
||||
CacheIn.set( cache_key, overview, { :expires_in => 4.seconds } )
|
||||
end
|
||||
end
|
||||
|
||||
# overview lists
|
||||
overviews = Ticket.overview_list(
|
||||
overviews = Ticket::Overview.all(
|
||||
:current_user => user,
|
||||
)
|
||||
overviews.each { |overview|
|
||||
cache_key = @cache_key + '_overview_data_' + overview.link
|
||||
if CacheIn.expired(cache_key)
|
||||
overview_data = Ticket.overview(
|
||||
overview_data = Ticket::Overview.list(
|
||||
:view => overview.link,
|
||||
# :view_mode => params[:view_mode],
|
||||
:current_user => user,
|
||||
:array => true,
|
||||
)
|
||||
|
@ -434,7 +433,7 @@ class UserState
|
|||
# create_attributes
|
||||
cache_key = @cache_key + '_ticket_create_attributes'
|
||||
if CacheIn.expired(cache_key)
|
||||
ticket_create_attributes = Ticket.attributes_to_change(
|
||||
ticket_create_attributes = Ticket::ScreenOptions.attributes_to_change(
|
||||
:current_user_id => user.id,
|
||||
)
|
||||
ticket_create_attributes_cache = CacheIn.get( cache_key, { :re_expire => true } )
|
||||
|
@ -638,7 +637,7 @@ class ClientState
|
|||
end
|
||||
|
||||
# overview_data
|
||||
overviews = Ticket.overview_list(
|
||||
overviews = Ticket::Overview.all(
|
||||
:current_user => user,
|
||||
)
|
||||
overviews.each { |overview|
|
||||
|
@ -661,7 +660,7 @@ class ClientState
|
|||
group_ids.push group.id
|
||||
}
|
||||
agents = {}
|
||||
Ticket.agents.each { |user|
|
||||
Ticket::ScreenOptions.agents.each { |user|
|
||||
agents[ user.id ] = 1
|
||||
}
|
||||
groups_users = {}
|
||||
|
|
63
lib/sso.rb
Normal file
63
lib/sso.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Sso < ApplicationLib
|
||||
|
||||
=begin
|
||||
|
||||
authenticate user via username and password
|
||||
|
||||
result = Sso.check( params, config_item )
|
||||
|
||||
returns
|
||||
|
||||
result = user_model # if authentication was successfully
|
||||
|
||||
=end
|
||||
|
||||
def self.check(params)
|
||||
|
||||
# use std. auth backends
|
||||
config = [
|
||||
{
|
||||
:adapter => 'Sso::Env',
|
||||
},
|
||||
{
|
||||
:adapter => 'Sso::Otrs',
|
||||
},
|
||||
]
|
||||
|
||||
# added configured backends
|
||||
Setting.where( :area => 'Security::SSO' ).each {|setting|
|
||||
if setting.state[:value]
|
||||
config.push setting.state[:value]
|
||||
end
|
||||
}
|
||||
|
||||
# try to login against configure auth backends
|
||||
user_auth = nil
|
||||
config.each {|config_item|
|
||||
next if !config_item[:adapter]
|
||||
|
||||
# load backend
|
||||
backend = self.load_adapter( config_item[:adapter] )
|
||||
return if !backend
|
||||
|
||||
user_auth = backend.check( params, config_item )
|
||||
|
||||
# auth ok
|
||||
if user_auth
|
||||
|
||||
# remember last login date
|
||||
user_auth.update_last_login
|
||||
|
||||
# reset login failed
|
||||
user_auth.login_failed = 0
|
||||
user_auth.save
|
||||
|
||||
return user_auth
|
||||
end
|
||||
}
|
||||
return
|
||||
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
module SSO
|
||||
end
|
||||
module SSO::ENV
|
||||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Sso::Env
|
||||
def self.check( params, config_item )
|
||||
|
||||
# try to find user based on login
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module SSO
|
||||
end
|
||||
module SSO::OTRS
|
||||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
module Sso::Otrs
|
||||
def self.check( params, config_item )
|
||||
|
||||
endpoint = Setting.get('import_otrs_endpoint')
|
||||
|
|
|
@ -55,7 +55,7 @@ class SignupTest < TestCase
|
|||
},
|
||||
{
|
||||
:execute => 'wait',
|
||||
:value => 2,
|
||||
:value => 5,
|
||||
},
|
||||
|
||||
# check action
|
||||
|
|
|
@ -7,7 +7,7 @@ Setting.create_or_update(
|
|||
:area => 'Security::Authentication',
|
||||
:description => 'Enables user authentication via LDAP.',
|
||||
:state => {
|
||||
:adapter => 'ldap',
|
||||
:adapter => 'Auth::Ldap',
|
||||
:host => 'localhost',
|
||||
:port => 389,
|
||||
:bind_dn => 'cn=Manager,dc=example,dc=org',
|
||||
|
@ -45,6 +45,7 @@ else
|
|||
:created_by_id => 1
|
||||
)
|
||||
end
|
||||
|
||||
class AuthTest < ActiveSupport::TestCase
|
||||
test 'auth' do
|
||||
tests = [
|
||||
|
|
Loading…
Reference in a new issue