ticket.rb 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. class Ticket < ApplicationModel
  2. before_create :number_generate, :check_defaults
  3. before_update :check_defaults
  4. before_destroy :destroy_dependencies
  5. belongs_to :group
  6. has_many :articles, :class_name => 'Ticket::Article', :after_add => :cache_update, :after_remove => :cache_update
  7. belongs_to :organization
  8. belongs_to :ticket_state, :class_name => 'Ticket::State'
  9. belongs_to :ticket_priority, :class_name => 'Ticket::Priority'
  10. belongs_to :owner, :class_name => 'User'
  11. belongs_to :customer, :class_name => 'User'
  12. belongs_to :created_by, :class_name => 'User'
  13. belongs_to :create_article_type, :class_name => 'Ticket::Article::Type'
  14. belongs_to :create_article_sender, :class_name => 'Ticket::Article::Sender'
  15. def self.number_check (string)
  16. self.number_adapter.number_check_item(string)
  17. end
  18. def agent_of_group
  19. Group.find( self.group_id ).users.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
  20. end
  21. def self.agents
  22. User.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
  23. end
  24. def self.attributes_to_change(params)
  25. if params[:ticket_id]
  26. params[:ticket] = self.find( params[:ticket_id] )
  27. end
  28. if params[:article_id]
  29. params[:article] = self.find( params[:article_id] )
  30. end
  31. # get ticket states
  32. ticket_state_ids = []
  33. if params[:ticket]
  34. ticket_state_type = params[:ticket].ticket_state.state_type
  35. end
  36. ticket_state_types = ['open', 'closed', 'pending action', 'pending reminder']
  37. if ticket_state_type && !ticket_state_types.include?(ticket_state_type.name)
  38. ticket_state_ids.push params[:ticket].ticket_state.id
  39. end
  40. ticket_state_types.each {|type|
  41. ticket_state_type = Ticket::StateType.where( :name => type ).first
  42. if ticket_state_type
  43. ticket_state_type.states.each {|ticket_state|
  44. ticket_state_ids.push ticket_state.id
  45. }
  46. end
  47. }
  48. # get owner
  49. owner_ids = []
  50. if params[:ticket]
  51. params[:ticket].agent_of_group.each { |user|
  52. owner_ids.push user.id
  53. }
  54. end
  55. # get group
  56. group_ids = []
  57. Group.where( :active => true ).each { |group|
  58. group_ids.push group.id
  59. }
  60. # get group / user relations
  61. agents = {}
  62. Ticket.agents.each { |user|
  63. agents[ user.id ] = 1
  64. }
  65. groups_users = {}
  66. group_ids.each {|group_id|
  67. groups_users[ group_id ] = []
  68. Group.find( group_id ).users.each {|user|
  69. next if !agents[ user.id ]
  70. groups_users[ group_id ].push user.id
  71. }
  72. }
  73. # get priorities
  74. ticket_priority_ids = []
  75. Ticket::Priority.where( :active => true ).each { |priority|
  76. ticket_priority_ids.push priority.id
  77. }
  78. ticket_article_type_ids = []
  79. if params[:ticket]
  80. ticket_article_types = ['note', 'phone']
  81. if params[:ticket].group.email_address_id
  82. ticket_article_types.push 'email'
  83. end
  84. ticket_article_types.each {|ticket_article_type_name|
  85. ticket_article_type = Ticket::Article::Type.lookup( :name => ticket_article_type_name )
  86. if ticket_article_type
  87. ticket_article_type_ids.push ticket_article_type.id
  88. end
  89. }
  90. end
  91. return {
  92. :ticket_article_type_id => ticket_article_type_ids,
  93. :ticket_state_id => ticket_state_ids,
  94. :ticket_priority_id => ticket_priority_ids,
  95. :owner_id => owner_ids,
  96. :group_id => group_ids,
  97. :group_id__owner_id => groups_users,
  98. }
  99. end
  100. def merge_to(data)
  101. # update articles
  102. Ticket::Article.where( :ticket_id => self.id ).update_all( ['ticket_id = ?', data[:ticket_id] ] )
  103. # update history
  104. # create new merge article
  105. Ticket::Article.create(
  106. :created_by_id => data[:created_by_id],
  107. :ticket_id => self.id,
  108. :ticket_article_type_id => Ticket::Article::Type.lookup( :name => 'note' ).id,
  109. :ticket_article_sender_id => Ticket::Article::Sender.lookup( :name => 'Agent' ).id,
  110. :body => 'merged',
  111. :internal => false
  112. )
  113. # add history to both
  114. # link tickets
  115. Link.add(
  116. :link_type => 'parent',
  117. :link_object_source => 'Ticket',
  118. :link_object_source_value => data[:ticket_id],
  119. :link_object_target => 'Ticket',
  120. :link_object_target_value => self.id
  121. )
  122. # set state to 'merged'
  123. self.ticket_state_id = Ticket::State.lookup( :name => 'merged' ).id
  124. # rest owner
  125. self.owner_id = User.where( :login => '-' ).first.id
  126. # save ticket
  127. self.save
  128. end
  129. # def self.agent
  130. # Role.where( :name => ['Agent'], :active => true ).first.users.where( :active => true ).uniq()
  131. # end
  132. def subject_build (subject)
  133. # clena subject
  134. subject = self.subject_clean(subject)
  135. ticket_hook = Setting.get('ticket_hook')
  136. ticket_hook_divider = Setting.get('ticket_hook_divider')
  137. # none position
  138. if Setting.get('ticket_hook_position') == 'none'
  139. return subject
  140. end
  141. # right position
  142. if Setting.get('ticket_hook_position') == 'right'
  143. return subject + " [#{ticket_hook}#{ticket_hook_divider}#{self.number}] "
  144. end
  145. # left position
  146. return "[#{ticket_hook}#{ticket_hook_divider}#{self.number}] " + subject
  147. end
  148. def subject_clean (subject)
  149. ticket_hook = Setting.get('ticket_hook')
  150. ticket_hook_divider = Setting.get('ticket_hook_divider')
  151. ticket_subject_size = Setting.get('ticket_subject_size')
  152. # remove all possible ticket hook formats with []
  153. subject = subject.gsub /\[#{ticket_hook}: #{self.number}\](\s+?|)/, ''
  154. subject = subject.gsub /\[#{ticket_hook}:#{self.number}\](\s+?|)/, ''
  155. subject = subject.gsub /\[#{ticket_hook}#{ticket_hook_divider}#{self.number}\](\s+?|)/, ''
  156. # remove all possible ticket hook formats without []
  157. subject = subject.gsub /#{ticket_hook}: #{self.number}(\s+?|)/, ''
  158. subject = subject.gsub /#{ticket_hook}:#{self.number}(\s+?|)/, ''
  159. subject = subject.gsub /#{ticket_hook}#{ticket_hook_divider}#{self.number}(\s+?|)/, ''
  160. # remove leading "..:\s" and "..[\d+]:\s" e. g. "Re: " or "Re[5]: "
  161. subject = subject.gsub /^(..(\[\d+\])?:\s)+/, ''
  162. # resize subject based on config
  163. if subject.length > ticket_subject_size.to_i
  164. subject = subject[ 0, ticket_subject_size.to_i ] + '[...]'
  165. end
  166. return subject
  167. end
  168. # ticket.permission(
  169. # :current_user => 123
  170. # )
  171. def permission (data)
  172. # check customer
  173. if data[:current_user].is_role('Customer')
  174. # access ok if its own ticket
  175. return true if self.customer_id == data[:current_user].id
  176. # access ok if its organization ticket
  177. if data[:current_user].organization_id && self.organization_id
  178. return true if self.organization_id == data[:current_user].organization_id
  179. end
  180. # no access
  181. return false
  182. end
  183. # check agent
  184. return true if self.owner_id == data[:current_user].id
  185. data[:current_user].groups.each {|group|
  186. return true if self.group.id == group.id
  187. }
  188. return false
  189. end
  190. # Ticket.overview_list(
  191. # :current_user => 123,
  192. # )
  193. def self.overview_list (data)
  194. # get customer overviews
  195. if data[:current_user].is_role('Customer')
  196. role = data[:current_user].is_role( 'Customer' )
  197. if data[:current_user].organization_id && data[:current_user].organization.shared
  198. overviews = Overview.where( :role_id => role.id, :active => true )
  199. else
  200. overviews = Overview.where( :role_id => role.id, :organization_shared => false, :active => true )
  201. end
  202. return overviews
  203. end
  204. # get agent overviews
  205. role = data[:current_user].is_role( 'Agent' )
  206. overviews = Overview.where( :role_id => role.id, :active => true )
  207. return overviews
  208. end
  209. # Ticket.overview(
  210. # :view => 'some_view_url',
  211. # :current_user => OBJECT,
  212. # )
  213. def self.overview (data)
  214. overviews = self.overview_list(data)
  215. # build up attributes hash
  216. overview_selected = nil
  217. overview_selected_raw = nil
  218. overviews.each { |overview|
  219. # remember selected view
  220. if data[:view] && data[:view] == overview.link
  221. overview_selected = overview
  222. overview_selected_raw = Marshal.load( Marshal.dump(overview.attributes) )
  223. end
  224. # replace e.g. 'current_user.id' with current_user.id
  225. overview.condition.each { |item, value |
  226. if value && value.class.to_s == 'String'
  227. parts = value.split( '.', 2 )
  228. if parts[0] && parts[1] && parts[0] == 'current_user'
  229. overview.condition[item] = data[:current_user][parts[1].to_sym]
  230. end
  231. end
  232. }
  233. }
  234. if data[:view] && !overview_selected
  235. return
  236. end
  237. # sortby
  238. # prio
  239. # state
  240. # group
  241. # customer
  242. # order
  243. # asc
  244. # desc
  245. # groupby
  246. # prio
  247. # state
  248. # group
  249. # customer
  250. # all = attributes[:myopenassigned]
  251. # all.merge( { :group_id => groups } )
  252. # @tickets = Ticket.where(:group_id => groups, attributes[:myopenassigned] ).limit(params[:limit])
  253. # get only tickets with permissions
  254. if data[:current_user].is_role('Customer')
  255. group_ids = Group.select( 'groups.id' ).
  256. where( 'groups.active = ?', true ).
  257. map( &:id )
  258. else
  259. group_ids = Group.select( 'groups.id' ).joins(:users).
  260. where( 'groups_users.user_id = ?', [ data[:current_user].id ] ).
  261. where( 'groups.active = ?', true ).
  262. map( &:id )
  263. end
  264. # overview meta for navbar
  265. if !overview_selected
  266. # loop each overview
  267. result = []
  268. overviews.each { |overview|
  269. # get count
  270. count = Ticket.where( :group_id => group_ids ).where( self._condition( overview.condition ) ).count()
  271. # get meta info
  272. all = {
  273. :name => overview.name,
  274. :prio => overview.prio,
  275. :link => overview.link,
  276. }
  277. # push to result data
  278. result.push all.merge( { :count => count } )
  279. }
  280. return result
  281. end
  282. # get result list
  283. if data[:array]
  284. order_by = overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s
  285. if overview_selected.group_by && !overview_selected.group_by.empty?
  286. order_by = overview_selected.group_by + '_id, ' + order_by
  287. end
  288. tickets = Ticket.select( 'id' ).
  289. where( :group_id => group_ids ).
  290. where( self._condition( overview_selected.condition ) ).
  291. order( order_by ).
  292. limit( 500 )
  293. ticket_ids = []
  294. tickets.each { |ticket|
  295. ticket_ids.push ticket.id
  296. }
  297. tickets_count = Ticket.where( :group_id => group_ids ).
  298. where( self._condition( overview_selected.condition ) ).
  299. count()
  300. return {
  301. :ticket_list => ticket_ids,
  302. :tickets_count => tickets_count,
  303. :overview => overview_selected_raw,
  304. }
  305. end
  306. # get tickets for overview
  307. data[:start_page] ||= 1
  308. tickets = Ticket.where( :group_id => group_ids ).
  309. where( self._condition( overview_selected.condition ) ).
  310. order( overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s )#.
  311. # limit( overview_selected.view[ data[:view_mode].to_sym ][:per_page] ).
  312. # offset( overview_selected.view[ data[:view_mode].to_sym ][:per_page].to_i * ( data[:start_page].to_i - 1 ) )
  313. tickets_count = Ticket.where( :group_id => group_ids ).
  314. where( self._condition( overview_selected.condition ) ).
  315. count()
  316. return {
  317. :tickets => tickets,
  318. :tickets_count => tickets_count,
  319. :overview => overview_selected_raw,
  320. }
  321. end
  322. def self._condition(condition)
  323. sql = ''
  324. bind = [nil]
  325. condition.each {|key, value|
  326. if sql != ''
  327. sql += ' AND '
  328. end
  329. if value.class == Array
  330. sql += " #{key} IN (?)"
  331. bind.push value
  332. elsif value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
  333. time = Time.now
  334. if value['area'] == 'minute'
  335. if value['direction'] == 'last'
  336. time -= value['count'].to_i * 60
  337. else
  338. time += value['count'].to_i * 60
  339. end
  340. elsif value['area'] == 'hour'
  341. if value['direction'] == 'last'
  342. time -= value['count'].to_i * 60 * 60
  343. else
  344. time += value['count'].to_i * 60 * 60
  345. end
  346. elsif value['area'] == 'day'
  347. if value['direction'] == 'last'
  348. time -= value['count'].to_i * 60 * 60 * 24
  349. else
  350. time += value['count'].to_i * 60 * 60 * 24
  351. end
  352. elsif value['area'] == 'month'
  353. if value['direction'] == 'last'
  354. time -= value['count'].to_i * 60 * 60 * 24 * 31
  355. else
  356. time += value['count'].to_i * 60 * 60 * 24 * 31
  357. end
  358. elsif value['area'] == 'year'
  359. if value['direction'] == 'last'
  360. time -= value['count'].to_i * 60 * 60 * 24 * 365
  361. else
  362. time += value['count'].to_i * 60 * 60 * 24 * 365
  363. end
  364. end
  365. if value['direction'] == 'last'
  366. sql += " #{key} > ?"
  367. bind.push time
  368. else
  369. sql += " #{key} < ?"
  370. bind.push time
  371. end
  372. else
  373. sql += " #{key} = ?"
  374. bind.push value
  375. end
  376. }
  377. bind[0] = sql
  378. return bind
  379. end
  380. def self.number_adapter
  381. # load backend based on config
  382. adapter_name = Setting.get('ticket_number')
  383. adapter = nil
  384. case adapter_name
  385. when Symbol, String
  386. require "ticket/number/#{adapter_name.to_s.downcase}"
  387. adapter = Ticket::Number.const_get("#{adapter_name.to_s.capitalize}")
  388. else
  389. raise "Missing number_adapter '#{adapter_name}'"
  390. end
  391. return adapter
  392. end
  393. def self.escalation_calculation_rebuild
  394. ticket_state_list_open = Ticket::State.where(
  395. :state_type_id => Ticket::StateType.where(
  396. :name => ['new','open', 'pending reminder', 'pending action']
  397. )
  398. )
  399. tickets = Ticket.where( :ticket_state_id => ticket_state_list_open )
  400. tickets.each {|ticket|
  401. ticket.escalation_calculation
  402. }
  403. end
  404. def _escalation_calculation_get_sla
  405. sla_selected = nil
  406. Sla.where( :active => true ).each {|sla|
  407. if !sla.condition || sla.condition.empty?
  408. sla_selected = sla
  409. elsif sla.condition
  410. hit = false
  411. map = [
  412. [ 'tickets.ticket_priority_id', 'ticket_priority_id' ],
  413. [ 'tickets.group_id', 'group_id' ]
  414. ]
  415. map.each {|item|
  416. if sla.condition[ item[0] ]
  417. if sla.condition[ item[0] ].class == String
  418. sla.condition[ item[0] ] = [ sla.condition[ item[0] ] ]
  419. end
  420. if sla.condition[ item[0] ].include?( self[ item[1] ].to_s )
  421. hit = true
  422. else
  423. hit = false
  424. end
  425. end
  426. }
  427. if hit
  428. sla_selected = sla
  429. end
  430. end
  431. }
  432. # get and set calendar settings
  433. if sla_selected
  434. BusinessTime::Config.beginning_of_workday = sla_selected.data['beginning_of_workday']
  435. BusinessTime::Config.end_of_workday = sla_selected.data['end_of_workday']
  436. days = []
  437. ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].each {|day|
  438. if sla_selected.data[day]
  439. days.push day.downcase.to_sym
  440. end
  441. }
  442. BusinessTime::Config.work_week = days
  443. end
  444. return sla_selected
  445. end
  446. def _escalation_calculation_dest_time(start_time, diff_min)
  447. start_time = Time.parse( start_time.to_s )
  448. dest_time = (diff_min / 60).round.business_hour.after( start_time )
  449. return dest_time
  450. end
  451. def _escalation_calculation_higher_time(escalation_time, check_time, done_time)
  452. return escalation_time if done_time
  453. return check_time if !escalation_time
  454. return escalation_time if !check_time
  455. return check_time if escalation_time > check_time
  456. return escalation_time
  457. end
  458. def _escalation_calculation_business_time_diff(start_time, end_time)
  459. start_time = Time.parse(start_time.to_s)
  460. end_time = Time.parse(end_time.to_s)
  461. diff = start_time.business_time_until(end_time) / 60
  462. diff.round
  463. end
  464. def escalation_calculation
  465. # set escalation off if ticket is already closed
  466. ticket_state = Ticket::State.lookup( :id => self.ticket_state_id )
  467. ticket_state_type = Ticket::StateType.lookup( :id => ticket_state.state_type_id )
  468. ignore_escalation = ['removed', 'closed', 'merged', 'pending action']
  469. if ignore_escalation.include?( ticket_state_type.name )
  470. self.escalation_time = nil
  471. # self.first_response_escal_date = nil
  472. # self.close_time_escal_date = nil
  473. self.save
  474. return true
  475. end
  476. # get sla for ticket
  477. sla_selected = self._escalation_calculation_get_sla
  478. # reset escalation if no sla is set
  479. if !sla_selected
  480. self.escalation_time = nil
  481. # self.first_response_escal_date = nil
  482. # self.close_time_escal_date = nil
  483. self.save
  484. return true
  485. end
  486. # puts sla_selected.inspect
  487. # puts days.inspect
  488. self.escalation_time = nil
  489. self.first_response_escal_date = nil
  490. self.update_time_escal_date = nil
  491. self.close_time_escal_date = nil
  492. # first response
  493. if sla_selected.first_response_time
  494. self.first_response_escal_date = self._escalation_calculation_dest_time( created_at, sla_selected.first_response_time )
  495. # set ticket escalation
  496. self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response )
  497. end
  498. if self.first_response# && !self.first_response_in_min
  499. # created_at = Time.parse(self.created_at.to_s)
  500. # first_response_at = Time.parse(self.first_response.to_s)
  501. # diff = created_at.business_time_until(first_response_at) / 60
  502. # self.first_response_in_min = diff.round
  503. self.first_response_in_min = self._escalation_calculation_business_time_diff( self.created_at, self.first_response )
  504. end
  505. # set sla time
  506. if sla_selected.first_response_time && self.first_response_in_min
  507. self.first_response_diff_in_min = sla_selected.first_response_time - self.first_response_in_min
  508. end
  509. # update time
  510. if sla_selected.update_time
  511. last_update = self.last_contact_agent
  512. if !last_update
  513. last_update = self.created_at
  514. end
  515. self.update_time_escal_date = self._escalation_calculation_dest_time( last_update, sla_selected.update_time )
  516. # set ticket escalation
  517. self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.update_time_escal_date, false )
  518. end
  519. # if self.last_contact_agent && !self.update_time_in_min
  520. # created_at = Time.parse(self.created_at.to_s)
  521. # last_contact_agent = Time.parse(self.last_contact_agent.to_s)
  522. # diff = created_at.business_time_until(closed_at) / 60
  523. # self.close_time_in_min = diff.round
  524. # end
  525. # close time
  526. if sla_selected.close_time
  527. self.close_time_escal_date = self._escalation_calculation_dest_time( self.created_at, sla_selected.close_time )
  528. # set ticket escalation
  529. self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time )
  530. end
  531. if self.close_time# && !self.close_time_in_min
  532. # created_at = Time.parse(self.created_at.to_s)
  533. # closed_at = Time.parse(self.close_time.to_s)
  534. # diff = created_at.business_time_until(closed_at) / 60
  535. # self.close_time_in_min = diff.round
  536. self.close_time_in_min = self._escalation_calculation_business_time_diff( self.created_at, self.close_time )
  537. end
  538. # set sla time
  539. if sla_selected.close_time && self.close_time_in_min
  540. self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min
  541. end
  542. self.save
  543. end
  544. private
  545. def number_generate
  546. return if self.number
  547. # generate number
  548. (1..25_000).each do |i|
  549. number = Ticket.number_adapter.number_generate_item()
  550. ticket = Ticket.where( :number => number ).first
  551. if ticket != nil
  552. number = Ticket.number_adapter.number_generate_item()
  553. else
  554. self.number = number
  555. return number
  556. end
  557. end
  558. end
  559. def check_defaults
  560. if !self.owner_id
  561. self.owner_id = 1
  562. end
  563. # if self.customer_id && ( !self.organization_id || self.organization_id.empty? )
  564. if self.customer_id
  565. customer = User.find( self.customer_id )
  566. if self.organization_id != customer.organization_id
  567. self.organization_id = customer.organization_id
  568. end
  569. end
  570. end
  571. def destroy_dependencies
  572. # delete history
  573. History.history_destroy( 'Ticket', self.id )
  574. # delete articles
  575. self.articles.destroy_all
  576. end
  577. class Number
  578. end
  579. class Flag < ApplicationModel
  580. end
  581. class Priority < ApplicationModel
  582. self.table_name = 'ticket_priorities'
  583. validates :name, :presence => true
  584. end
  585. class StateType < ApplicationModel
  586. has_many :states, :class_name => 'Ticket::State'
  587. validates :name, :presence => true
  588. end
  589. class State < ApplicationModel
  590. belongs_to :state_type, :class_name => 'Ticket::StateType'
  591. validates :name, :presence => true
  592. end
  593. end