ticket.rb 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class Ticket < ApplicationModel
  3. include LogsActivityStream
  4. include NotifiesClients
  5. include LatestChangeObserved
  6. include Historisable
  7. include Taggable
  8. include SearchIndexed
  9. include Ticket::Escalation
  10. include Ticket::Subject
  11. load 'ticket/permission.rb'
  12. include Ticket::Permission
  13. load 'ticket/assets.rb'
  14. include Ticket::Assets
  15. load 'ticket/search_index.rb'
  16. include Ticket::SearchIndex
  17. extend Ticket::Search
  18. store :preferences
  19. before_create :check_generate, :check_defaults, :check_title, :set_default_state, :set_default_priority
  20. after_create :check_escalation_update
  21. before_update :check_defaults, :check_title, :reset_pending_time
  22. after_update :check_escalation_update
  23. before_destroy :destroy_dependencies
  24. validates :group_id, presence: true
  25. activity_stream_permission 'ticket.agent'
  26. activity_stream_attributes_ignored :organization_id, # organization_id will channge automatically on user update
  27. :create_article_type_id,
  28. :create_article_sender_id,
  29. :article_count,
  30. :first_response_at,
  31. :first_response_escalation_at,
  32. :first_response_in_min,
  33. :first_response_diff_in_min,
  34. :close_at,
  35. :close_escalation_at,
  36. :close_in_min,
  37. :close_diff_in_min,
  38. :update_escalation_at,
  39. :update_in_min,
  40. :update_diff_in_min,
  41. :last_contact_at,
  42. :last_contact_agent_at,
  43. :last_contact_customer_at,
  44. :preferences
  45. history_attributes_ignored :create_article_type_id,
  46. :create_article_sender_id,
  47. :article_count,
  48. :preferences
  49. belongs_to :group, class_name: 'Group'
  50. has_many :articles, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update
  51. has_many :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', dependent: :destroy
  52. belongs_to :organization, class_name: 'Organization'
  53. belongs_to :state, class_name: 'Ticket::State'
  54. belongs_to :priority, class_name: 'Ticket::Priority'
  55. belongs_to :owner, class_name: 'User'
  56. belongs_to :customer, class_name: 'User'
  57. belongs_to :created_by, class_name: 'User'
  58. belongs_to :updated_by, class_name: 'User'
  59. belongs_to :create_article_type, class_name: 'Ticket::Article::Type'
  60. belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender'
  61. self.inheritance_column = nil
  62. attr_accessor :callback_loop
  63. =begin
  64. list of agents in group of ticket
  65. ticket = Ticket.find(123)
  66. result = ticket.agent_of_group
  67. returns
  68. result = [user1, user2, ...]
  69. =end
  70. def agent_of_group
  71. roles = Role.with_permissions('ticket.agent')
  72. role_ids = roles.map(&:id)
  73. Group.find(group_id)
  74. .users.where(active: true)
  75. .joins(:roles)
  76. .where('roles.id' => role_ids, 'roles.active' => true)
  77. .order('users.login')
  78. .uniq()
  79. end
  80. =begin
  81. get user access conditions
  82. conditions = Ticket.access_condition( User.find(1) )
  83. returns
  84. result = [user1, user2, ...]
  85. =end
  86. def self.access_condition(user)
  87. access_condition = []
  88. if user.permissions?('ticket.agent')
  89. group_ids = Group.select('groups.id').joins(:users)
  90. .where('groups_users.user_id = ?', user.id)
  91. .where('groups.active = ?', true)
  92. .map(&:id)
  93. access_condition = [ 'group_id IN (?)', group_ids ]
  94. else
  95. access_condition = if !user.organization || ( !user.organization.shared || user.organization.shared == false )
  96. [ 'tickets.customer_id = ?', user.id ]
  97. else
  98. [ '(tickets.customer_id = ? OR tickets.organization_id = ?)', user.id, user.organization.id ]
  99. end
  100. end
  101. access_condition
  102. end
  103. =begin
  104. processes tickets which have reached their pending time and sets next state_id
  105. processed_tickets = Ticket.process_pending
  106. returns
  107. processed_tickets = [<Ticket>, ...]
  108. =end
  109. def self.process_pending
  110. result = []
  111. # process pending action tickets
  112. pending_action = Ticket::StateType.find_by(name: 'pending action')
  113. ticket_states_pending_action = Ticket::State.where(state_type_id: pending_action)
  114. .where.not(next_state_id: nil)
  115. if !ticket_states_pending_action.empty?
  116. next_state_map = {}
  117. ticket_states_pending_action.each { |state|
  118. next_state_map[state.id] = state.next_state_id
  119. }
  120. tickets = where(state_id: next_state_map.keys)
  121. .where('pending_time <= ?', Time.zone.now)
  122. tickets.each { |ticket|
  123. Transaction.execute do
  124. ticket.state_id = next_state_map[ticket.state_id]
  125. ticket.updated_at = Time.zone.now
  126. ticket.updated_by_id = 1
  127. ticket.save!
  128. end
  129. result.push ticket
  130. }
  131. end
  132. # process pending reminder tickets
  133. pending_reminder = Ticket::StateType.find_by(name: 'pending reminder')
  134. ticket_states_pending_reminder = Ticket::State.where(state_type_id: pending_reminder)
  135. if !ticket_states_pending_reminder.empty?
  136. reminder_state_map = {}
  137. ticket_states_pending_reminder.each { |state|
  138. reminder_state_map[state.id] = state.next_state_id
  139. }
  140. tickets = where(state_id: reminder_state_map.keys)
  141. .where('pending_time <= ?', Time.zone.now)
  142. tickets.each { |ticket|
  143. article_id = nil
  144. article = Ticket::Article.last_customer_agent_article(ticket.id)
  145. if article
  146. article_id = article.id
  147. end
  148. # send notification
  149. Transaction::BackgroundJob.run(
  150. object: 'Ticket',
  151. type: 'reminder_reached',
  152. object_id: ticket.id,
  153. article_id: article_id,
  154. user_id: 1,
  155. )
  156. result.push ticket
  157. }
  158. end
  159. result
  160. end
  161. =begin
  162. processes escalated tickets
  163. processed_tickets = Ticket.process_escalation
  164. returns
  165. processed_tickets = [<Ticket>, ...]
  166. =end
  167. def self.process_escalation
  168. result = []
  169. # get max warning diff
  170. tickets = where('escalation_at <= ?', Time.zone.now + 15.minutes)
  171. tickets.each { |ticket|
  172. # get sla
  173. sla = ticket.escalation_calculation_get_sla
  174. article_id = nil
  175. article = Ticket::Article.last_customer_agent_article(ticket.id)
  176. if article
  177. article_id = article.id
  178. end
  179. # send escalation
  180. if ticket.escalation_at < Time.zone.now
  181. Transaction::BackgroundJob.run(
  182. object: 'Ticket',
  183. type: 'escalation',
  184. object_id: ticket.id,
  185. article_id: article_id,
  186. user_id: 1,
  187. )
  188. result.push ticket
  189. next
  190. end
  191. # check if warning need to be sent
  192. Transaction::BackgroundJob.run(
  193. object: 'Ticket',
  194. type: 'escalation_warning',
  195. object_id: ticket.id,
  196. article_id: article_id,
  197. user_id: 1,
  198. )
  199. result.push ticket
  200. }
  201. result
  202. end
  203. =begin
  204. merge tickets
  205. ticket = Ticket.find(123)
  206. result = ticket.merge_to(
  207. ticket_id: 123,
  208. user_id: 123,
  209. )
  210. returns
  211. result = true|false
  212. =end
  213. def merge_to(data)
  214. # update articles
  215. Transaction.execute do
  216. Ticket::Article.where(ticket_id: id).each(&:touch)
  217. # quiet update of reassign of articles
  218. Ticket::Article.where(ticket_id: id).update_all(['ticket_id = ?', data[:ticket_id]])
  219. # update history
  220. # create new merge article
  221. Ticket::Article.create(
  222. ticket_id: id,
  223. type_id: Ticket::Article::Type.lookup(name: 'note').id,
  224. sender_id: Ticket::Article::Sender.lookup(name: 'Agent').id,
  225. body: 'merged',
  226. internal: false,
  227. created_by_id: data[:user_id],
  228. updated_by_id: data[:user_id],
  229. )
  230. # add history to both
  231. # reassign links to the new ticket
  232. Link.where(
  233. link_object_source_id: Link::Object.find_by(name: 'Ticket').id,
  234. link_object_source_value: id,
  235. ).update_all(link_object_source_value: data[:ticket_id])
  236. Link.where(
  237. link_object_target_id: Link::Object.find_by(name: 'Ticket').id,
  238. link_object_target_value: id,
  239. ).update_all(link_object_target_value: data[:ticket_id])
  240. # link tickets
  241. Link.add(
  242. link_type: 'parent',
  243. link_object_source: 'Ticket',
  244. link_object_source_value: data[:ticket_id],
  245. link_object_target: 'Ticket',
  246. link_object_target_value: id
  247. )
  248. # set state to 'merged'
  249. self.state_id = Ticket::State.lookup(name: 'merged').id
  250. # rest owner
  251. self.owner_id = 1
  252. # save ticket
  253. save!
  254. # touch new ticket (to broadcast change)
  255. Ticket.find(data[:ticket_id]).touch
  256. end
  257. true
  258. end
  259. =begin
  260. check if online notifcation should be shown in general as already seen with current state
  261. ticket = Ticket.find(1)
  262. seen = ticket.online_notification_seen_state(user_id_check)
  263. returns
  264. result = true # or false
  265. check if online notifcation should be shown for this user as already seen with current state
  266. ticket = Ticket.find(1)
  267. seen = ticket.online_notification_seen_state(check_user_id)
  268. returns
  269. result = true # or false
  270. =end
  271. def online_notification_seen_state(user_id_check = nil)
  272. state = Ticket::State.lookup(id: state_id)
  273. state_type = Ticket::StateType.lookup(id: state.state_type_id)
  274. # always to set unseen for ticket owner
  275. if state_type.name != 'merged'
  276. if user_id_check
  277. return false if user_id_check == owner_id && user_id_check != updated_by_id
  278. end
  279. end
  280. # set all to seen if pending action state is a closed or merged state
  281. if state_type.name == 'pending action' && state.next_state_id
  282. state = Ticket::State.lookup(id: state.next_state_id)
  283. state_type = Ticket::StateType.lookup(id: state.state_type_id)
  284. end
  285. # set all to seen if new state is pending reminder state
  286. if state_type.name == 'pending reminder'
  287. if user_id_check
  288. return false if owner_id == 1
  289. return false if updated_by_id != owner_id && user_id_check == owner_id
  290. return true
  291. end
  292. return true
  293. end
  294. # set all to seen if new state is a closed or merged state
  295. return true if state_type.name == 'closed'
  296. return true if state_type.name == 'merged'
  297. false
  298. end
  299. =begin
  300. get count of tickets and tickets which match on selector
  301. ticket_count, tickets = Ticket.selectors(params[:condition], limit, current_user)
  302. =end
  303. def self.selectors(selectors, limit = 10, current_user = nil)
  304. raise 'no selectors given' if !selectors
  305. query, bind_params, tables = selector2sql(selectors, current_user)
  306. return [] if !query
  307. if !current_user
  308. ticket_count = Ticket.where(query, *bind_params).joins(tables).count
  309. tickets = Ticket.where(query, *bind_params).joins(tables).limit(limit)
  310. return [ticket_count, tickets]
  311. end
  312. access_condition = Ticket.access_condition(current_user)
  313. ticket_count = Ticket.where(access_condition).where(query, *bind_params).joins(tables).count
  314. tickets = Ticket.where(access_condition).where(query, *bind_params).joins(tables).limit(limit)
  315. [ticket_count, tickets]
  316. end
  317. =begin
  318. generate condition query to search for tickets based on condition
  319. query_condition, bind_condition, tables = selector2sql(params[:condition], current_user)
  320. condition example
  321. {
  322. 'ticket.title' => {
  323. operator: 'contains', # contains not
  324. value: 'some value',
  325. },
  326. 'ticket.state_id' => {
  327. operator: 'is',
  328. value: [1,2,5]
  329. },
  330. 'ticket.created_at' => {
  331. operator: 'after (absolute)', # after,before
  332. value: '2015-10-17T06:00:00.000Z',
  333. },
  334. 'ticket.created_at' => {
  335. operator: 'within next (relative)', # before,within,in,after
  336. range: 'day', # minute|hour|day|month|year
  337. value: '25',
  338. },
  339. 'ticket.owner_id' => {
  340. operator: 'is', # is not
  341. pre_condition: 'current_user.id',
  342. },
  343. 'ticket.owner_id' => {
  344. operator: 'is', # is not
  345. pre_condition: 'specific',
  346. value: 4711,
  347. },
  348. 'ticket.escalation_at' => {
  349. operator: 'is not', # not
  350. value: nil,
  351. },
  352. 'ticket.tags' => {
  353. operator: 'contains all', # contains all|contains one|contains all not|contains one not
  354. value: 'tag1, tag2',
  355. },
  356. }
  357. =end
  358. def self.selector2sql(selectors, current_user = nil)
  359. current_user_id = UserInfo.current_user_id
  360. if current_user
  361. current_user_id = current_user.id
  362. end
  363. return if !selectors
  364. # remember query and bind params
  365. query = ''
  366. bind_params = []
  367. like = Rails.application.config.db_like
  368. # get tables to join
  369. tables = ''
  370. selectors.each { |attribute, selector|
  371. selector = attribute.split(/\./)
  372. next if !selector[1]
  373. next if selector[0] == 'ticket'
  374. next if tables.include?(selector[0])
  375. if query != ''
  376. query += ' AND '
  377. end
  378. if selector[0] == 'customer'
  379. tables += ', users customers'
  380. query += 'tickets.customer_id = customers.id'
  381. elsif selector[0] == 'organization'
  382. tables += ', organizations'
  383. query += 'tickets.organization_id = organizations.id'
  384. elsif selector[0] == 'owner'
  385. tables += ', users owners'
  386. query += 'tickets.owner_id = owners.id'
  387. elsif selector[0] == 'article'
  388. tables += ', ticket_articles articles'
  389. query += 'tickets.id = articles.ticket_id'
  390. else
  391. raise "invalid selector #{attribute.inspect}->#{selector.inspect}"
  392. end
  393. }
  394. # add conditions
  395. selectors.each { |attribute, selector_raw|
  396. # validation
  397. raise "Invalid selector #{selector_raw.inspect}" if !selector_raw
  398. raise "Invalid selector #{selector_raw.inspect}" if !selector_raw.respond_to?(:key?)
  399. selector = selector_raw.stringify_keys
  400. raise "Invalid selector, operator missing #{selector.inspect}" if !selector['operator']
  401. # validate value / allow empty but only if pre_condition exists and is not specific
  402. if !selector.key?('value') || ((selector['value'].class == String || selector['value'].class == Array) && (selector['value'].respond_to?(:empty?) && selector['value'].empty?))
  403. return nil if selector['pre_condition'].nil?
  404. return nil if selector['pre_condition'].respond_to?(:empty?) && selector['pre_condition'].empty?
  405. return nil if selector['pre_condition'] == 'specific'
  406. end
  407. # validate pre_condition values
  408. return nil if selector['pre_condition'] && selector['pre_condition'] !~ /^(not_set|current_user\.|specific)/
  409. # get attributes
  410. attributes = attribute.split(/\./)
  411. attribute = "#{attributes[0]}s.#{attributes[1]}"
  412. if attributes[0] == 'ticket' && attributes[1] == 'tags'
  413. selector['value'] = selector['value'].split(/,/).collect(&:strip)
  414. end
  415. if query != ''
  416. query += ' AND '
  417. end
  418. if selector['operator'] == 'is'
  419. if selector['pre_condition'] == 'not_set'
  420. if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
  421. query += "#{attribute} IN (?)"
  422. bind_params.push 1
  423. else
  424. query += "#{attribute} IS NOT NULL"
  425. end
  426. elsif selector['pre_condition'] == 'current_user.id'
  427. raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
  428. query += "#{attribute} IN (?)"
  429. bind_params.push current_user_id
  430. elsif selector['pre_condition'] == 'current_user.organization_id'
  431. raise "Use current_user.id in selector, but no current_user is set #{selector.inspect}" if !current_user_id
  432. query += "#{attribute} IN (?)"
  433. user = User.lookup(id: current_user_id)
  434. bind_params.push user.organization_id
  435. else
  436. # rubocop:disable Style/IfInsideElse
  437. if selector['value'].nil?
  438. query += "#{attribute} IS NOT NULL"
  439. else
  440. query += "#{attribute} IN (?)"
  441. bind_params.push selector['value']
  442. end
  443. # rubocop:enable Style/IfInsideElse
  444. end
  445. elsif selector['operator'] == 'is not'
  446. if selector['pre_condition'] == 'not_set'
  447. if attributes[1] =~ /^(created_by|updated_by|owner|customer|user)_id/
  448. query += "#{attribute} NOT IN (?)"
  449. bind_params.push 1
  450. else
  451. query += "#{attribute} IS NULL"
  452. end
  453. elsif selector['pre_condition'] == 'current_user.id'
  454. query += "#{attribute} NOT IN (?)"
  455. bind_params.push current_user_id
  456. elsif selector['pre_condition'] == 'current_user.organization_id'
  457. query += "#{attribute} NOT IN (?)"
  458. user = User.lookup(id: current_user_id)
  459. bind_params.push user.organization_id
  460. else
  461. # rubocop:disable Style/IfInsideElse
  462. if selector['value'].nil?
  463. query += "#{attribute} IS NOT NULL"
  464. else
  465. query += "#{attribute} NOT IN (?)"
  466. bind_params.push selector['value']
  467. end
  468. # rubocop:enable Style/IfInsideElse
  469. end
  470. elsif selector['operator'] == 'contains'
  471. query += "#{attribute} #{like} (?)"
  472. value = "%#{selector['value']}%"
  473. bind_params.push value
  474. elsif selector['operator'] == 'contains not'
  475. query += "#{attribute} NOT #{like} (?)"
  476. value = "%#{selector['value']}%"
  477. bind_params.push value
  478. elsif selector['operator'] == 'contains all' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  479. query += "? = (
  480. SELECT
  481. COUNT(*)
  482. FROM
  483. tag_objects,
  484. tag_items,
  485. tags
  486. WHERE
  487. tickets.id = tags.o_id AND
  488. tag_objects.id = tags.tag_object_id AND
  489. tag_objects.name = 'Ticket' AND
  490. tag_items.id = tags.tag_item_id AND
  491. tag_items.name IN (?)
  492. )"
  493. bind_params.push selector['value'].count
  494. bind_params.push selector['value']
  495. elsif selector['operator'] == 'contains one' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  496. query += "1 <= (
  497. SELECT
  498. COUNT(*)
  499. FROM
  500. tag_objects,
  501. tag_items,
  502. tags
  503. WHERE
  504. tickets.id = tags.o_id AND
  505. tag_objects.id = tags.tag_object_id AND
  506. tag_objects.name = 'Ticket' AND
  507. tag_items.id = tags.tag_item_id AND
  508. tag_items.name IN (?)
  509. )"
  510. bind_params.push selector['value']
  511. elsif selector['operator'] == 'contains all not' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  512. query += "0 = (
  513. SELECT
  514. COUNT(*)
  515. FROM
  516. tag_objects,
  517. tag_items,
  518. tags
  519. WHERE
  520. tickets.id = tags.o_id AND
  521. tag_objects.id = tags.tag_object_id AND
  522. tag_objects.name = 'Ticket' AND
  523. tag_items.id = tags.tag_item_id AND
  524. tag_items.name IN (?)
  525. )"
  526. bind_params.push selector['value']
  527. elsif selector['operator'] == 'contains one not' && attributes[0] == 'ticket' && attributes[1] == 'tags'
  528. query += "(
  529. SELECT
  530. COUNT(*)
  531. FROM
  532. tag_objects,
  533. tag_items,
  534. tags
  535. WHERE
  536. tickets.id = tags.o_id AND
  537. tag_objects.id = tags.tag_object_id AND
  538. tag_objects.name = 'Ticket' AND
  539. tag_items.id = tags.tag_item_id AND
  540. tag_items.name IN (?)
  541. ) BETWEEN ? AND ?"
  542. bind_params.push selector['value']
  543. bind_params.push selector['value'].count - 1
  544. bind_params.push selector['value'].count
  545. elsif selector['operator'] == 'before (absolute)'
  546. query += "#{attribute} <= ?"
  547. bind_params.push selector['value']
  548. elsif selector['operator'] == 'after (absolute)'
  549. query += "#{attribute} >= ?"
  550. bind_params.push selector['value']
  551. elsif selector['operator'] == 'within last (relative)'
  552. query += "#{attribute} >= ?"
  553. time = nil
  554. if selector['range'] == 'minute'
  555. time = Time.zone.now - selector['value'].to_i.minutes
  556. elsif selector['range'] == 'hour'
  557. time = Time.zone.now - selector['value'].to_i.hours
  558. elsif selector['range'] == 'day'
  559. time = Time.zone.now - selector['value'].to_i.days
  560. elsif selector['range'] == 'month'
  561. time = Time.zone.now - selector['value'].to_i.months
  562. elsif selector['range'] == 'year'
  563. time = Time.zone.now - selector['value'].to_i.years
  564. else
  565. raise "Unknown selector attributes '#{selector.inspect}'"
  566. end
  567. bind_params.push time
  568. elsif selector['operator'] == 'within next (relative)'
  569. query += "#{attribute} <= ?"
  570. time = nil
  571. if selector['range'] == 'minute'
  572. time = Time.zone.now + selector['value'].to_i.minutes
  573. elsif selector['range'] == 'hour'
  574. time = Time.zone.now + selector['value'].to_i.hours
  575. elsif selector['range'] == 'day'
  576. time = Time.zone.now + selector['value'].to_i.days
  577. elsif selector['range'] == 'month'
  578. time = Time.zone.now + selector['value'].to_i.months
  579. elsif selector['range'] == 'year'
  580. time = Time.zone.now + selector['value'].to_i.years
  581. else
  582. raise "Unknown selector attributes '#{selector.inspect}'"
  583. end
  584. bind_params.push time
  585. elsif selector['operator'] == 'before (relative)'
  586. query += "#{attribute} <= ?"
  587. time = nil
  588. if selector['range'] == 'minute'
  589. time = Time.zone.now - selector['value'].to_i.minutes
  590. elsif selector['range'] == 'hour'
  591. time = Time.zone.now - selector['value'].to_i.hours
  592. elsif selector['range'] == 'day'
  593. time = Time.zone.now - selector['value'].to_i.days
  594. elsif selector['range'] == 'month'
  595. time = Time.zone.now - selector['value'].to_i.months
  596. elsif selector['range'] == 'year'
  597. time = Time.zone.now - selector['value'].to_i.years
  598. else
  599. raise "Unknown selector attributes '#{selector.inspect}'"
  600. end
  601. bind_params.push time
  602. elsif selector['operator'] == 'after (relative)'
  603. query += "#{attribute} >= ?"
  604. time = nil
  605. if selector['range'] == 'minute'
  606. time = Time.zone.now + selector['value'].to_i.minutes
  607. elsif selector['range'] == 'hour'
  608. time = Time.zone.now + selector['value'].to_i.hours
  609. elsif selector['range'] == 'day'
  610. time = Time.zone.now + selector['value'].to_i.days
  611. elsif selector['range'] == 'month'
  612. time = Time.zone.now + selector['value'].to_i.months
  613. elsif selector['range'] == 'year'
  614. time = Time.zone.now + selector['value'].to_i.years
  615. else
  616. raise "Unknown selector attributes '#{selector.inspect}'"
  617. end
  618. bind_params.push time
  619. else
  620. raise "Invalid operator '#{selector['operator']}' for '#{selector['value'].inspect}'"
  621. end
  622. }
  623. [query, bind_params, tables]
  624. end
  625. =begin
  626. perform changes on ticket
  627. ticket.perform_changes({}, 'trigger', item, current_user_id)
  628. =end
  629. def perform_changes(perform, perform_origin, item = nil, current_user_id = nil)
  630. logger.debug "Perform #{perform_origin} #{perform.inspect} on Ticket.find(#{id})"
  631. changed = false
  632. perform.each do |key, value|
  633. (object_name, attribute) = key.split('.', 2)
  634. raise "Unable to update object #{object_name}.#{attribute}, only can update tickets and send notifications!" if object_name != 'ticket' && object_name != 'notification'
  635. # send notification
  636. if object_name == 'notification'
  637. # value['recipient'] was a string in the past (single-select) so we convert it to array if needed
  638. value_recipient = value['recipient']
  639. if !value_recipient.is_a?(Array)
  640. value_recipient = [value_recipient]
  641. end
  642. recipients_raw = []
  643. value_recipient.each { |recipient|
  644. if recipient == 'article_last_sender'
  645. if item && item[:article_id]
  646. article = Ticket::Article.lookup(id: item[:article_id])
  647. if article.reply_to.present?
  648. recipients_raw.push(article.reply_to)
  649. elsif article.from.present?
  650. recipients_raw.push(article.from)
  651. elsif article.origin_by_id
  652. email = User.lookup(id: article.origin_by_id).email
  653. recipients_raw.push(email)
  654. elsif article.created_by_id
  655. email = User.lookup(id: article.created_by_id).email
  656. recipients_raw.push(email)
  657. end
  658. end
  659. elsif recipient == 'ticket_customer'
  660. email = User.lookup(id: customer_id).email
  661. recipients_raw.push(email)
  662. elsif recipient == 'ticket_owner'
  663. email = User.lookup(id: owner_id).email
  664. recipients_raw.push(email)
  665. elsif recipient == 'ticket_agents'
  666. agent_of_group.each { |user|
  667. recipients_raw.push(user.email)
  668. }
  669. else
  670. logger.error "Unknown email notification recipient '#{recipient}'"
  671. next
  672. end
  673. }
  674. recipients_checked = []
  675. recipients_raw.each { |recipient_email|
  676. # send notifications only to email adresses
  677. next if !recipient_email
  678. next if recipient_email !~ /@/
  679. # check if address is valid
  680. begin
  681. recipient_email = Mail::Address.new(recipient_email).address
  682. rescue
  683. next # because unable to parse
  684. end
  685. # do not sent notifications to this recipients
  686. send_no_auto_response_reg_exp = Setting.get('send_no_auto_response_reg_exp')
  687. begin
  688. next if recipient_email =~ /#{send_no_auto_response_reg_exp}/i
  689. rescue => e
  690. logger.error "ERROR: Invalid regex '#{send_no_auto_response_reg_exp}' in setting send_no_auto_response_reg_exp"
  691. logger.error 'ERROR: ' + e.inspect
  692. next if recipient_email =~ /(mailer-daemon|postmaster|abuse|root)@.+?\..+?/i
  693. end
  694. # check if notification should be send because of customer emails
  695. if item && item[:article_id]
  696. article = Ticket::Article.lookup(id: item[:article_id])
  697. if article && article.preferences['is-auto-response'] == true && article.from && article.from =~ /#{Regexp.quote(recipient_email)}/i
  698. logger.info "Send not trigger based notification to #{recipient_email} because of auto response tagged incoming email"
  699. next
  700. end
  701. end
  702. email = recipient_email.downcase.strip
  703. next if recipients_checked.include?(email)
  704. recipients_checked.push(email)
  705. }
  706. next if recipients_checked.blank?
  707. recipient_string = recipients_checked.join(', ')
  708. group = self.group
  709. next if !group
  710. email_address = group.email_address
  711. if !email_address
  712. logger.info "Unable to send trigger based notification to #{recipient_string} because no email address is set for group '#{group.name}'"
  713. next
  714. end
  715. if !email_address.channel_id
  716. logger.info "Unable to send trigger based notification to #{recipient_string} because no channel is set for email address '#{email_address.email}' (id: #{email_address.id})"
  717. next
  718. end
  719. objects = {
  720. ticket: self,
  721. article: articles.last,
  722. }
  723. # get subject
  724. subject = NotificationFactory::Mailer.template(
  725. templateInline: value['subject'],
  726. locale: 'en-en',
  727. objects: objects,
  728. quote: false,
  729. )
  730. subject = subject_build(subject)
  731. body = NotificationFactory::Mailer.template(
  732. templateInline: value['body'],
  733. locale: 'en-en',
  734. objects: objects,
  735. quote: true,
  736. )
  737. Ticket::Article.create(
  738. ticket_id: id,
  739. to: recipient_string,
  740. subject: subject,
  741. content_type: 'text/html',
  742. body: body,
  743. internal: false,
  744. sender: Ticket::Article::Sender.find_by(name: 'System'),
  745. type: Ticket::Article::Type.find_by(name: 'email'),
  746. preferences: {
  747. perform_origin: perform_origin,
  748. },
  749. updated_by_id: 1,
  750. created_by_id: 1,
  751. )
  752. next
  753. end
  754. # update tags
  755. if key == 'ticket.tags'
  756. next if value['value'].blank?
  757. tags = value['value'].split(/,/)
  758. if value['operator'] == 'add'
  759. tags.each { |tag|
  760. tag_add(tag)
  761. }
  762. elsif value['operator'] == 'remove'
  763. tags.each { |tag|
  764. tag_remove(tag)
  765. }
  766. else
  767. logger.error "Unknown #{attribute} operator #{value['operator']}"
  768. end
  769. next
  770. end
  771. # lookup pre_condition
  772. if value['pre_condition']
  773. if value['pre_condition'] =~ /^not_set/
  774. value['value'] = 1
  775. elsif value['pre_condition'] =~ /^current_user\./
  776. raise 'Unable to use current_user, got no current_user_id for ticket.perform_changes' if !current_user_id
  777. value['value'] = current_user_id
  778. end
  779. end
  780. # update ticket
  781. next if self[attribute].to_s == value['value'].to_s
  782. changed = true
  783. self[attribute] = value['value']
  784. logger.debug "set #{object_name}.#{attribute} = #{value['value'].inspect}"
  785. end
  786. return if !changed
  787. save
  788. end
  789. =begin
  790. get all email references headers of a ticket, to exclude some, parse it as array into method
  791. references = ticket.get_references
  792. result
  793. ['message-id-1234', 'message-id-5678']
  794. ignore references header(s)
  795. references = ticket.get_references(['message-id-5678'])
  796. result
  797. ['message-id-1234']
  798. =end
  799. def get_references(ignore = [])
  800. references = []
  801. Ticket::Article.select('in_reply_to, message_id').where(ticket_id: id).each { |article|
  802. if !article.in_reply_to.empty?
  803. references.push article.in_reply_to
  804. end
  805. next if !article.message_id
  806. next if article.message_id.empty?
  807. references.push article.message_id
  808. }
  809. ignore.each { |item|
  810. references.delete(item)
  811. }
  812. references
  813. end
  814. =begin
  815. get all articles of a ticket in correct order (overwrite active record default method)
  816. artilces = ticket.articles
  817. result
  818. [article1, articl2]
  819. =end
  820. def articles
  821. Ticket::Article.where(ticket_id: id).order(:created_at, :id)
  822. end
  823. def history_get(fulldata = false)
  824. list = History.list(self.class.name, self['id'], 'Ticket::Article')
  825. return list if !fulldata
  826. # get related objects
  827. assets = {}
  828. list.each { |item|
  829. record = Kernel.const_get(item['object']).find(item['o_id'])
  830. assets = record.assets(assets)
  831. if item['related_object']
  832. record = Kernel.const_get(item['related_object']).find( item['related_o_id'])
  833. assets = record.assets(assets)
  834. end
  835. }
  836. {
  837. history: list,
  838. assets: assets,
  839. }
  840. end
  841. private
  842. def check_generate
  843. return if number
  844. self.number = Ticket::Number.generate
  845. end
  846. def check_title
  847. return if !title
  848. title.gsub!(/\s|\t|\r/, ' ')
  849. end
  850. def check_defaults
  851. if !owner_id
  852. self.owner_id = 1
  853. end
  854. return if !customer_id
  855. customer = User.find_by(id: customer_id)
  856. return if !customer
  857. return if organization_id == customer.organization_id
  858. self.organization_id = customer.organization_id
  859. end
  860. def reset_pending_time
  861. # ignore if no state has changed
  862. return if !changes['state_id']
  863. # check if new state isn't pending*
  864. current_state = Ticket::State.lookup(id: state_id)
  865. current_state_type = Ticket::StateType.lookup(id: current_state.state_type_id)
  866. # in case, set pending_time to nil
  867. return if current_state_type.name =~ /^pending/i
  868. self.pending_time = nil
  869. end
  870. def check_escalation_update
  871. escalation_calculation
  872. true
  873. end
  874. def destroy_dependencies
  875. # delete articles
  876. articles.destroy_all
  877. # destroy online notifications
  878. OnlineNotification.remove(self.class.to_s, id)
  879. end
  880. def set_default_state
  881. return if state_id
  882. default_ticket_state = Ticket::State.find_by(default_create: true)
  883. return if !default_ticket_state
  884. self.state_id = default_ticket_state.id
  885. end
  886. def set_default_priority
  887. return if priority_id
  888. default_ticket_priority = Ticket::Priority.find_by(default_create: true)
  889. return if !default_ticket_priority
  890. self.priority_id = default_ticket_priority.id
  891. end
  892. end