job.rb 6.1 KB

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