FEATURE: Ability to expire policy monthly, quarterly, yearly (#11)

FEATURE: Ability to expire policy monthly, quarterly, yearly (#11)

That feature was described here: https://meta.discourse.org/t/discourse-policy/88557/36

In general, we need an ability to expire policy monthly, quartely an yearly.

To achieve that, the policy is now accepting as renew param:

  • integer - number of days
  • monthly
  • quarterly
  • yearly
diff --git a/app/models/post_policy.rb b/app/models/post_policy.rb
index 0ffe76c..ce012aa 100644
--- a/app/models/post_policy.rb
+++ b/app/models/post_policy.rb
@@ -5,6 +5,8 @@ class PostPolicy < ActiveRecord::Base
   belongs_to :group
   has_many :policy_users
 
+  enum renew_interval: { monthly: 0, quarterly: 1, yearly: 2 }
+
   def accepted_by
     return [] if !policy_group
     policy_users
diff --git a/db/migrate/20200312233001_add_renew_interval_to_post_policies.rb b/db/migrate/20200312233001_add_renew_interval_to_post_policies.rb
new file mode 100644
index 0000000..6fce4f8
--- /dev/null
+++ b/db/migrate/20200312233001_add_renew_interval_to_post_policies.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddRenewIntervalToPostPolicies < ActiveRecord::Migration[6.0]
+  def up
+    add_column :post_policies, :renew_interval, :integer
+  end
+
+  def down
+    remove_column :post_policies, :renew_interval
+  end
+end
diff --git a/jobs/scheduled/check_policy.rb b/jobs/scheduled/check_policy.rb
index ca267ab..77a274a 100644
--- a/jobs/scheduled/check_policy.rb
+++ b/jobs/scheduled/check_policy.rb
@@ -42,30 +42,49 @@ module Jobs
       PostPolicy.where("next_renew_at < ?", Time.zone.now).find_each do |policy|
         policy.policy_users.accepted.where("accepted_at < ?", policy.next_renew_at).update_all(expired_at: Time.zone.now)
         next_renew = policy.renew_start
-        if policy.renew_days < 1
+        if policy.renew_days.to_i < 1 && !PostPolicy.renew_intervals.keys.include?(policy.renew_interval)
           Rails.logger.warn("Invalid policy on post #{policy.post_id}")
         else
           while next_renew < Time.zone.now
-            next_renew += policy.renew_days.days
+            next_renew = calculate_next_renew_date(next_renew, policy)
           end
         end
         policy.update(next_renew_at: next_renew)
       end
 
-      DB.exec <<~SQL, now: Time.zone.now
+      DB.exec <<~SQL, now: Time.zone.now, monthly: PostPolicy.renew_intervals['monthly'], quarterly: PostPolicy.renew_intervals['quarterly'], yearly: PostPolicy.renew_intervals['yearly']
         UPDATE policy_users pu
            SET expired_at = :now
           FROM post_policies pp
          WHERE pp.id = pu.post_policy_id
            AND pp.renew_start IS NULL
-           AND pp.renew_days  IS NOT NULL
+           AND (pp.renew_days  IS NOT NULL OR pp.renew_interval IS NOT NULL)
            AND pu.accepted_at IS NOT NULL
            AND pu.expired_at  IS NULL
            AND pu.revoked_at  IS NULL
-           AND pu.accepted_at < :now::timestamp - (INTERVAL '1 day' * pp.renew_days)
+           AND
+           (
+             (pp.renew_days IS NOT NULL AND pu.accepted_at < :now::timestamp - (INTERVAL '1 day' * pp.renew_days::integer)) OR
+             (pp.renew_interval = :monthly AND pu.accepted_at < :now::timestamp - (INTERVAL '1 month')) OR
+             (pp.renew_interval = :quarterly AND pu.accepted_at < :now::timestamp - (INTERVAL '1 month' * 3)) OR
+             (pp.renew_interval = :yearly AND pu.accepted_at < :now::timestamp - (INTERVAL '1 year'))
+           )
       SQL
     end
 
+    def calculate_next_renew_date(date, policy)
+      case policy.renew_interval
+      when 'monthly'
+        date + 1.month
+      when 'quarterly'
+        date + 3.months
+      when 'yearly'
+        date + 1.year
+      else
+        date + policy.renew_days.to_i.days
+      end
+    end
+
     def missing_users(post)
       post.post_policy.not_accepted_by
     end
diff --git a/plugin.rb b/plugin.rb
index e499bf0..4637e09 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -121,8 +121,11 @@ after_initialize do
           end
         end
 
-        if (renew_days = policy["data-renew"].to_i) > 0
-          post_policy.renew_days = renew_days
+        renew_days = policy["data-renew"]
+        if (renew_days.to_i) > 0 || PostPolicy.renew_intervals.keys.include?(renew_days)
+          post_policy.renew_days = PostPolicy.renew_intervals.keys.include?(renew_days) ? nil : renew_days
+          post_policy.renew_interval = post_policy.renew_days.present? ? nil : renew_days
+
           post_policy.renew_start = nil
 
           if (renew_start = policy["data-renew-start"])
diff --git a/spec/lib/check_policy_spec.rb b/spec/lib/check_policy_spec.rb
index 6f1d9d7..b0571d1 100644
--- a/spec/lib/check_policy_spec.rb
+++ b/spec/lib/check_policy_spec.rb
@@ -148,6 +148,86 @@ describe DiscoursePolicy::CheckPolicy do
     expect(post.post_policy.accepted_by.sort).to eq([user2])
   end
 
+  %w(monthly quarterly yearly).each do |how_often|
+    it "sets correctly next_renew_at for #{how_often} when renew-start is set" do
+      period =
+        case how_often
+        when 'monthly'
+          1.month
+        when 'quarterly'
+          3.months
+        when 'yearly'
+          12.months
+        end
+      freeze_time Time.utc(2020, 10, 16)
+
+      raw = <<~MD
+     [policy group=#{group.name} renew=#{how_often} renew-start="17-10-2020"]
+     I always open **doors**!
+     [/policy]
+      MD
+
+      post = create_post(raw: raw, user: Fabricate(:admin))
+
+      accept_policy(post)
+
+      freeze_time Time.utc(2020, 10, 17)
+
+      DiscoursePolicy::CheckPolicy.new.execute
+
+      post.reload
+      expect(post.post_policy.accepted_by.sort).to eq([user1, user2])
+
+      freeze_time Time.utc(2020, 10, 18)
+
+      DiscoursePolicy::CheckPolicy.new.execute
+
+      post.reload
+      expect(post.post_policy.accepted_by.sort).to eq([])
+      expect(post.post_policy.next_renew_at.to_s).to eq((Time.utc(2020, 10, 17) + period).to_s)
+    end
+  end
+
+  %w(monthly quarterly yearly).each do |how_often|
+    it "expires policies when #{how_often}" do
+      period =
+        case how_often
+        when 'monthly'
+          1.month
+        when 'quarterly'
+          3.months
+        when 'yearly'
+          12.months
+        end
+      freeze_time Time.utc(2020, 10, 16)
+
+      raw = <<~MD
+     [policy group=#{group.name} renew=#{how_often}]
+     I always open **doors**!
+     [/policy]
+      MD
+
+      post = create_post(raw: raw, user: Fabricate(:admin))
+
+      accept_policy(post)
+
+      freeze_time Time.utc(2020, 10, 30)
+
+      DiscoursePolicy::CheckPolicy.new.execute
+
+      post.reload
+      expect(post.post_policy.accepted_by.sort).to eq([user1, user2])
+
+      freeze_time Time.utc(2020, 10, 16) + period + 1.day
+
+      DiscoursePolicy::CheckPolicy.new.execute
+
+      post.reload
+      expect(post.post_policy.accepted_by.sort).to eq([])
+      expect(post.post_policy.renew_start).to eq(nil)
+    end
+  end
+
   it "will correctly notify users" do
     SiteSetting.queue_jobs = false
     freeze_time

GitHub sha: 105ad4af

This commit appears in #11 which was merged by lis2.