# frozen_string_literal: true module DiscourseAutomation module Triggers module Recurring RECURRENCE_CHOICES = [ { id: "minute", name: "discourse_automation.triggerables.recurring.frequencies.minute" }, { id: "hour", name: "discourse_automation.triggerables.recurring.frequencies.hour" }, { id: "day", name: "discourse_automation.triggerables.recurring.frequencies.day" }, { id: "weekday", name: "discourse_automation.triggerables.recurring.frequencies.weekday" }, { id: "week", name: "discourse_automation.triggerables.recurring.frequencies.week" }, { id: "month", name: "discourse_automation.triggerables.recurring.frequencies.month" }, { id: "year", name: "discourse_automation.triggerables.recurring.frequencies.year" }, ] def self.setup_pending_automation(automation, fields, previous_fields) start_date = fields.dig("start_date", "value") interval = fields.dig("recurrence", "value", "interval") frequency = fields.dig("recurrence", "value", "frequency") # this case is not possible in practice but better be safe if !start_date || !interval || !frequency automation.pending_automations.destroy_all return end previous_start_date = previous_fields&.dig("start_date", "value") previous_interval = previous_fields&.dig("recurrence", "value", "interval") previous_frequency = previous_fields&.dig("recurrence", "value", "frequency") if previous_start_date != start_date || previous_interval != interval || previous_frequency != frequency automation.pending_automations.destroy_all elsif automation.pending_automations.present? return end start_date = Time.parse(start_date) byday = start_date.strftime("%A").upcase[0, 2] interval = interval.to_i interval_end = interval + 1 next_trigger_date = case frequency when "minute" (Time.zone.now + interval.minute).beginning_of_minute when "hour" (Time.zone.now + interval.hour).beginning_of_hour when "day" RRule::Rule .new("FREQ=DAILY;INTERVAL=#{interval}", dtstart: start_date) .between(Time.now, interval_end.days.from_now) .first when "weekday" max_weekends = (interval_end.to_f / 5).ceil RRule::Rule .new("FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR", dtstart: start_date) .between(Time.now.end_of_day, max_weekends.weeks.from_now) .drop(interval - 1) .first when "week" RRule::Rule .new("FREQ=WEEKLY;INTERVAL=#{interval};BYDAY=#{byday}", dtstart: start_date) .between(Time.now.end_of_week, interval_end.weeks.from_now) .first when "month" count = 0 (start_date.beginning_of_month.to_date..start_date.end_of_month.to_date).each do |date| count += 1 if date.strftime("%A") == start_date.strftime("%A") break if date.day == start_date.day end RRule::Rule .new("FREQ=MONTHLY;INTERVAL=#{interval};BYDAY=#{count}#{byday}", dtstart: start_date) .between(Time.now, interval_end.months.from_now) .first when "year" RRule::Rule .new("FREQ=YEARLY;INTERVAL=#{interval}", dtstart: start_date) .between(Time.now, interval_end.years.from_now) .first end if next_trigger_date && next_trigger_date > Time.zone.now automation.pending_automations.create!(execute_at: next_trigger_date) end end end end end DiscourseAutomation::Triggerable.add(DiscourseAutomation::Triggers::RECURRING) do field :recurrence, component: :period, extra: { content: DiscourseAutomation::Triggers::Recurring::RECURRENCE_CHOICES, }, required: true field :start_date, component: :date_time, required: true on_update do |automation, fields, previous_fields| DiscourseAutomation::Triggers::Recurring.setup_pending_automation( automation, fields, previous_fields, ) end on_call do |automation, fields, previous_fields| DiscourseAutomation::Triggers::Recurring.setup_pending_automation( automation, fields, previous_fields, ) end enable_manual_trigger end