escalation.rb 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. class Escalation
  2. attr_reader :ticket
  3. def initialize(ticket, force: false)
  4. @ticket = ticket
  5. @force = force
  6. end
  7. def preferences
  8. @preferences ||= Escalation::TicketPreferences.new(ticket)
  9. end
  10. def biz
  11. @biz ||= calendar&.biz breaks: biz_breaks
  12. end
  13. def biz_breaks
  14. @biz_breaks ||= Escalation::TicketBizBreak.new(ticket, calendar).biz_breaks
  15. end
  16. def escalation_disabled?
  17. @escalation_disabled ||= Ticket::State.lookup(id: ticket.state_id).ignore_escalation?
  18. end
  19. def sla
  20. @sla ||= Sla.for_ticket(ticket)
  21. end
  22. def calendar
  23. @calendar ||= sla&.calendar
  24. end
  25. def forced?
  26. !!@force
  27. end
  28. def force!
  29. @force = true
  30. end
  31. def calculatable?
  32. !escalation_disabled? || preferences.close_at_changed?(ticket) || preferences.last_contact_at_changed?(ticket)
  33. end
  34. def calculate!
  35. calculate
  36. ticket.save! if ticket.has_changes_to_save?
  37. end
  38. def calculate
  39. if !calculatable? && !forced?
  40. calculate_not_calculatable
  41. elsif !calendar
  42. calculate_no_calendar
  43. elsif forced? || any_changes?
  44. enforce_if_needed
  45. update_escalations
  46. update_statistics
  47. apply_preferences
  48. end
  49. end
  50. def any_changes?
  51. preferences.any_changes?(ticket, sla, escalation_disabled?)
  52. end
  53. def assign_reset
  54. ticket.assign_attributes(
  55. escalation_at: nil,
  56. first_response_escalation_at: nil,
  57. update_escalation_at: nil,
  58. close_escalation_at: nil
  59. )
  60. end
  61. def calculate_not_calculatable
  62. assign_reset
  63. apply_preferences if !preferences.hash[:escalation_disabled]
  64. end
  65. def calculate_no_calendar
  66. assign_reset
  67. end
  68. def apply_preferences
  69. preferences.update_preferences(ticket, sla, escalation_disabled?)
  70. end
  71. def enforce_if_needed
  72. return if !preferences.sla_changed?(sla) && !preferences.calendar_changed?(sla.calendar)
  73. force!
  74. end
  75. def update_escalations
  76. ticket.assign_attributes [escalation_first_response, escalation_update, escalation_close]
  77. .compact
  78. .each_with_object({}) { |elem, memo| memo.merge!(elem) }
  79. ticket.escalation_at = calculate_next_escalation
  80. end
  81. def update_statistics
  82. ticket.assign_attributes [statistics_first_response, statistics_update, statistics_close]
  83. .compact
  84. .each_with_object({}) { |elem, memo| memo.merge!(elem) }
  85. end
  86. private
  87. # escalation
  88. # skip escalation neither forced
  89. # nor state switched from closed to open
  90. def skip_escalation?
  91. !forced? && !preferences.escalation_became_enabled?(escalation_disabled?)
  92. end
  93. def escalation_first_response
  94. return if skip_escalation? && !preferences.first_response_at_changed?(ticket)
  95. nullify = escalation_disabled? || ticket.first_response_at.present?
  96. {
  97. first_response_escalation_at: nullify ? nil : calculate_time(ticket.created_at, sla.first_response_time)
  98. }
  99. end
  100. def escalation_update
  101. return if skip_escalation? && !preferences.last_update_at_changed?(ticket)
  102. nullify = escalation_disabled? || ticket.agent_responded?
  103. timestamp = nullify ? nil : ticket.last_contact_customer_at
  104. {
  105. update_escalation_at: timestamp ? calculate_time(timestamp, sla.update_time) : nil
  106. }
  107. end
  108. def escalation_close
  109. return if skip_escalation? && !preferences.close_at_changed?(ticket)
  110. nullify = escalation_disabled? || ticket.close_at.present?
  111. {
  112. close_escalation_at: nullify ? nil : calculate_time(ticket.created_at, sla.solution_time)
  113. }
  114. end
  115. def calculate_time(start_time, span)
  116. return if span.nil? || !span.positive?
  117. Escalation::DestinationTime.new(start_time, span, biz).destination_time
  118. end
  119. def calculate_next_escalation
  120. return if escalation_disabled?
  121. [
  122. (ticket.first_response_escalation_at if !ticket.first_response_at),
  123. ticket.update_escalation_at,
  124. (ticket.close_escalation_at if !ticket.close_at)
  125. ].compact.min
  126. end
  127. # statistics
  128. def skip_statistics_first_response?
  129. return true if !forced? && !preferences.first_response_at_changed?(ticket)
  130. ticket.first_response_at.blank? || sla.first_response_time.blank?
  131. end
  132. def statistics_first_response
  133. return if skip_statistics_first_response?
  134. minutes = calculate_minutes(ticket.created_at, ticket.first_response_at)
  135. {
  136. first_response_in_min: minutes,
  137. first_response_diff_in_min: minutes ? (sla.first_response_time - minutes) : nil
  138. }
  139. end
  140. def skip_statistics_update?
  141. return true if !forced? && !preferences.last_update_at_changed?(ticket)
  142. return true if !sla.update_time
  143. !ticket.agent_responded?
  144. end
  145. # ATTENTION: Recalculation after SLA change won't happen
  146. # SLA change will cause wrong statistics in some edge cases.
  147. # Since this changes `update_in_min` calculation to retain longest timespan.
  148. # But it does not keep track of previous update times.
  149. def statistics_update_applicable?(minutes)
  150. ticket.update_in_min.blank? || minutes > ticket.update_in_min # keep longest timespan
  151. end
  152. def statistics_update
  153. return if skip_statistics_update?
  154. minutes = calculate_minutes(ticket.last_contact_customer_at, ticket.last_contact_agent_at)
  155. return if !forced? && !statistics_update_applicable?(minutes)
  156. {
  157. update_in_min: minutes,
  158. update_diff_in_min: minutes ? (sla.update_time - minutes) : nil
  159. }
  160. end
  161. def skip_statistics_close?
  162. return true if !forced? && !preferences.close_at_changed?(ticket)
  163. ticket.close_at.blank? || sla.solution_time.blank?
  164. end
  165. def statistics_close
  166. return if skip_statistics_close?
  167. minutes = calculate_minutes(ticket.created_at, ticket.close_at)
  168. {
  169. close_in_min: minutes,
  170. close_diff_in_min: minutes ? (sla.solution_time - minutes) : nil
  171. }
  172. end
  173. def calculate_minutes(start_time, end_time)
  174. return if !end_time || !start_time
  175. Escalation::PeriodWorkingMinutes.new(start_time, end_time, ticket, biz).period_working_minutes
  176. end
  177. end