2022-06-25 12:48:46 +00:00
|
|
|
class Member < ApplicationRecord
|
2022-06-25 20:27:02 +00:00
|
|
|
default_scope { where(excluded: false) }
|
2022-06-25 12:48:46 +00:00
|
|
|
has_many :contributions
|
|
|
|
has_many :notifications
|
2023-07-12 18:13:08 +00:00
|
|
|
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
|
|
|
|
|
2023-07-12 18:13:08 +00:00
|
|
|
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
|
|
|
|
|
2023-07-12 18:13:08 +00:00
|
|
|
def obtains_full_rights_on
|
2023-09-20 13:19:35 +00:00
|
|
|
joined_on + Config.full_rights_vesting_period
|
2023-07-12 18:13:08 +00:00
|
|
|
end
|
|
|
|
|
2022-06-25 12:48:46 +00:00
|
|
|
def reset_status!
|
|
|
|
update(status: expected_status)
|
|
|
|
end
|
|
|
|
|
2023-03-23 18:15:45 +00:00
|
|
|
def employed?
|
2023-09-20 13:19:35 +00:00
|
|
|
# normal is deprecated, here for retrocompatibility reasons
|
2023-03-23 18:15:45 +00:00
|
|
|
category == "normal" || category == "employed"
|
|
|
|
end
|
|
|
|
|
2022-06-25 20:27:02 +00:00
|
|
|
def remove_personal_information!
|
|
|
|
update(
|
|
|
|
excluded: true,
|
|
|
|
display_name: "",
|
2023-03-31 01:24:16 +00:00
|
|
|
legal_name: "",
|
|
|
|
pronouns: "",
|
2022-06-25 20:27:02 +00:00
|
|
|
email: nil,
|
|
|
|
identification_number: "",
|
|
|
|
category: "",
|
|
|
|
address: "",
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2022-06-25 12:48:46 +00:00
|
|
|
def expected_status
|
|
|
|
if joined_on.nil?
|
|
|
|
:pending
|
2023-07-12 18:13:08 +00:00
|
|
|
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,
|
2023-07-12 18:13:08 +00:00
|
|
|
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,
|
2023-07-12 18:13:08 +00:00
|
|
|
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
|