ticket.rb 33 KB

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