job.rb 6.1 KB

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