New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add job to send virtual hearing reminder emails #15581
Changes from all commits
2a5ede9
57896f0
d0a265c
35d33dc
9864721
8a527bc
eca4762
2e4e157
e1681ab
fd046f5
e8c2e13
fd310c2
61be568
32e86c2
fef3bff
beb7d28
ab8d0f9
2994dca
bb3c395
28f5df4
b875978
d5ebc0a
2bdaa14
98f7e17
be1ec0d
bc70cf8
be2664e
ef06351
a2e2242
55753ab
e73bfc8
e9b611f
8e47544
5663dae
31ffa4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,16 @@ class RecipientIsDeceasedVeteran < StandardError; end | |
|
||
def initialize(virtual_hearing:, type:) | ||
@virtual_hearing = virtual_hearing | ||
@type = type | ||
@type = type.to_s | ||
end | ||
|
||
def call | ||
# Assumption: Reminders and confirmation/cancellation/change emails are sent | ||
# separately, so this will return early if any reminder emails are sent. If | ||
# reminder emails are being sent, we are assuming the other emails have all | ||
# already been sent too. | ||
return if send_reminder | ||
|
||
if !virtual_hearing.appellant_email_sent | ||
virtual_hearing.update!(appellant_email_sent: send_email(appellant_recipient)) | ||
end | ||
|
@@ -30,19 +36,39 @@ def call | |
delegate :appeal, to: :hearing | ||
delegate :veteran, to: :appeal | ||
|
||
def send_reminder | ||
if type == "appellant_reminder" && send_email(appellant_recipient) | ||
virtual_hearing.update!(appellant_reminder_sent_at: Time.zone.now) | ||
|
||
return true | ||
end | ||
|
||
if type == "representative_reminder" && | ||
!virtual_hearing.representative_email.nil? && | ||
send_email(representative_recipient) | ||
virtual_hearing.update!(representative_reminder_sent_at: Time.zone.now) | ||
|
||
return true | ||
end | ||
|
||
false | ||
end | ||
|
||
def email_for_recipient(recipient) | ||
args = { | ||
mail_recipient: recipient, | ||
virtual_hearing: virtual_hearing | ||
} | ||
|
||
case type.to_s | ||
case type | ||
when "confirmation" | ||
VirtualHearingMailer.confirmation(**args) | ||
when "cancellation" | ||
VirtualHearingMailer.cancellation(**args) | ||
when "updated_time_confirmation" | ||
VirtualHearingMailer.updated_time_confirmation(**args) | ||
when "appellant_reminder", "representative_reminder" | ||
VirtualHearingMailer.reminder(**args) | ||
else | ||
fail ArgumentError, "Invalid type of email to send: `#{type}`" | ||
end | ||
|
@@ -57,7 +83,10 @@ def external_message_id(msg) | |
DataDogService.increment_counter( | ||
app_name: Constants.DATADOG_METRICS.HEARINGS.APP_NAME, | ||
metric_group: Constants.DATADOG_METRICS.HEARINGS.VIRTUAL_HEARINGS_GROUP_NAME, | ||
metric_name: "emails.submitted" | ||
metric_name: "emails.submitted", | ||
attrs: { | ||
email_type: type | ||
} | ||
) | ||
|
||
Rails.logger.info( | ||
|
@@ -101,7 +130,7 @@ def send_email(recipient) | |
|
||
msg = email.deliver_now! | ||
rescue StandardError, Savon::Error, BGS::ShareError => error | ||
# Savon::Error and BGS::ShareError are sometimes thrown when making requests to BGS enpoints | ||
# Savon::Error and BGS::ShareError are sometimes thrown when making requests to BGS endpoints | ||
Raven.capture_exception(error) | ||
|
||
Rails.logger.warn("Failed to send #{type} email to #{recipient.title}: #{error}") | ||
|
@@ -126,11 +155,11 @@ def create_sent_hearing_email_event(recipient, external_id) | |
) | ||
SentHearingEmailEvent.create!( | ||
hearing: hearing, | ||
email_type: type, | ||
email_type: type.ends_with?("reminder") ? "reminder" : type, | ||
email_address: recipient.email, | ||
external_message_id: external_id, | ||
recipient_role: recipient_is_veteran ? "veteran" : recipient.title.downcase, | ||
sent_by: virtual_hearing.updated_by | ||
sent_by: type.ends_with?("reminder") ? User.system_user : virtual_hearing.updated_by | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tagging the system user as the person who sent the email, since technically no one is triggering the email to be sent There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense |
||
) | ||
rescue StandardError => error | ||
Raven.capture_exception(error) | ||
|
@@ -185,6 +214,8 @@ def appellant_recipient | |
end | ||
|
||
def should_judge_receive_email? | ||
!virtual_hearing.judge_email.nil? && !virtual_hearing.judge_email_sent && type.to_s != "cancellation" | ||
!virtual_hearing.judge_email.nil? && | ||
!virtual_hearing.judge_email_sent && | ||
%w[confirmation updated_time_confirmation].include?(type) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# frozen_string_literal: true | ||
|
||
class VirtualHearings::SendReminderEmailsJob < ApplicationJob | ||
def perform | ||
VirtualHearingRepository.maybe_ready_for_reminder_email.each do |virtual_hearing| | ||
send_reminder_emails(virtual_hearing) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
end | ||
end | ||
|
||
private | ||
|
||
def send_reminder_emails(virtual_hearing) | ||
if should_send_appellant_reminder?(virtual_hearing) | ||
VirtualHearings::SendEmail | ||
.new(virtual_hearing: virtual_hearing, type: :appellant_reminder) | ||
.call | ||
end | ||
|
||
if virtual_hearing.representative_email.present? && should_sent_representative_reminder?(virtual_hearing) | ||
VirtualHearings::SendEmail | ||
.new(virtual_hearing: virtual_hearing, type: :representative_reminder) | ||
.call | ||
end | ||
end | ||
|
||
def should_send_appellant_reminder?(virtual_hearing) | ||
VirtualHearings::ReminderService | ||
.new(virtual_hearing, virtual_hearing.appellant_reminder_sent_at) | ||
.should_send_reminder_email? | ||
end | ||
|
||
def should_sent_representative_reminder?(virtual_hearing) | ||
VirtualHearings::ReminderService | ||
.new(virtual_hearing, virtual_hearing.representative_reminder_sent_at) | ||
.should_send_reminder_email? | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# frozen_string_literal: true | ||
|
||
## | ||
# Service that determines whether or not a send a reminder to the appellant or representative | ||
# about their virtual hearing. | ||
|
||
class VirtualHearings::ReminderService | ||
def initialize(virtual_hearing, last_sent_reminder) | ||
@virtual_hearing = virtual_hearing | ||
@last_sent_reminder = last_sent_reminder | ||
end | ||
|
||
def should_send_reminder_email? | ||
return false if days_until_hearing <= 0 | ||
|
||
should_send_2_day_reminder? || | ||
should_send_3_day_friday_reminder? || | ||
should_send_7_day_reminder? | ||
end | ||
|
||
private | ||
|
||
attr_reader :virtual_hearing | ||
attr_reader :last_sent_reminder | ||
|
||
def should_send_2_day_reminder? | ||
days_between_hearing_and_created_at > 2 && | ||
days_until_hearing <= 2 && days_from_hearing_day_to_last_sent_reminder > 2 | ||
end | ||
|
||
# The 3 day reminder is a special reminder that is sent on Friday, *only* if the hearing | ||
# itself is on Monday. | ||
def should_send_3_day_friday_reminder? | ||
ferristseng marked this conversation as resolved.
Show resolved
Hide resolved
|
||
days_between_hearing_and_created_at > 3 && | ||
days_until_hearing <= 3 && virtual_hearing.hearing.scheduled_for.monday? | ||
end | ||
|
||
def should_send_7_day_reminder? | ||
days_between_hearing_and_created_at > 7 && | ||
days_until_hearing <= 7 && days_from_hearing_day_to_last_sent_reminder > 7 | ||
end | ||
|
||
# Determines the date between when the hearing is scheduled, and when the virtual hearing was scheduled. | ||
# If the virtual hearing was scheduled within a reminder period, we skip sending the reminder for that period | ||
# because the confirmation will have redundant information. | ||
def days_between_hearing_and_created_at | ||
(virtual_hearing.hearing.scheduled_for - virtual_hearing.created_at) / 1.day | ||
end | ||
|
||
def days_until_hearing | ||
((virtual_hearing.hearing.scheduled_for - Time.zone.now.utc) / 1.day).round | ||
end | ||
|
||
def days_from_hearing_day_to_last_sent_reminder | ||
# Pick arbitrarily big value if the reminder has never been sent. | ||
return Float::INFINITY if last_sent_reminder.nil? | ||
|
||
(virtual_hearing.hearing.scheduled_for - last_sent_reminder) / 1.day | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
class AddReminderEmailFlagsToVirtualHearings < Caseflow::Migration | ||
def change | ||
add_column :virtual_hearings, | ||
:appellant_reminder_sent_at, | ||
:datetime, | ||
comment: "The datetime the last reminder email was sent to the appellant." | ||
add_column :virtual_hearings, | ||
:representative_reminder_sent_at, | ||
:datetime, | ||
comment: "The datetime the last reminder email was sent to the representative." | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small refactor here so we don't have to call
to_s
when we're usingtype