job.rb 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class Job < ApplicationModel
  3. include ChecksClientNotification
  4. include ChecksConditionValidation
  5. load 'job/assets.rb'
  6. include Job::Assets
  7. store :timeplan
  8. store :condition
  9. store :perform
  10. validates :name, presence: true
  11. before_create :updated_matching, :update_next_run_at
  12. before_update :updated_matching, :update_next_run_at
  13. def self.run
  14. jobs = Job.where(active: true, running: false)
  15. jobs.each do |job|
  16. logger.debug "Execute job #{job.inspect}"
  17. next if !job.executable?
  18. matching = job.matching_count
  19. if job.matching != matching
  20. job.matching = matching
  21. job.save
  22. end
  23. next if !job.in_timeplan?
  24. # find tickets to change
  25. ticket_count, tickets = Ticket.selectors(job.condition, 2_000)
  26. logger.debug "Job #{job.name} with #{ticket_count} tickets"
  27. job.processed = ticket_count || 0
  28. job.running = true
  29. job.save
  30. if tickets
  31. tickets.each do |ticket|
  32. Transaction.execute(disable_notification: job.disable_notification, reset_user_id: true) do
  33. ticket.perform_changes(job.perform, 'job')
  34. end
  35. end
  36. end
  37. job.running = false
  38. job.last_run_at = Time.zone.now
  39. job.save
  40. end
  41. true
  42. end
  43. def executable?
  44. return false if !active
  45. # only execute jobs, older then 1 min, to give admin posibility to change
  46. return false if updated_at > Time.zone.now - 1.minute
  47. # check if jobs need to be executed
  48. # ignore if job was running within last 10 min.
  49. return false if last_run_at && last_run_at > Time.zone.now - 10.minutes
  50. true
  51. end
  52. def in_timeplan?(time = Time.zone.now)
  53. day_map = {
  54. 0 => 'Sun',
  55. 1 => 'Mon',
  56. 2 => 'Tue',
  57. 3 => 'Wed',
  58. 4 => 'Thu',
  59. 5 => 'Fri',
  60. 6 => 'Sat',
  61. }
  62. # check day
  63. return false if !timeplan['days']
  64. return false if !timeplan['days'][day_map[time.wday]]
  65. # check hour
  66. return false if !timeplan['hours']
  67. return false if !timeplan['hours'][time.hour.to_s] && !timeplan['hours'][time.hour]
  68. # check min
  69. return false if !timeplan['minutes']
  70. return false if !timeplan['minutes'][match_minutes(time.min).to_s] && !timeplan['minutes'][match_minutes(time.min)]
  71. true
  72. end
  73. def matching_count
  74. ticket_count, tickets = Ticket.selectors(condition, 1)
  75. ticket_count || 0
  76. end
  77. def next_run_at_calculate(time = Time.zone.now)
  78. if last_run_at
  79. diff = time - last_run_at
  80. if diff.positive?
  81. time = time + 10.minutes
  82. end
  83. end
  84. day_map = {
  85. 0 => 'Sun',
  86. 1 => 'Mon',
  87. 2 => 'Tue',
  88. 3 => 'Wed',
  89. 4 => 'Thu',
  90. 5 => 'Fri',
  91. 6 => 'Sat',
  92. }
  93. return nil if !active
  94. return nil if !timeplan['days']
  95. return nil if !timeplan['hours']
  96. return nil if !timeplan['minutes']
  97. # loop week days
  98. (0..7).each do |day_counter|
  99. time_to_check = nil
  100. day_to_check = if day_counter.zero?
  101. time
  102. else
  103. time + 1.day
  104. end
  105. if !timeplan['days'][day_map[day_to_check.wday]]
  106. # start on next day at 00:00:00
  107. time = day_to_check - day_to_check.sec.seconds
  108. time = time - day_to_check.min.minutes
  109. time = time - day_to_check.hour.hours
  110. next
  111. end
  112. min = day_to_check.min
  113. if min < 9
  114. min = 0
  115. elsif min < 20
  116. min = 10
  117. elsif min < 30
  118. min = 20
  119. elsif min < 40
  120. min = 30
  121. elsif min < 50
  122. min = 40
  123. elsif min < 60
  124. min = 50
  125. end
  126. # move to [0-5]0:00 time stamps
  127. day_to_check = day_to_check - day_to_check.min.minutes + min.minutes
  128. day_to_check = day_to_check - day_to_check.sec.seconds
  129. # loop minutes till next full hour
  130. if day_to_check.min.nonzero?
  131. (0..5).each do |minute_counter|
  132. if minute_counter.nonzero?
  133. break if day_to_check.min.zero?
  134. day_to_check = day_to_check + 10.minutes
  135. end
  136. next if !timeplan['hours'][day_to_check.hour] && !timeplan['hours'][day_to_check.hour.to_s]
  137. next if !timeplan['minutes'][match_minutes(day_to_check.min)] && !timeplan['minutes'][match_minutes(day_to_check.min).to_s]
  138. return day_to_check
  139. end
  140. end
  141. # loop hours
  142. hour_to_check = nil
  143. (0..23).each do |hour_counter|
  144. hour_to_check = day_to_check + hour_counter.hours
  145. # start on next day
  146. if hour_to_check.day != day_to_check.day
  147. time = day_to_check - day_to_check.hour.hours
  148. break
  149. end
  150. # ignore not configured hours
  151. next if !timeplan['hours'][hour_to_check.hour] && !timeplan['hours'][hour_to_check.hour.to_s]
  152. return nil if !hour_to_check
  153. # loop minutes
  154. minute_to_check = nil
  155. (0..5).each do |minute_counter|
  156. minute_to_check = hour_to_check + minute_counter.minutes * 10
  157. next if !timeplan['minutes'][match_minutes(minute_to_check.min)] && !timeplan['minutes'][match_minutes(minute_to_check.min).to_s]
  158. time_to_check = minute_to_check
  159. break
  160. end
  161. next if !minute_to_check
  162. return time_to_check
  163. end
  164. end
  165. nil
  166. end
  167. private
  168. def updated_matching
  169. self.matching = matching_count
  170. true
  171. end
  172. def update_next_run_at
  173. self.next_run_at = next_run_at_calculate
  174. true
  175. end
  176. def match_minutes(minutes)
  177. return 0 if minutes < 10
  178. "#{minutes.to_s.gsub(/(\d)\d/, '\\1')}0".to_i
  179. end
  180. end