ticket.rb 35 KB

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