ticket.rb 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. # Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/
  2. class Ticket < ApplicationModel
  3. include CanBeImported
  4. include HasActivityStreamLog
  5. include ChecksClientNotification
  6. include CanCsvImport
  7. include ChecksHtmlSanitized
  8. include ChecksHumanChanges
  9. include HasHistory
  10. include HasTags
  11. include HasSearchIndexBackend
  12. include HasOnlineNotifications
  13. include HasLinks
  14. include HasObjectManagerAttributes
  15. include HasTaskbars
  16. include Ticket::CallsStatsTicketReopenLog
  17. include Ticket::EnqueuesUserTicketCounterJob
  18. include Ticket::ResetsPendingTimeSeconds
  19. include Ticket::SetsCloseTime
  20. include Ticket::SetsOnlineNotificationSeen
  21. include Ticket::TouchesAssociations
  22. include Ticket::TriggersSubscriptions
  23. include Ticket::ChecksReopenAfterCertainTime
  24. include Ticket::Checklists
  25. include ::Ticket::Escalation
  26. include ::Ticket::Subject
  27. include ::Ticket::Assets
  28. include ::Ticket::SearchIndex
  29. include ::Ticket::CanSelector
  30. include ::Ticket::Search
  31. include ::Ticket::MergeHistory
  32. include ::Ticket::PerformChanges
  33. store :preferences
  34. after_initialize :check_defaults, if: :new_record?
  35. before_create :check_generate, :check_defaults, :check_title, :set_default_state, :set_default_priority
  36. before_update :check_defaults, :check_title, :reset_pending_time, :check_owner_active
  37. # This must be loaded late as it depends on the internal before_create and before_update handlers of ticket.rb.
  38. include Ticket::SetsLastOwnerUpdateTime
  39. # workflow checks should run after before_create and before_update callbacks
  40. # the transaction dispatcher must be run after the workflow checks!
  41. include ChecksCoreWorkflow
  42. include HasTransactionDispatcher
  43. validates :group_id, presence: true
  44. activity_stream_permission 'ticket.agent'
  45. core_workflow_screens 'create_middle', 'edit', 'overview_bulk'
  46. core_workflow_admin_screens 'create_middle', 'edit'
  47. taskbar_entities 'TicketZoom', 'TicketCreate'
  48. taskbar_ignore_state_updates_entities 'TicketZoom'
  49. activity_stream_attributes_ignored :organization_id, # organization_id will change automatically on user update
  50. :create_article_type_id,
  51. :create_article_sender_id,
  52. :article_count,
  53. :first_response_at,
  54. :first_response_escalation_at,
  55. :first_response_in_min,
  56. :first_response_diff_in_min,
  57. :close_at,
  58. :close_escalation_at,
  59. :close_in_min,
  60. :close_diff_in_min,
  61. :update_escalation_at,
  62. :update_in_min,
  63. :update_diff_in_min,
  64. :last_close_at,
  65. :last_contact_at,
  66. :last_contact_agent_at,
  67. :last_contact_customer_at,
  68. :last_owner_update_at,
  69. :preferences
  70. search_index_attributes_relevant :organization_id,
  71. :group_id,
  72. :state_id,
  73. :priority_id
  74. history_attributes_ignored :create_article_type_id,
  75. :create_article_sender_id,
  76. :article_count,
  77. :preferences
  78. history_relation_object 'Ticket::Article', 'Mention', 'Ticket::SharedDraftZoom', 'Checklist', 'Checklist::Item'
  79. validates :note, length: { maximum: 250 }
  80. sanitized_html :note
  81. belongs_to :group, optional: true
  82. belongs_to :organization, optional: true
  83. has_many :articles, -> { reorder(:created_at, :id) }, class_name: 'Ticket::Article', after_add: :cache_update, after_remove: :cache_update, dependent: :destroy, inverse_of: :ticket
  84. has_many :ticket_time_accounting, class_name: 'Ticket::TimeAccounting', dependent: :destroy, inverse_of: :ticket
  85. has_many :mentions, as: :mentionable, dependent: :destroy
  86. has_one :shared_draft, class_name: 'Ticket::SharedDraftZoom', inverse_of: :ticket, dependent: :destroy
  87. belongs_to :state, class_name: 'Ticket::State', optional: true
  88. belongs_to :priority, class_name: 'Ticket::Priority', optional: true
  89. belongs_to :owner, class_name: 'User', optional: true
  90. belongs_to :customer, class_name: 'User', optional: true
  91. belongs_to :created_by, class_name: 'User', optional: true
  92. belongs_to :updated_by, class_name: 'User', optional: true
  93. belongs_to :create_article_type, class_name: 'Ticket::Article::Type', optional: true
  94. belongs_to :create_article_sender, class_name: 'Ticket::Article::Sender', optional: true
  95. association_attributes_ignored :flags, :mentions
  96. attr_accessor :callback_loop
  97. =begin
  98. processes tickets which have reached their pending time and sets next state_id
  99. processed_tickets = Ticket.process_pending
  100. returns
  101. processed_tickets = [<Ticket>, ...]
  102. =end
  103. def self.process_pending
  104. result = []
  105. # process pending action tickets
  106. pending_action = Ticket::StateType.find_by(name: 'pending action')
  107. ticket_states_pending_action = Ticket::State.where(state_type_id: pending_action)
  108. .where.not(next_state_id: nil)
  109. if ticket_states_pending_action.present?
  110. next_state_map = {}
  111. ticket_states_pending_action.each do |state|
  112. next_state_map[state.id] = state.next_state_id
  113. end
  114. where(state_id: next_state_map.keys, pending_time: ..Time.current)
  115. .find_each(batch_size: 500) do |ticket|
  116. Transaction.execute do
  117. ticket.state_id = next_state_map[ticket.state_id]
  118. ticket.updated_at = Time.zone.now
  119. ticket.updated_by_id = 1
  120. ticket.save!
  121. end
  122. result.push ticket
  123. end
  124. end
  125. # process pending reminder tickets
  126. pending_reminder = Ticket::StateType.find_by(name: 'pending reminder')
  127. ticket_states_pending_reminder = Ticket::State.where(state_type_id: pending_reminder)
  128. if ticket_states_pending_reminder.present?
  129. reminder_state_map = {}
  130. ticket_states_pending_reminder.each do |state|
  131. reminder_state_map[state.id] = state.next_state_id
  132. end
  133. where(state_id: reminder_state_map.keys, pending_time: ..Time.current)
  134. .find_each(batch_size: 500) do |ticket|
  135. article_id = nil
  136. article = Ticket::Article.last_customer_agent_article(ticket.id)
  137. if article
  138. article_id = article.id
  139. end
  140. # send notification
  141. TransactionJob.perform_now(
  142. object: 'Ticket',
  143. type: 'reminder_reached',
  144. object_id: ticket.id,
  145. article_id: article_id,
  146. user_id: 1,
  147. )
  148. result.push ticket
  149. end
  150. end
  151. result
  152. end
  153. def auto_assign(user)
  154. return if !persisted?
  155. return if Setting.get('ticket_auto_assignment').blank?
  156. return if owner_id != 1
  157. return if !TicketPolicy.new(user, self).full?
  158. user_ids_ignore = Array(Setting.get('ticket_auto_assignment_user_ids_ignore')).map(&:to_i)
  159. return if user_ids_ignore.include?(user.id)
  160. ticket_auto_assignment_selector = Setting.get('ticket_auto_assignment_selector')
  161. return if ticket_auto_assignment_selector.blank?
  162. condition = ticket_auto_assignment_selector[:condition].merge(
  163. 'ticket.id' => {
  164. 'operator' => 'is',
  165. 'value' => id,
  166. }
  167. )
  168. ticket_count, = Ticket.selectors(condition, limit: 1, current_user: user, access: 'full')
  169. return if ticket_count.to_i.zero?
  170. update!(owner: user)
  171. end
  172. =begin
  173. processes escalated tickets
  174. processed_tickets = Ticket.process_escalation
  175. returns
  176. processed_tickets = [<Ticket>, ...]
  177. =end
  178. def self.process_escalation
  179. result = []
  180. # fetch all escalated and soon to be escalating tickets
  181. where(escalation_at: ..15.minutes.from_now)
  182. .find_each(batch_size: 500) do |ticket|
  183. article_id = nil
  184. article = Ticket::Article.last_customer_agent_article(ticket.id)
  185. if article
  186. article_id = article.id
  187. end
  188. # send escalation
  189. if ticket.escalation_at < Time.zone.now
  190. TransactionJob.perform_now(
  191. object: 'Ticket',
  192. type: 'escalation',
  193. object_id: ticket.id,
  194. article_id: article_id,
  195. user_id: 1,
  196. )
  197. result.push ticket
  198. next
  199. end
  200. # check if warning needs to be sent
  201. TransactionJob.perform_now(
  202. object: 'Ticket',
  203. type: 'escalation_warning',
  204. object_id: ticket.id,
  205. article_id: article_id,
  206. user_id: 1,
  207. )
  208. result.push ticket
  209. end
  210. result
  211. end
  212. =begin
  213. processes tickets which auto unassign time has reached
  214. processed_tickets = Ticket.process_auto_unassign
  215. returns
  216. processed_tickets = [<Ticket>, ...]
  217. =end
  218. def self.process_auto_unassign
  219. # process pending action tickets
  220. state_ids = Ticket::State.by_category_ids(:work_on)
  221. return [] if state_ids.blank?
  222. result = []
  223. groups = Group.where(active: true).where('assignment_timeout IS NOT NULL AND groups.assignment_timeout != 0')
  224. return [] if groups.blank?
  225. groups.each do |group|
  226. next if group.assignment_timeout.blank?
  227. ticket_ids = Ticket.where('state_id IN (?) AND owner_id != 1 AND group_id = ? AND last_owner_update_at IS NOT NULL', state_ids, group.id).limit(600).pluck(:id)
  228. ticket_ids.each do |ticket_id|
  229. ticket = Ticket.find_by(id: ticket_id)
  230. next if !ticket
  231. minutes_since_last_assignment = Time.zone.now - ticket.last_owner_update_at
  232. next if (minutes_since_last_assignment / 60) <= group.assignment_timeout
  233. Transaction.execute do
  234. ticket.owner_id = 1
  235. ticket.updated_at = Time.zone.now
  236. ticket.updated_by_id = 1
  237. ticket.save!
  238. end
  239. result.push ticket
  240. end
  241. end
  242. result
  243. end
  244. =begin
  245. merge tickets
  246. ticket = Ticket.find(123)
  247. result = ticket.merge_to(
  248. ticket_id: 123,
  249. user_id: 123,
  250. )
  251. returns
  252. result = true|false
  253. =end
  254. def merge_to(data)
  255. # prevent cross merging tickets
  256. target_ticket = Ticket.find_by(id: data[:ticket_id])
  257. raise 'no target ticket given' if !target_ticket
  258. raise Exceptions::UnprocessableEntity, __('It is not possible to merge into an already merged ticket.') if target_ticket.state.state_type.name == 'merged'
  259. # check different ticket ids
  260. raise Exceptions::UnprocessableEntity, __('A ticket cannot be merged into itself.') if id == target_ticket.id
  261. # update articles
  262. Transaction.execute context: 'merge' do
  263. Ticket::Article.where(ticket_id: id).each(&:touch)
  264. # quiet update of reassign of articles
  265. Ticket::Article.where(ticket_id: id).update_all(['ticket_id = ?', data[:ticket_id]]) # rubocop:disable Rails/SkipsModelValidations
  266. # mark target ticket as updated
  267. # otherwise the "received_merge" history entry
  268. # will be the same as the last updated_at
  269. # which might be a long time ago
  270. target_ticket.updated_at = Time.zone.now
  271. # add merge event to both ticket's history (Issue #2469 - Add information "Ticket merged" to History)
  272. target_ticket.history_log(
  273. 'received_merge',
  274. data[:user_id],
  275. id_to: target_ticket.id,
  276. id_from: id,
  277. )
  278. history_log(
  279. 'merged_into',
  280. data[:user_id],
  281. id_to: target_ticket.id,
  282. id_from: id,
  283. )
  284. # create new merge article
  285. Ticket::Article.create(
  286. ticket_id: id,
  287. type_id: Ticket::Article::Type.lookup(name: 'note').id,
  288. sender_id: Ticket::Article::Sender.lookup(name: 'Agent').id,
  289. body: 'merged',
  290. internal: false,
  291. created_by_id: data[:user_id],
  292. updated_by_id: data[:user_id],
  293. )
  294. # search for mention duplicates and destroy them before moving mentions
  295. Mention.duplicates(self, target_ticket).destroy_all
  296. Mention.where(mentionable: self).update_all(mentionable_id: target_ticket.id) # rubocop:disable Rails/SkipsModelValidations
  297. # reassign links to the new ticket
  298. # rubocop:disable Rails/SkipsModelValidations
  299. ticket_source_id = Link::Object.find_by(name: 'Ticket').id
  300. # search for all duplicate source and target links and destroy them
  301. # before link merging
  302. Link.duplicates(
  303. object1_id: ticket_source_id,
  304. object1_value: id,
  305. object2_value: data[:ticket_id]
  306. ).destroy_all
  307. Link.where(
  308. link_object_source_id: ticket_source_id,
  309. link_object_source_value: id,
  310. ).update_all(link_object_source_value: data[:ticket_id])
  311. Link.where(
  312. link_object_target_id: ticket_source_id,
  313. link_object_target_value: id,
  314. ).update_all(link_object_target_value: data[:ticket_id])
  315. # rubocop:enable Rails/SkipsModelValidations
  316. # link tickets
  317. Link.add(
  318. link_type: 'parent',
  319. link_object_source: 'Ticket',
  320. link_object_source_value: data[:ticket_id],
  321. link_object_target: 'Ticket',
  322. link_object_target_value: id
  323. )
  324. # external sync references
  325. ExternalSync.migrate('Ticket', id, target_ticket.id)
  326. # set state to 'merged'
  327. state_type = Ticket::StateType.lookup(name: 'merged')
  328. self.state_id = Ticket::State.lookup(state_type_id: state_type.id).id
  329. # rest owner
  330. self.owner_id = 1
  331. # save ticket
  332. save!
  333. # touch new ticket (to broadcast change)
  334. target_ticket.touch # rubocop:disable Rails/SkipsModelValidations
  335. EventBuffer.add('transaction', {
  336. object: target_ticket.class.name,
  337. type: 'update.received_merge',
  338. data: target_ticket,
  339. changes: {},
  340. id: target_ticket.id,
  341. user_id: UserInfo.current_user_id,
  342. created_at: Time.zone.now,
  343. })
  344. EventBuffer.add('transaction', {
  345. object: self.class.name,
  346. type: 'update.merged_into',
  347. data: self,
  348. changes: {},
  349. id: id,
  350. user_id: UserInfo.current_user_id,
  351. created_at: Time.zone.now,
  352. })
  353. end
  354. true
  355. end
  356. =begin
  357. perform active triggers on ticket
  358. Ticket.perform_triggers(ticket, article, triggers, item, triggers, options)
  359. =end
  360. def self.perform_triggers(ticket, article, triggers, item, options = {})
  361. recursive = Setting.get('ticket_trigger_recursive')
  362. type = options[:type] || item[:type]
  363. local_options = options.clone
  364. local_options[:type] = type
  365. local_options[:reset_user_id] = true
  366. local_options[:disable] = ['Transaction::Notification']
  367. local_options[:trigger_ids] ||= {}
  368. local_options[:trigger_ids][ticket.id.to_s] ||= []
  369. local_options[:loop_count] ||= 0
  370. local_options[:loop_count] += 1
  371. ticket_trigger_recursive_max_loop = Setting.get('ticket_trigger_recursive_max_loop')&.to_i || 10
  372. if local_options[:loop_count] > ticket_trigger_recursive_max_loop
  373. message = "Stopped perform_triggers for this object (Ticket/#{ticket.id}), because loop count was #{local_options[:loop_count]}!"
  374. logger.info { message }
  375. return [false, message]
  376. end
  377. return [true, __('No triggers active')] if triggers.blank?
  378. # check if notification should be send because of customer emails
  379. send_notification = true
  380. if local_options[:send_notification] == false
  381. send_notification = false
  382. elsif item[:article_id]
  383. article = Ticket::Article.lookup(id: item[:article_id])
  384. if article&.preferences && article.preferences['send-auto-response'] == false
  385. send_notification = false
  386. end
  387. end
  388. Transaction.execute(local_options) do
  389. triggers.each do |trigger|
  390. logger.debug { "Probe trigger (#{trigger.name}/#{trigger.id}) for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  391. user_id = ticket.updated_by_id
  392. if article
  393. user_id = article.updated_by_id
  394. end
  395. user = User.lookup(id: user_id)
  396. # verify is condition is matching
  397. ticket_count, tickets = Ticket.selectors(
  398. trigger.condition,
  399. limit: 1,
  400. execution_time: true,
  401. current_user: user,
  402. access: 'ignore',
  403. ticket_action: type,
  404. ticket_id: ticket.id,
  405. article_id: article&.id,
  406. changes: item[:changes],
  407. changes_required: trigger.condition_changes_required?
  408. )
  409. next if ticket_count.blank?
  410. next if ticket_count.zero?
  411. next if tickets.take.id != ticket.id
  412. return [true, message] if recursive == false && local_options[:loop_count] > 1
  413. if article && send_notification == false && trigger.perform['notification.email'] && trigger.perform['notification.email']['recipient']
  414. recipient = trigger.perform['notification.email']['recipient']
  415. local_options[:send_notification] = false
  416. if recipient.include?('ticket_customer') || recipient.include?('article_last_sender')
  417. logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because sender do not want to get auto responder for object (Ticket/#{ticket.id}/Article/#{article.id})" }
  418. next
  419. end
  420. end
  421. if local_options[:trigger_ids][ticket.id.to_s].include?(trigger.id)
  422. logger.info { "Skip trigger (#{trigger.name}/#{trigger.id}) because was already executed for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  423. next
  424. end
  425. local_options[:trigger_ids][ticket.id.to_s].push trigger.id
  426. logger.info { "Execute trigger (#{trigger.name}/#{trigger.id}) for this object (Ticket:#{ticket.id}/Loop:#{local_options[:loop_count]})" }
  427. ticket.perform_changes(trigger, 'trigger', item, user_id, activator_type: type)
  428. if recursive == true
  429. TransactionDispatcher.commit(local_options)
  430. end
  431. end
  432. end
  433. [true, ticket, local_options]
  434. end
  435. =begin
  436. get all email references headers of a ticket, to exclude some, parse it as array into method
  437. references = ticket.get_references
  438. result
  439. ['message-id-1234', 'message-id-5678']
  440. ignore references header(s)
  441. references = ticket.get_references(['message-id-5678'])
  442. result
  443. ['message-id-1234']
  444. =end
  445. # limited by 32kb (https://github.com/zammad/zammad/issues/5334)
  446. # https://learn.microsoft.com/en-us/office365/servicedescriptions/exchange-online-service-description/exchange-online-limits
  447. def get_references(ignore = [], max_length: 30_000)
  448. references = []
  449. counter = 0
  450. Ticket::Article.select('in_reply_to, message_id').where(ticket_id: id).reorder(id: :desc).each do |article|
  451. new_references = []
  452. if article.message_id.present?
  453. new_references.push article.message_id
  454. end
  455. if article.in_reply_to.present?
  456. new_references.push article.in_reply_to
  457. end
  458. new_references -= ignore
  459. counter += new_references.join.length
  460. break if counter > max_length
  461. references.unshift(*new_references)
  462. end
  463. references
  464. end
  465. # Get whichever #last_contact_* was later
  466. # This is not identical to #last_contact_at
  467. # It returns time to last original (versus follow up) contact
  468. # @return [Time, nil]
  469. def last_original_update_at
  470. [last_contact_agent_at, last_contact_customer_at].compact.max
  471. end
  472. # true if conversation did happen and agent responded
  473. # false if customer is waiting for response or agent reached out and customer did not respond yet
  474. # @return [Bool]
  475. def agent_responded?
  476. return false if last_contact_customer_at.blank?
  477. return false if last_contact_agent_at.blank?
  478. last_contact_customer_at < last_contact_agent_at
  479. end
  480. =begin
  481. Get the color of the state the current ticket is in
  482. ticket.current_state_color
  483. returns a hex color code
  484. =end
  485. def current_state_color
  486. return '#f35912' if escalation_at && escalation_at < Time.zone.now
  487. case state.state_type.name
  488. when 'new', 'open'
  489. return '#faab00'
  490. when 'closed'
  491. return '#38ad69'
  492. when 'pending reminder'
  493. return '#faab00' if pending_time && pending_time < Time.zone.now
  494. end
  495. '#000000'
  496. end
  497. def mention_user_ids
  498. mentions.pluck(:user_id)
  499. end
  500. private
  501. def check_generate
  502. return true if number
  503. self.number = Ticket::Number.generate
  504. true
  505. end
  506. def check_title
  507. return true if !title
  508. title.gsub!(%r{\s|\t|\r}, ' ')
  509. true
  510. end
  511. def check_defaults
  512. check_default_owner
  513. check_default_organization
  514. true
  515. end
  516. def check_default_owner
  517. return if !has_attribute?(:owner_id)
  518. return if owner_id || owner
  519. self.owner_id = 1
  520. end
  521. def check_default_organization
  522. return if !has_attribute?(:organization_id)
  523. return if !customer_id
  524. customer = User.find_by(id: customer_id)
  525. return if !customer
  526. return if organization_id.present? && customer.organization_id?(organization_id)
  527. return if organization.present? && customer.organization_id?(organization.id)
  528. self.organization_id = customer.organization_id
  529. end
  530. def reset_pending_time
  531. # ignore if no state has changed
  532. return true if !changes_to_save['state_id']
  533. # ignore if new state is blank and
  534. # let handle ActiveRecord the error
  535. return if state_id.blank?
  536. # check if new state isn't pending*
  537. current_state = Ticket::State.lookup(id: state_id)
  538. current_state_type = Ticket::StateType.lookup(id: current_state.state_type_id)
  539. # in case, set pending_time to nil
  540. return true if current_state_type.name.match?(%r{^pending}i)
  541. self.pending_time = nil
  542. true
  543. end
  544. def set_default_state
  545. return true if state_id
  546. default_ticket_state = Ticket::State.find_by(default_create: true)
  547. return true if !default_ticket_state
  548. self.state_id = default_ticket_state.id
  549. true
  550. end
  551. def set_default_priority
  552. return true if priority_id
  553. default_ticket_priority = Ticket::Priority.find_by(default_create: true)
  554. return true if !default_ticket_priority
  555. self.priority_id = default_ticket_priority.id
  556. true
  557. end
  558. def check_owner_active
  559. return true if Setting.get('import_mode')
  560. # only change the owner for non closed Tickets for historical/reporting reasons
  561. return true if state.present? && Ticket::StateType.lookup(id: state.state_type_id)&.name == 'closed'
  562. # return when ticket is unassigned
  563. return true if owner_id.blank?
  564. return true if owner_id == 1
  565. # return if owner is active, is agent and has access to group of ticket
  566. return true if owner.active? && owner.permissions?('ticket.agent') && owner.group_access?(group_id, 'full')
  567. # else set the owner of the ticket to the default user as unassigned
  568. self.owner_id = 1
  569. true
  570. end
  571. end