saucy/app/models/member.rb

159 lines
4.0 KiB
Ruby
Raw Normal View History

2022-06-25 12:48:46 +00:00
class Member < ApplicationRecord
default_scope { where(excluded: false) }
2022-06-25 12:48:46 +00:00
has_many :contributions
has_many :notifications
has_many :payments
2022-06-26 11:57:00 +00:00
has_paper_trail
2022-06-25 12:48:46 +00:00
2023-04-19 21:52:31 +00:00
def self.ransackable_attributes(auth_object = nil)
%w[display_name legal_name email identification_number]
end
def create_payment
payments.create(status: "pending")
end
2022-06-25 12:48:46 +00:00
def cancelled_on
2023-09-20 13:19:35 +00:00
expires_on + Config.payment_pending_grace_period
2022-06-25 12:48:46 +00:00
end
def obtains_full_rights_on
2023-09-20 13:19:35 +00:00
joined_on + Config.full_rights_vesting_period
end
2022-06-25 12:48:46 +00:00
def reset_status!
update(status: expected_status)
end
def employed?
2023-09-20 13:19:35 +00:00
# normal is deprecated, here for retrocompatibility reasons
category == "normal" || category == "employed"
end
def remove_personal_information!
update(
excluded: true,
display_name: "",
2023-03-31 01:24:16 +00:00
legal_name: "",
pronouns: "",
email: nil,
identification_number: "",
category: "",
address: "",
)
end
2022-06-25 12:48:46 +00:00
def expected_status
if joined_on.nil?
:pending
elsif obtains_full_rights_on.future?
2022-06-25 12:48:46 +00:00
:passive
elsif expires_on.future?
:active
elsif cancelled_on.future?
:expired
else
:cancelled
end
end
2023-08-07 12:50:50 +00:00
def register_contribution(contribution_params, overriden_expires_on, should_send_notification)
Contribution.transaction do
is_first_contribution = self.contributions.empty?
contribution = self.contributions.build(contribution_params)
if contribution.save
self.handle_new_contribution(contribution, overriden_expires_on)
self.reset_status!
if should_send_notification
if is_first_contribution
NotificationMailer
.with(contribution: contribution)
.first_payment_confirmation
.deliver_now!
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.
2024-03-23 15:22:04 +00:00
self.notifications.create!(
template: "first_payment_confirmation",
to_be_sent_on: Date.today,
sent_at: Time.current,
status: "sent",
)
2023-08-07 12:50:50 +00:00
else
NotificationMailer
.with(contribution: contribution)
.payment_renewal_confirmation
.deliver_now!
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.
2024-03-23 15:22:04 +00:00
self.notifications.create!(
template: "payment_renewal_confirmation",
to_be_sent_on: Date.today,
sent_at: Time.current,
status: "sent",
)
2023-08-07 12:50:50 +00:00
end
end
true
else
false
end
end
end
2022-06-25 12:48:46 +00:00
def handle_new_contribution(contribution, overriden_expires_on)
if joined_on.nil?
self.joined_on = contribution.payment_on
self.expires_on = overriden_expires_on.presence || (joined_on + 1.year)
else
self.expires_on = overriden_expires_on.presence || expires_on + 1.year
end
save!
end
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.
2024-03-23 15:22:04 +00:00
def todays_notification
candidate = Notification
.templates
.filter { |t| self.expires_on + t[:to_be_sent_on] <= Date.today }
.last
if candidate
last_sent = self
.notifications
.where(status: 'sent', template: candidate[:template])
.where.not(sent_at: nil)
.order(:sent_at)
.last
if last_sent && self.expires_on + candidate[:to_be_sent_on] <= last_sent.sent_at
nil
else
self.notifications.new(
template: candidate[:template],
status: 'scheduled',
to_be_sent_on: Date.today,
)
end
2022-06-25 12:48:46 +00:00
end
end
def generate_missing_ifthenpay_links!
self.regular_ifthenpay_link = IfThenPay.generate_gateway_link(
id: number,
amount: "%.2f" % Config.regular_payment_value,
description: Config.ifthenpay_payment_title,
2022-06-25 12:48:46 +00:00
) unless self.regular_ifthenpay_link.present?
self.reduced_ifthenpay_link = IfThenPay.generate_gateway_link(
id: number,
amount: "%.2f" % Config.reduced_payment_value,
description: Config.ifthenpay_payment_title,
2022-06-25 12:48:46 +00:00
) unless self.reduced_ifthenpay_link.present?
save!
end
def self.reset_all_status!
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.
2024-03-23 15:22:04 +00:00
Member.all.each(&:reset_status!)
2022-06-25 12:48:46 +00:00
end
end