Redo notification system
Previously, saucy generated each member's future notification every day (marked as scheduled). And every day saucy would deliver every unsent notification scheduled for that day. This means that each member with expiration date in the future had ~6 notifications scheduled for them in the database. This was troublesome because if the cron job that delivers notification was down or the server was down for more than 24h, notifications were silently skipped and we had no easy way of detecting it: we had to check every member for missing sent notifications. It also had the disadvantage that we were deleting and recreating hundreds of database entries for no good reason. To fix this, the new system no longer stores future scheduled notifications. Instead, it now only stores sent notifications and notifications currently being delivered. Every day, the deliver task checks every member if there's a notification that needs to be sent in that day, by checking if we're past the date of sending a particular notification type and checking if in that window of time no notifications of that type have been sent. Let's suppose we send 6 notifications, one per month starting 60 days before the membership expires until 90 days after: -60d -30d t=0 30d 60d 90d ----|------|------|------|------|------|------ A B C D E F If we're on day t=-60d, we need to deliver a notification of type A. But first we check if no notifications of type A have been sent to that member on that day. From days t=-59d to t=-31d, we check if in the time range t=[-60d, -31d] any notification of type A was sent to that member. If not, we need to deliver it. If we're on days t=[-30d, -1d], we do the same but for notification of type B.
This commit is contained in:
parent
336550fcbb
commit
76df4d41b7
@ -31,6 +31,12 @@ class MembersController < ApplicationController
|
|||||||
@member.generate_missing_ifthenpay_links!
|
@member.generate_missing_ifthenpay_links!
|
||||||
@member.reset_status!
|
@member.reset_status!
|
||||||
NotificationMailer.with(member: @member).registration.deliver_now!
|
NotificationMailer.with(member: @member).registration.deliver_now!
|
||||||
|
@member.notifications.create!(
|
||||||
|
template: "registration",
|
||||||
|
to_be_sent_on: Date.today,
|
||||||
|
sent_at: Time.current,
|
||||||
|
status: "sent",
|
||||||
|
)
|
||||||
|
|
||||||
redirect_to @member, notice: "Member was successfully created."
|
redirect_to @member, notice: "Member was successfully created."
|
||||||
else
|
else
|
||||||
@ -40,6 +46,12 @@ class MembersController < ApplicationController
|
|||||||
|
|
||||||
def resend_registration
|
def resend_registration
|
||||||
NotificationMailer.with(member: @member).registration.deliver_now!
|
NotificationMailer.with(member: @member).registration.deliver_now!
|
||||||
|
@member.notifications.create!(
|
||||||
|
template: "registration",
|
||||||
|
to_be_sent_on: Date.today,
|
||||||
|
sent_at: Time.current,
|
||||||
|
status: "sent",
|
||||||
|
)
|
||||||
|
|
||||||
redirect_to @member, notice: "Payment reminder sent."
|
redirect_to @member, notice: "Payment reminder sent."
|
||||||
end
|
end
|
||||||
|
@ -4,8 +4,7 @@ class NotificationsController < ApplicationController
|
|||||||
|
|
||||||
# GET /notifications
|
# GET /notifications
|
||||||
def index
|
def index
|
||||||
@scheduled = Notification.where(status: 'scheduled').order(to_be_sent_on: :asc)
|
@notifications = Notification.order(to_be_sent_on: :asc)
|
||||||
@sent = Notification.where(status: 'sent').order(sent_at: :desc)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /notifications/1/deliver
|
# POST /notifications/1/deliver
|
||||||
|
@ -2,32 +2,32 @@ class NotificationMailer < ApplicationMailer
|
|||||||
default to: ->() { @member.email }
|
default to: ->() { @member.email }
|
||||||
|
|
||||||
def expiration_in_60d
|
def expiration_in_60d
|
||||||
set_notification
|
set_member
|
||||||
mail
|
mail
|
||||||
end
|
end
|
||||||
|
|
||||||
def expiration_in_30d
|
def expiration_in_30d
|
||||||
set_notification
|
set_member
|
||||||
mail
|
mail
|
||||||
end
|
end
|
||||||
|
|
||||||
def expired
|
def expired
|
||||||
set_notification
|
set_member
|
||||||
mail
|
mail
|
||||||
end
|
end
|
||||||
|
|
||||||
def expired_30d_ago
|
def expired_30d_ago
|
||||||
set_notification
|
set_member
|
||||||
mail
|
mail
|
||||||
end
|
end
|
||||||
|
|
||||||
def expired_60d_ago
|
def expired_60d_ago
|
||||||
set_notification
|
set_member
|
||||||
mail
|
mail
|
||||||
end
|
end
|
||||||
|
|
||||||
def cancelled
|
def cancelled
|
||||||
set_notification
|
set_member
|
||||||
mail
|
mail
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -47,12 +47,6 @@ class NotificationMailer < ApplicationMailer
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def set_notification
|
|
||||||
@notification = params[:notification]
|
|
||||||
@member = @notification.member
|
|
||||||
@payment = @notification.member.create_payment
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_contribution
|
def set_contribution
|
||||||
@contribution = params[:contribution]
|
@contribution = params[:contribution]
|
||||||
@member = @contribution.member
|
@member = @contribution.member
|
||||||
|
@ -65,7 +65,6 @@ class Member < ApplicationRecord
|
|||||||
if contribution.save
|
if contribution.save
|
||||||
self.handle_new_contribution(contribution, overriden_expires_on)
|
self.handle_new_contribution(contribution, overriden_expires_on)
|
||||||
self.reset_status!
|
self.reset_status!
|
||||||
self.regenerate_notifications
|
|
||||||
|
|
||||||
if should_send_notification
|
if should_send_notification
|
||||||
if is_first_contribution
|
if is_first_contribution
|
||||||
@ -73,11 +72,23 @@ class Member < ApplicationRecord
|
|||||||
.with(contribution: contribution)
|
.with(contribution: contribution)
|
||||||
.first_payment_confirmation
|
.first_payment_confirmation
|
||||||
.deliver_now!
|
.deliver_now!
|
||||||
|
self.notifications.create!(
|
||||||
|
template: "first_payment_confirmation",
|
||||||
|
to_be_sent_on: Date.today,
|
||||||
|
sent_at: Time.current,
|
||||||
|
status: "sent",
|
||||||
|
)
|
||||||
else
|
else
|
||||||
NotificationMailer
|
NotificationMailer
|
||||||
.with(contribution: contribution)
|
.with(contribution: contribution)
|
||||||
.payment_renewal_confirmation
|
.payment_renewal_confirmation
|
||||||
.deliver_now!
|
.deliver_now!
|
||||||
|
self.notifications.create!(
|
||||||
|
template: "payment_renewal_confirmation",
|
||||||
|
to_be_sent_on: Date.today,
|
||||||
|
sent_at: Time.current,
|
||||||
|
status: "sent",
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -99,24 +110,29 @@ class Member < ApplicationRecord
|
|||||||
save!
|
save!
|
||||||
end
|
end
|
||||||
|
|
||||||
def regenerate_notifications(from=Date.today)
|
def todays_notification
|
||||||
notifications.where(status: 'scheduled').delete_all
|
candidate = Notification
|
||||||
|
.templates
|
||||||
|
.filter { |t| self.expires_on + t[:to_be_sent_on] <= Date.today }
|
||||||
|
.last
|
||||||
|
|
||||||
dates = notifications.pluck(:to_be_sent_on)
|
if candidate
|
||||||
|
last_sent = self
|
||||||
|
.notifications
|
||||||
|
.where(status: 'sent', template: candidate[:template])
|
||||||
|
.where.not(sent_at: nil)
|
||||||
|
.order(:sent_at)
|
||||||
|
.last
|
||||||
|
|
||||||
return if expires_on.nil?
|
if last_sent && self.expires_on + candidate[:to_be_sent_on] <= last_sent.sent_at
|
||||||
|
nil
|
||||||
[
|
else
|
||||||
{ to_be_sent_on: expires_on - 60.days, template: "expiration_in_60d" },
|
self.notifications.new(
|
||||||
{ to_be_sent_on: expires_on - 30.days, template: "expiration_in_30d" },
|
template: candidate[:template],
|
||||||
{ to_be_sent_on: expires_on + 0.days, template: "expired" },
|
status: 'scheduled',
|
||||||
{ to_be_sent_on: expires_on + 30.days, template: "expired_30d_ago" },
|
to_be_sent_on: Date.today,
|
||||||
{ to_be_sent_on: expires_on + 60.days, template: "expired_60d_ago" },
|
)
|
||||||
{ to_be_sent_on: expires_on + Config.payment_pending_grace_period, template: "cancelled" },
|
end
|
||||||
].reject do |n|
|
|
||||||
n[:to_be_sent_on].before?(from) || dates.include?(n[:to_be_sent_on])
|
|
||||||
end.each do |n|
|
|
||||||
notifications.create(n.merge(status: "scheduled"))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -137,24 +153,6 @@ class Member < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.reset_all_status!
|
def self.reset_all_status!
|
||||||
Member.all.each do |member|
|
Member.all.each(&:reset_status!)
|
||||||
member.reset_status!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.generate_all_missing_ifthenpay_links!
|
|
||||||
Member.all.each do |member|
|
|
||||||
member.generate_missing_ifthenpay_links!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.regenerate_all_notifications
|
|
||||||
ActiveRecord::Base.transaction do
|
|
||||||
ActiveRecord::Base.connection.execute('LOCK members IN ACCESS EXCLUSIVE MODE')
|
|
||||||
|
|
||||||
Member.all.each do |member|
|
|
||||||
member.regenerate_notifications
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,23 +1,29 @@
|
|||||||
class Notification < ApplicationRecord
|
class Notification < ApplicationRecord
|
||||||
belongs_to :member
|
belongs_to :member
|
||||||
|
|
||||||
scope :scheduled_for_today, ->() { where(status: 'scheduled', to_be_sent_on: Date.today) }
|
|
||||||
|
|
||||||
def self.send_scheduled_for_today
|
def self.send_scheduled_for_today
|
||||||
ActiveRecord::Base.transaction do
|
Member
|
||||||
ActiveRecord::Base.connection.execute('LOCK members IN ACCESS EXCLUSIVE MODE')
|
.where('expires_on < ?', Date.today - self.templates.first[:to_be_sent_on])
|
||||||
|
.filter_map(&:todays_notification)
|
||||||
|
.each(&:deliver!)
|
||||||
|
end
|
||||||
|
|
||||||
scheduled_for_today.each do |n|
|
def self.templates
|
||||||
n.deliver!
|
[
|
||||||
end
|
{ to_be_sent_on: -60.days, template: "expiration_in_60d" },
|
||||||
end
|
{ to_be_sent_on: -30.days, template: "expiration_in_30d" },
|
||||||
|
{ to_be_sent_on: 0.days, template: "expired" },
|
||||||
|
{ to_be_sent_on: 30.days, template: "expired_30d_ago" },
|
||||||
|
{ to_be_sent_on: 60.days, template: "expired_60d_ago" },
|
||||||
|
{ to_be_sent_on: Config.payment_pending_grace_period, template: "cancelled" },
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def deliver!
|
def deliver!
|
||||||
# actually send the email.
|
|
||||||
NotificationMailer.with(notification: self).send(template).deliver_now!
|
|
||||||
|
|
||||||
update(status: 'sent', sent_at: Time.current)
|
|
||||||
# TODO: do something about failures
|
# TODO: do something about failures
|
||||||
|
|
||||||
|
save!
|
||||||
|
NotificationMailer.with(member: self.member).send(template).deliver_now!
|
||||||
|
update(status: 'sent', sent_at: Time.current)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento
|
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento
|
||||||
das quotas até <strong><%= @notification.member.expires_on %></strong>.
|
das quotas até <strong><%= @member.expires_on %></strong>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
@ -8,7 +8,7 @@ nossas actividades, e gostaríamos de continuar a contar com a tua
|
|||||||
participação.
|
participação.
|
||||||
|
|
||||||
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento
|
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento
|
||||||
das quotas até <%= @notification.member.expires_on %>.
|
das quotas até <%= @member.expires_on %>.
|
||||||
|
|
||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento
|
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento
|
||||||
das quotas até <strong><%= @notification.member.expires_on %></strong>.
|
das quotas até <strong><%= @member.expires_on %></strong>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
@ -8,7 +8,7 @@ nossas actividades, e gostaríamos de continuar a contar com a tua
|
|||||||
participação.
|
participação.
|
||||||
|
|
||||||
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento
|
Para estender a tua inscrição por mais um ano, pedimos que faças o pagamento
|
||||||
das quotas até <%= @notification.member.expires_on %>.
|
das quotas até <%= @member.expires_on %>.
|
||||||
|
|
||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on
|
Caso não recebamos o pagamento até dia <%= @member.cancelled_on
|
||||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
||||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
||||||
</p>
|
</p>
|
||||||
|
@ -8,7 +8,7 @@ nossas actividades. Gostaríamos de continuar a contar com a tua participação.
|
|||||||
|
|
||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
|
||||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on
|
Caso não recebamos o pagamento até dia <%= @member.cancelled_on
|
||||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
||||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on
|
Caso não recebamos o pagamento até dia <%= @member.cancelled_on
|
||||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
||||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
||||||
</p>
|
</p>
|
||||||
|
@ -8,7 +8,7 @@ nossas actividades. Gostaríamos de continuar a contar com a tua participação.
|
|||||||
|
|
||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
|
||||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on
|
Caso não recebamos o pagamento até dia <%= @member.cancelled_on
|
||||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
||||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on
|
Caso não recebamos o pagamento até dia <%= @member.cancelled_on
|
||||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
||||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
||||||
</p>
|
</p>
|
||||||
|
@ -8,7 +8,7 @@ nossas actividades. Gostaríamos de continuar a contar com a tua participação.
|
|||||||
|
|
||||||
<%= render partial: "payment", locals: { payment: @payment } %>
|
<%= render partial: "payment", locals: { payment: @payment } %>
|
||||||
|
|
||||||
Caso não recebamos o pagamento até dia <%= @notification.member.cancelled_on
|
Caso não recebamos o pagamento até dia <%= @member.cancelled_on
|
||||||
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
%>, cancelaremos permanentemente a tua inscrição. Estamos disponíveis para
|
||||||
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
esclarecer qualquer dúvida através do endereço direccao@ansol.org.
|
||||||
|
|
||||||
|
@ -1,59 +1,32 @@
|
|||||||
<h1><%= t('notifications.index.title') %></h1>
|
<h1><%= t('notifications.index.title') %></h1>
|
||||||
|
|
||||||
<details>
|
<table class='zebra'>
|
||||||
<summary><h2 style="display: inline"><%= t('notifications.index.scheduled') %></h2></summary>
|
<tr>
|
||||||
<table class='zebra'>
|
<th><%= t('notifications.attributes.to_be_sent_on') %></th>
|
||||||
|
<th><%= t('notifications.attributes.status') %></th>
|
||||||
|
<th><%= t('notifications.attributes.template') %></th>
|
||||||
|
<th><%= t('notifications.attributes.member') %></th>
|
||||||
|
<th><%= t('members.show.contribution_actions') %></th>
|
||||||
|
</tr>
|
||||||
|
<% @notifications.each do |notification| %>
|
||||||
<tr>
|
<tr>
|
||||||
<th><%= t('notifications.attributes.to_be_sent_on') %></th>
|
<td><%= notification.to_be_sent_on %></td>
|
||||||
<th><%= t('notifications.attributes.template') %></th>
|
<td><%= notification.status %></td>
|
||||||
<th><%= t('notifications.attributes.member') %></th>
|
<td><code><%= notification.template %></code></td>
|
||||||
<th><%= t('members.show.contribution_actions') %></th>
|
<td>
|
||||||
|
<% if notification.member %>
|
||||||
|
<%= link_to notification.member do %><%= notification.member.display_name %> <<%= notification.member.email %>><% end %>
|
||||||
|
<% else %>
|
||||||
|
---
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<% if notification.status == 'scheduled' %>
|
||||||
|
<%= form_with url: deliver_notification_path(notification) do |form| %>
|
||||||
|
<%= form.submit t('members.show.actions.deliver_notification') %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% @scheduled.each do |notification| %>
|
<% end %>
|
||||||
<tr>
|
</table>
|
||||||
<td><%= notification.to_be_sent_on %></td>
|
|
||||||
<td><code><%= notification.template %></code></td>
|
|
||||||
<td>
|
|
||||||
<% if notification.member %>
|
|
||||||
<%= link_to notification.member do %><%= notification.member.display_name %> <<%= notification.member.email %>><% end %>
|
|
||||||
<% else %>
|
|
||||||
---
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><h2 style="display: inline"><%= t('notifications.index.sent') %></h2></summary>
|
|
||||||
<table class='zebra'>
|
|
||||||
<tr>
|
|
||||||
<th><%= t('notifications.attributes.to_be_sent_on') %></th>
|
|
||||||
<th><%= t('notifications.attributes.template') %></th>
|
|
||||||
<th><%= t('notifications.attributes.member') %></th>
|
|
||||||
<th><%= t('members.show.contribution_actions') %></th>
|
|
||||||
</tr>
|
|
||||||
<% @sent.each do |notification| %>
|
|
||||||
<tr>
|
|
||||||
<td><%= notification.to_be_sent_on %></td>
|
|
||||||
<td><code><%= notification.template %></code></td>
|
|
||||||
<td>
|
|
||||||
<% if notification.member %>
|
|
||||||
<%= link_to notification.member do %><%= notification.member.display_name %> <<%= notification.member.email %>><% end %>
|
|
||||||
<% else %>
|
|
||||||
---
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<% if notification.status == 'scheduled' %>
|
|
||||||
<%= form_with url: deliver_notification_path(notification) do |form| %>
|
|
||||||
<%= form.submit t('members.show.actions.deliver_notification') %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
</details>
|
|
||||||
|
@ -2,9 +2,7 @@ desc "Application specific tasks"
|
|||||||
namespace :saucy do
|
namespace :saucy do
|
||||||
desc "Background sync operations"
|
desc "Background sync operations"
|
||||||
task sync: :environment do
|
task sync: :environment do
|
||||||
Member.generate_all_missing_ifthenpay_links!
|
|
||||||
Member.reset_all_status!
|
Member.reset_all_status!
|
||||||
Member.regenerate_all_notifications
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Send daily email notifications"
|
desc "Send daily email notifications"
|
||||||
|
@ -18,44 +18,77 @@ class MemberTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "expired after 1 year and 90 days" do
|
test "expired after 1 year and 90 days" do
|
||||||
Timecop.freeze(Date.today + 1.year + 90.days) do
|
Timecop.freeze(Date.today + 1.year + 90.days) do
|
||||||
assert_equal :cancelled, @member.expected_status
|
assert_equal :cancelled, @member.expected_status
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "regenerate notifications creates all 6 notifications" do
|
|
||||||
@member.regenerate_notifications
|
|
||||||
|
|
||||||
assert_equal 6, @member.notifications.count
|
|
||||||
end
|
|
||||||
|
|
||||||
test "regenerate_notifications does not create notifications in the past" do
|
|
||||||
Timecop.freeze(Date.today + 1.year) do
|
|
||||||
@member.regenerate_notifications
|
|
||||||
assert_equal 4, @member.notifications.count
|
|
||||||
end
|
|
||||||
|
|
||||||
Timecop.freeze(Date.today + 2.year) do
|
|
||||||
@member.regenerate_notifications
|
|
||||||
assert_equal 0, @member.notifications.count
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "regenerate_notifications does not create manually sent notifications" do
|
test "no notifications in the first ~10 months" do
|
||||||
@member.regenerate_notifications
|
(Date.today .. (@member.expires_on + Notification.templates.first[:to_be_sent_on] - 1.day)).each do |day|
|
||||||
|
Timecop.freeze(day) do
|
||||||
@member.notifications.first.deliver!
|
assert_nil @member.todays_notification
|
||||||
assert_equal 5, @member.notifications.where(status: 'scheduled').count
|
end
|
||||||
|
end
|
||||||
@member.regenerate_notifications
|
|
||||||
assert_equal 1, @member.notifications.where(status: 'sent').count
|
|
||||||
assert_equal 5, @member.notifications.where(status: 'scheduled').count
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "regenerate_notifications when given a date generates only notifications after that date" do
|
test "first warning notification 60~30 days before expiration" do
|
||||||
@member.regenerate_notifications(Date.today + 1.year)
|
(@member.expires_on - 60.days .. (@member.expires_on - 31.days)).each do |day|
|
||||||
|
Timecop.freeze(day) do
|
||||||
|
assert_equal "expiration_in_60d", @member.todays_notification[:template]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
assert_equal 4, @member.notifications.count
|
test "no duplicate notification" do
|
||||||
|
@member
|
||||||
|
.notifications
|
||||||
|
.create(template: "expiration_in_60d", to_be_sent_on: @member.expires_on - 60.days, status: "sent", sent_at: @member.expires_on - 60.days)
|
||||||
|
|
||||||
|
Timecop.freeze(@member.expires_on - 60.days) do
|
||||||
|
assert_nil @member.todays_notification
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "don't consider old notifications as duplicate" do
|
||||||
|
@member
|
||||||
|
.notifications
|
||||||
|
.create(template: "expiration_in_60d", to_be_sent_on: @member.expires_on - 60.days - 1.year, status: "sent", sent_at: @member.expires_on - 60.days - 1.year)
|
||||||
|
|
||||||
|
Timecop.freeze(@member.expires_on - 60.days) do
|
||||||
|
assert_equal "expiration_in_60d", @member.todays_notification[:template]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "don't consider unsent notifications as duplicate" do
|
||||||
|
@member
|
||||||
|
.notifications
|
||||||
|
.create(template: "expiration_in_60d", to_be_sent_on: @member.expires_on - 60.days, status: "sent", sent_at: nil)
|
||||||
|
|
||||||
|
Timecop.freeze(@member.expires_on - 60.days) do
|
||||||
|
assert_equal "expiration_in_60d", @member.todays_notification[:template]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "last notification ~90 days after expiration" do
|
||||||
|
Timecop.freeze(@member.expires_on + 90.days) do
|
||||||
|
assert_equal "cancelled", @member.todays_notification[:template]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "last notification ~95 days after expiration" do
|
||||||
|
Timecop.freeze(@member.expires_on + 95.days) do
|
||||||
|
assert_equal "cancelled", @member.todays_notification[:template]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "no duplicate last notification" do
|
||||||
|
@member
|
||||||
|
.notifications
|
||||||
|
.create(template: "cancelled", to_be_sent_on: @member.expires_on + 90.days, status: "sent", sent_at: @member.expires_on + 90.days)
|
||||||
|
|
||||||
|
Timecop.freeze(@member.expires_on + 95.days) do
|
||||||
|
assert_nil @member.todays_notification
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user