ticket.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
  2. class Ticket < ApplicationModel
  3. include Ticket::Escalation
  4. include Ticket::Subject
  5. include Ticket::Permission
  6. load 'ticket/assets.rb'
  7. include Ticket::Assets
  8. load 'ticket/history_log.rb'
  9. include Ticket::HistoryLog
  10. load 'ticket/activity_stream_log.rb'
  11. include Ticket::ActivityStreamLog
  12. load 'ticket/search_index.rb'
  13. include Ticket::SearchIndex
  14. extend Ticket::Search
  15. before_create :check_generate, :check_defaults, :check_title
  16. before_update :check_defaults, :check_title, :reset_pending_time
  17. before_destroy :destroy_dependencies
  18. notify_clients_support
  19. latest_change_support
  20. activity_stream_support ignore_attributes: {
  21. create_article_type_id: true,
  22. create_article_sender_id: true,
  23. article_count: true,
  24. first_response: true,
  25. first_response_escal_date: true,
  26. first_response_sla_time: true,
  27. first_response_in_min: true,
  28. first_response_diff_in_min: true,
  29. close_time: true,
  30. close_time_escal_date: true,
  31. close_time_sla_time: true,
  32. close_time_in_min: true,
  33. close_time_diff_in_min: true,
  34. update_time_escal_date: true,
  35. updtate_time_sla_time: true,
  36. update_time_in_min: true,
  37. update_time_diff_in_min: true,
  38. last_contact: true,
  39. last_contact_agent: true,
  40. last_contact_customer: true,
  41. }
  42. history_support ignore_attributes: {
  43. create_article_type_id: true,
  44. create_article_sender_id: true,
  45. article_count: true,
  46. }
  47. search_index_support(
  48. ignore_attributes: {
  49. create_article_type_id: true,
  50. create_article_sender_id: true,
  51. article_count: true,
  52. },
  53. keep_attributes: {
  54. customer_id: true,
  55. organization_id: true,
  56. },
  57. )
  58. belongs_to :group
  59. has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update
  60. belongs_to :organization
  61. belongs_to :state, class_name: 'Ticket::State'
  62. belongs_to :priority, class_name: 'Ticket::Priority'
  63. belongs_to :owner, class_name: 'User'
  64. belongs_to :customer, class_name: 'User'
  65. belongs_to :created_by, class_name: 'User'
  66. belongs_to :updated_by, class_name: 'User'
  67. belongs_to :create_article_type, class_name: 'Ticket::Article::Type'
  68. belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender'
  69. self.inheritance_column = nil
  70. attr_accessor :callback_loop
  71. =begin
  72. list of agents in group of ticket
  73. ticket = Ticket.find(123)
  74. result = ticket.agent_of_group
  75. returns
  76. result = [user1, user2, ...]
  77. =end
  78. def agent_of_group
  79. Group.find( group_id ).users.where( active: true ).joins(:roles).where( 'roles.name' => Z_ROLENAME_AGENT, 'roles.active' => true ).uniq()
  80. end
  81. =begin
  82. get user access conditions
  83. conditions = Ticket.access_condition( User.find(1) )
  84. returns
  85. result = [user1, user2, ...]
  86. =end
  87. def self.access_condition(user)
  88. access_condition = []
  89. if user.role?(Z_ROLENAME_AGENT)
  90. group_ids = Group.select( 'groups.id' ).joins(:users)
  91. .where( 'groups_users.user_id = ?', user.id )
  92. .where( 'groups.active = ?', true )
  93. .map( &:id )
  94. access_condition = [ 'group_id IN (?)', group_ids ]
  95. else
  96. if !user.organization || ( !user.organization.shared || user.organization.shared == false )
  97. access_condition = [ 'customer_id = ?', user.id ]
  98. else
  99. access_condition = [ '( customer_id = ? OR organization_id = ? )', user.id, user.organization.id ]
  100. end
  101. end
  102. access_condition
  103. end
  104. =begin
  105. processes tickets which have reached their pending time and sets next state_id
  106. processed_tickets = Ticket.process_pending()
  107. returns
  108. processed_tickets = [<Ticket>, ...]
  109. =end
  110. def self.process_pending
  111. ticket_states = Ticket::State.where(
  112. state_type_id: Ticket::StateType.find_by( name: 'pending action' ),
  113. )
  114. .where.not(next_state_id: nil) # rubocop:disable Style/MultilineOperationIndentation
  115. return [] if !ticket_states
  116. next_state_map = {}
  117. ticket_states.each { |state|
  118. next_state_map[state.id] = state.next_state_id
  119. }
  120. tickets = where(
  121. state_id: next_state_map.keys,
  122. )
  123. .where( 'pending_time <= ?', Time.zone.now ) # rubocop:disable Style/MultilineOperationIndentation
  124. return [] if !tickets
  125. result = []
  126. tickets.each { |ticket|
  127. ticket.state_id = next_state_map[ticket.state_id]
  128. ticket.updated_at = Time.zone.now
  129. ticket.updated_by_id = 1
  130. ticket.save!
  131. result.push ticket
  132. }
  133. # we do not have an destructor at this point, so we need to
  134. # execute ticket events manually
  135. Observer::Ticket::Notification.transaction
  136. result
  137. end
  138. =begin
  139. merge tickets
  140. ticket = Ticket.find(123)
  141. result = ticket.merge_to(
  142. ticket_id: 123,
  143. user_id: 123,
  144. )
  145. returns
  146. result = true|false
  147. =end
  148. def merge_to(data)
  149. # update articles
  150. Ticket::Article.where( ticket_id: id ).each(&:touch)
  151. # quiet update of reassign of articles
  152. Ticket::Article.where( ticket_id: id ).update_all( ['ticket_id = ?', data[:ticket_id] ] )
  153. # touch new ticket (to broadcast change)
  154. Ticket.find( data[:ticket_id] ).touch
  155. # update history
  156. # create new merge article
  157. Ticket::Article.create(
  158. ticket_id: id,
  159. type_id: Ticket::Article::Type.lookup( name: 'note' ).id,
  160. sender_id: Ticket::Article::Sender.lookup( name: Z_ROLENAME_AGENT ).id,
  161. body: 'merged',
  162. internal: false,
  163. created_by_id: data[:user_id],
  164. updated_by_id: data[:user_id],
  165. )
  166. # add history to both
  167. # link tickets
  168. Link.add(
  169. link_type: 'parent',
  170. link_object_source: 'Ticket',
  171. link_object_source_value: data[:ticket_id],
  172. link_object_target: 'Ticket',
  173. link_object_target_value: id
  174. )
  175. # set state to 'merged'
  176. self.state_id = Ticket::State.lookup( name: 'merged' ).id
  177. # rest owner
  178. self.owner_id = User.find_by( login: '-' ).id
  179. # save ticket
  180. save
  181. end
  182. =begin
  183. check if online notifcation should be shown in general as already seen with current state
  184. ticket = Ticket.find(1)
  185. seen = ticket.online_notification_seen_state(user_id_check)
  186. returns
  187. result = true # or false
  188. check if online notifcation should be shown for this user as already seen with current state
  189. ticket = Ticket.find(1)
  190. seen = ticket.online_notification_seen_state(check_user_id)
  191. returns
  192. result = true # or false
  193. =end
  194. def online_notification_seen_state(user_id_check = nil)
  195. state = Ticket::State.lookup( id: state_id )
  196. state_type = Ticket::StateType.lookup( id: state.state_type_id )
  197. # set all to seen if pending action state is a closed or merged state
  198. if state_type.name == 'pending action' && state.next_state_id
  199. state = Ticket::State.lookup( id: state.next_state_id )
  200. state_type = Ticket::StateType.lookup( id: state.state_type_id )
  201. end
  202. # set all to seen if new state is pending reminder state
  203. if state_type.name == 'pending reminder'
  204. if user_id_check
  205. return false if owner_id == 1
  206. return false if updated_by_id != owner_id && user_id_check == owner_id
  207. return true
  208. end
  209. return true
  210. end
  211. # set all to seen if new state is a closed or merged state
  212. return true if state_type.name == 'closed'
  213. return true if state_type.name == 'merged'
  214. false
  215. end
  216. =begin
  217. get count of tickets and tickets which match on selector
  218. ticket_count, tickets = Ticket.selectors(params[:condition], 6)
  219. =end
  220. def self.selectors(selectors, limit = 10)
  221. return if !selectors
  222. query, bind_params, tables = selector2sql(selectors)
  223. return [] if !query
  224. ticket_count = Ticket.where(query, *bind_params).joins(tables).count
  225. tickets = Ticket.where(query, *bind_params).joins(tables).limit(limit)
  226. [ticket_count, tickets]
  227. end
  228. =begin
  229. generate condition query to search for tickets based on condition
  230. query_condition, bind_condition = selector2sql(params[:condition])
  231. condition example
  232. {
  233. 'ticket.state_id' => {
  234. operator: 'is',
  235. value: [1,2,5]
  236. }
  237. }
  238. =end
  239. def self.selector2sql(selectors)
  240. return if !selectors
  241. query = ''
  242. bind_params = []
  243. tables = []
  244. selectors.each {|attribute, selector|
  245. selector = attribute.split(/\./)
  246. next if !selector[1]
  247. next if selector[0] == 'ticket'
  248. next if tables.include?(selector[0])
  249. tables.push selector[0].to_sym
  250. }
  251. selectors.each {|attribute, selector_raw|
  252. if query != ''
  253. query += ' AND '
  254. end
  255. selector = selector_raw.stringify_keys
  256. fail "Invalid selector #{selector.inspect}" if !selector
  257. fail "Invalid selector #{selector.inspect}" if !selector.respond_to?(:key?)
  258. fail "Invalid selector, operator missing #{selector.inspect}" if !selector['operator']
  259. return nil if !selector['value']
  260. return nil if selector['value'].respond_to?(:empty?) && selector['value'].empty?
  261. attributes = attribute.split(/\./)
  262. attribute = "#{attributes[0]}s.#{attributes[1]}"
  263. if selector['operator'] == 'is'
  264. query += "#{attribute} IN (?)"
  265. bind_params.push selector['value']
  266. elsif selector['operator'] == 'is not'
  267. query += "#{attribute} NOT IN (?)"
  268. bind_params.push selector['value']
  269. elsif selector['operator'] == 'contains'
  270. query += "#{attribute} LIKE (?)"
  271. value = "%#{selector['value']}%"
  272. bind_params.push value
  273. elsif selector['operator'] == 'contains not'
  274. query += "#{attribute} NOT LIKE (?)"
  275. value = "%#{selector['value']}%"
  276. bind_params.push value
  277. elsif selector['operator'] == 'before (absolute)'
  278. query += "#{attribute} <= ?"
  279. bind_params.push selector['value']
  280. elsif selector['operator'] == 'after (absolute)'
  281. query += "#{attribute} >= ?"
  282. bind_params.push selector['value']
  283. elsif selector['operator'] == 'before (relative)'
  284. query += "#{attribute} <= ?"
  285. bind_params.push Time.zone.now - selector['value'].to_i.minutes
  286. elsif selector['operator'] == 'after (relative)'
  287. query += "#{attribute} >= ?"
  288. bind_params.push Time.zone.now + selector['value'].to_i.minutes
  289. else
  290. fail "Invalid operator '#{selector['operator']}' for '#{selector['value'].inspect}'"
  291. end
  292. }
  293. [query, bind_params, tables]
  294. end
  295. private
  296. def check_generate
  297. return if number
  298. self.number = Ticket::Number.generate
  299. end
  300. def check_title
  301. return if !title
  302. title.gsub!(/\s|\t|\r/, ' ')
  303. end
  304. def check_defaults
  305. if !owner_id
  306. self.owner_id = 1
  307. end
  308. return if !customer_id
  309. customer = User.find( customer_id )
  310. return if organization_id == customer.organization_id
  311. self.organization_id = customer.organization_id
  312. end
  313. def reset_pending_time
  314. # ignore if no state has changed
  315. return if !changes['state_id']
  316. # check if new state isn't pending*
  317. current_state = Ticket::State.lookup( id: state_id )
  318. current_state_type = Ticket::StateType.lookup( id: current_state.state_type_id )
  319. # in case, set pending_time to nil
  320. return if current_state_type.name =~ /^pending/i
  321. self.pending_time = nil
  322. end
  323. def destroy_dependencies
  324. # delete articles
  325. articles.destroy_all
  326. # destroy online notifications
  327. OnlineNotification.remove( self.class.to_s, id )
  328. end
  329. end