Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/controllers/admin/organizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def organization_params
params.require(:organization)
.permit(:name, :street, :city, :state, :zipcode, :email, :url, :logo, :intake_location, :default_email_text, :account_request_id, :bank_is_set_up,
:reminder_schedule_definition, :deadline_day,
:deadline_reminders_enabled, :distribution_reminders_enabled,
users_attributes: %i(name email organization_admin), account_request_attributes: %i(ndbn_member_id id))
end

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/distributions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def send_notification(org, dist, subject: 'Your Distribution', distribution_chan
end

def schedule_reminder_email(distribution)
return if distribution.past? || !distribution.partner.send_reminders
return if distribution.past? || !distribution.partner.send_reminders || !distribution.organization.distribution_reminders_enabled

DistributionMailer.reminder_email(distribution.id).deliver_later(wait_until: distribution.issued_at - 1.day)
end
Expand Down
1 change: 1 addition & 0 deletions app/controllers/organizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def organization_params
:ytd_on_distribution_printout, :one_step_partner_invite,
:hide_value_columns_on_receipt, :hide_package_column_on_receipt,
:signature_for_distribution_pdf, :receive_email_on_requests,
:deadline_reminders_enabled, :distribution_reminders_enabled,
:bank_is_set_up,
:include_in_kind_values_in_exported_files,
:include_packages_in_distribution_export,
Expand Down
23 changes: 23 additions & 0 deletions app/javascript/controllers/reminder_toggle_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Controller } from "@hotwired/stimulus"

/*
* ReminderToggleController shows/hides dependent reminder-schedule fields based
* on the selected value of a Yes/No radio-button group. Used on the organization
* settings form so the reminder schedule and reminder email text are only shown
* when "Send monthly deadline reminder emails?" is set to Yes.
*/
export default class extends Controller {
static targets = ["source", "dependentField"]

connect() {
this.toggle()
}

toggle() {
const selected = this.sourceTargets.find((input) => input.checked)
const show = selected?.value === "true"
this.dependentFieldTargets.forEach((field) => {
field.classList.toggle("d-none", !show)
})
}
}
1 change: 1 addition & 0 deletions app/mailers/distribution_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def reminder_email(distribution_id)
requester_email = distribution.request ? distribution.request.requester.email : @partner.email

return if @distribution.past? || !@partner.send_reminders || @partner.deactivated?
return unless @distribution.organization.distribution_reminders_enabled

mail(to: requester_email, cc: @partner.email, subject: "#{@partner.name} Distribution Reminder")
end
Expand Down
5 changes: 5 additions & 0 deletions app/mailers/reminder_deadline_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ class ReminderDeadlineMailer < ApplicationMailer
def notify_deadline(partner)
@partner = partner
@organization = partner.organization

# Do not send even if the partner (or its group) has reminders on when the
# organization has turned monthly deadline reminders off.
return unless @organization.deadline_reminders_enabled

@deadline = deadline_date(partner)
reminder_email_text = @organization.reminder_email_text
@reminder_email_text_interpolated = TextInterpolatorService.new(reminder_email_text.body.to_s, {
Expand Down
2 changes: 2 additions & 0 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
# bank_is_set_up :boolean default(FALSE), not null
# city :string
# deadline_day :integer
# deadline_reminders_enabled :boolean default(FALSE), not null
# default_storage_location :integer
# distribute_monthly :boolean default(FALSE), not null
# distribution_reminders_enabled :boolean default(FALSE), not null
# email :string
# enable_child_based_requests :boolean default(TRUE), not null
# enable_individual_requests :boolean default(TRUE), not null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ def fetch
current_day = Time.current
deactivated_status = ::Partner.statuses[:deactivated]

partners_with_group_reminders = ::Partner.left_joins(:partner_group)
partners_with_group_reminders = ::Partner.left_joins(:partner_group, :organization)
.where.not(partner_groups: {reminder_schedule_definition: nil})
.where.not(partner_groups: {deadline_day: nil})
.where(organizations: {deadline_reminders_enabled: true})
.where.not(status: deactivated_status)

# where partner groups have reminder schedule match
Expand All @@ -17,6 +18,7 @@ def fetch
partners_with_only_organization_reminders = ::Partner.left_joins(:partner_group, :organization)
.where(partner_groups: {reminder_schedule_definition: nil})
.where(send_reminders: true)
.where(organizations: {deadline_reminders_enabled: true})
.where.not(organizations: {deadline_day: nil})
.where.not(organizations: {reminder_schedule_definition: nil})
.where.not(status: deactivated_status)
Expand Down
2 changes: 2 additions & 0 deletions app/views/admin/organizations/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
<%= f.input :city %>
<%= f.input :state, collection: us_states, class: "form-control", placeholder: "state" %>
<%= f.input :zipcode %>
<%= f.input :deadline_reminders_enabled, label: 'Send monthly deadline reminder emails to partners?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %>
<%= f.input :distribution_reminders_enabled, label: 'Send day-before distribution reminder emails to partners?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %>
<%= render 'shared/deadline_day_fields', parent_form: f, parent_object: @organization %>
<%= f.simple_fields_for :account_request do |account_request| %>
<%= account_request.input :ndbn_member, label: 'NDBN Membership', wrapper: :input_group do %>
Expand Down
6 changes: 5 additions & 1 deletion app/views/distributions/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
<div class='w-72'>
<%= f.input :issued_at, as: :datetime, ampm: true, minute_step: 15, label: "Distribution date and time", html5: true, :input_html => { :value => date_place_holder&.strftime("%Y-%m-%dT%0k:%M")} %>
</div>
<%= f.input :reminder_email_enabled, as: :boolean, checked_value: true, unchecked_value: false, label: "Send email reminder the day before?" %>
<% if current_organization.distribution_reminders_enabled %>
<%= f.input :reminder_email_enabled, as: :boolean, checked_value: true, unchecked_value: false, label: "Send email reminder the day before?" %>
<% else %>
<%= render 'shared/reminders_disabled_notice', reminder_type: :distribution %>
<% end %>
<%= f.input :agency_rep, label: "Agency representative" %>

<div class='d-flex flex-row' data-controller="distribution-delivery">
Expand Down
60 changes: 37 additions & 23 deletions app/views/organizations/_details.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -174,37 +174,51 @@

<h4>Other emails</h4>
<div>
<h6 class="font-weight-bold">Reminder emails are sent</h6>
<h6 class="font-weight-bold">Send monthly deadline reminder emails to partners?</h6>
<p>
<%= fa_icon "calendar" %>
<%= @organization.reminder_schedule&.show_description.blank? ? 'Not defined' : @organization.reminder_schedule&.show_description %>
<% if @organization.reminder_schedule.valid? %>
<small class="mb-3 d-block text-muted">
<%= "Your next reminder date is #{@organization.reminder_schedule.next_occurrence&.strftime('%a %b %d %Y')}." %>
</small>
<% end %>
<%= humanize_boolean(@organization.deadline_reminders_enabled) %>
</p>
</div>
<div>
<h6 class="font-weight-bold">Deadline day in reminder email</h6>
<h6 class="font-weight-bold">Send day-before distribution reminder emails to partners?</h6>
<p>
<%= fa_icon "calendar" %>
<%= @organization.deadline_day.blank? ? 'Not defined' : "The #{@organization.deadline_day.ordinalize} after the reminder." %>
<small class="mb-3 d-block text-muted">
<% if @organization.deadline_day && @organization.reminder_schedule.valid? %>
<%= "The deadline on your next reminder email will be #{DeadlineService.new(
deadline_day: @organization.deadline_day,
today: @organization.reminder_schedule.next_occurrence).next_deadline&.strftime('%a %b %d %Y')}." %>
<% end %>
</small>
<%= humanize_boolean(@organization.distribution_reminders_enabled) %>
</p>
</div>
<div>
<h6 class="font-weight-bold">Additional text for reminder email</h6>
<div class="border rounded p-2 mb-3">
<%= @organization.reminder_email_text.presence || 'Not defined' %>
<% if @organization.deadline_reminders_enabled %>
<div>
<h6 class="font-weight-bold">Reminder emails are sent</h6>
<p>
<%= fa_icon "calendar" %>
<%= @organization.reminder_schedule&.show_description.blank? ? 'Not defined' : @organization.reminder_schedule&.show_description %>
<% if @organization.reminder_schedule.valid? %>
<small class="mb-3 d-block text-muted">
<%= "Your next reminder date is #{@organization.reminder_schedule.next_occurrence&.strftime('%a %b %d %Y')}." %>
</small>
<% end %>
</p>
</div>
</div>
<div>
<h6 class="font-weight-bold">Deadline day in reminder email</h6>
<p>
<%= fa_icon "calendar" %>
<%= @organization.deadline_day.blank? ? 'Not defined' : "The #{@organization.deadline_day.ordinalize} after the reminder." %>
<small class="mb-3 d-block text-muted">
<% if @organization.deadline_day && @organization.reminder_schedule.valid? %>
<%= "The deadline on your next reminder email will be #{DeadlineService.new(
deadline_day: @organization.deadline_day,
today: @organization.reminder_schedule.next_occurrence).next_deadline&.strftime('%a %b %d %Y')}." %>
<% end %>
</small>
</p>
</div>
<div>
<h6 class="font-weight-bold">Additional text for reminder email</h6>
<div class="border rounded p-2 mb-3">
<%= @organization.reminder_email_text.presence || 'Not defined' %>
</div>
</div>
<% end %>
<div>
<h6 class="font-weight-bold">Distribution email content</h6>
<div class="border rounded p-2 mb-3">
Expand Down
16 changes: 11 additions & 5 deletions app/views/organizations/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,17 @@
<hr>

<h4>Other emails</h4>
<%= render 'shared/deadline_day_fields', parent_form: f, parent_object: current_organization %>
<% default_reminder_email_text_hint = "You can use the variable <code>%{partner_name}</code> to include the partner's name in the message." %>
<%= f.input :reminder_email_text, label: "Additional text for reminder email", hint: default_reminder_email_text_hint.html_safe do %>
<%= f.rich_text_area :reminder_email_text, placeholder: 'Enter reminder email content...' %>
<% end %>
<div data-controller="reminder-toggle">
<%= f.input :deadline_reminders_enabled, label: 'Send monthly deadline reminder emails to partners?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first, input_html: { data: { 'reminder-toggle-target': 'source', action: 'change->reminder-toggle#toggle' } } %>
<%= f.input :distribution_reminders_enabled, label: 'Send day-before distribution reminder emails to partners?', as: :radio_buttons, collection: [[true, 'Yes'], [false, 'No']], label_method: :second, value_method: :first %>
<div data-reminder-toggle-target="dependentField">
<%= render 'shared/deadline_day_fields', parent_form: f, parent_object: current_organization %>
<% default_reminder_email_text_hint = "You can use the variable <code>%{partner_name}</code> to include the partner's name in the message." %>
<%= f.input :reminder_email_text, label: "Additional text for reminder email", hint: default_reminder_email_text_hint.html_safe do %>
<%= f.rich_text_area :reminder_email_text, placeholder: 'Enter reminder email content...' %>
<% end %>
</div>
</div>
<% default_email_text_hint = "You can use the variables <code>%{partner_name}</code>, <code>%{delivery_method}</code>, <code>%{distribution_date}</code>, and <code>%{comment}</code> to include the partner's name, delivery method, distribution date, and comments sent in the request." %>
<%= f.input :default_email_text, label: "Distribution email content", hint: default_email_text_hint.html_safe do %>
<%= f.rich_text_area :default_email_text, placeholder: 'Enter distribution email content...' %>
Expand Down
24 changes: 15 additions & 9 deletions app/views/partner_groups/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,24 @@
<%= f.association :item_categories, collection: @item_categories, as: :check_boxes, label: '' %>
</div>

<div class='mt-3' data-controller='checkbox-with-nested-element'>
<div class='mt-3'>
<h2 class='text-bold text-lg'>Do you want to send deadline reminders to them every month?</h2>
<%= f.input :send_reminders, as: :boolean, label: "Yes", input_html: { data: { action: 'click->checkbox-with-nested-element#toggleNestedElementVisibility', 'checkbox-with-nested-element-target': 'checkbox' } } %>
<% if current_organization.deadline_reminders_enabled %>
<div data-controller='checkbox-with-nested-element'>
<%= f.input :send_reminders, as: :boolean, label: "Yes", input_html: { data: { action: 'click->checkbox-with-nested-element#toggleNestedElementVisibility', 'checkbox-with-nested-element-target': 'checkbox' } } %>

<div class='pl-5 d-none' data-checkbox-with-nested-element-target='nestedElement'>
<div class="callout callout-info">
<h5>That's great!</h5>
<p>You must fill out the details below to send reminders.</p>
</div>
<div class='pl-5 d-none' data-checkbox-with-nested-element-target='nestedElement'>
<div class="callout callout-info">
<h5>That's great!</h5>
<p>You must fill out the details below to send reminders.</p>
</div>

<%= render 'shared/deadline_day_fields', parent_form: f, parent_object: @partner_group %>
</div>
<%= render 'shared/deadline_day_fields', parent_form: f, parent_object: @partner_group %>
</div>
</div>
<% else %>
<%= render 'shared/reminders_disabled_notice', reminder_type: :deadline %>
<% end %>
</div>
</div>

Expand Down
3 changes: 3 additions & 0 deletions app/views/partners/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
<h5 class='text-uppercase font-weight-bold'>Reminder</h5>
<p>Please note that reminders will be sent out according to the settings of their partner group OR organization. This won't work unless you've set up at least one.</p>
</div>
<% unless current_organization.deadline_reminders_enabled || current_organization.distribution_reminders_enabled %>
<%= render 'shared/reminders_disabled_notice', reminder_type: :all %>
<% end %>
</div>

<%= f.input :quota, label: "Quota", wrapper: :input_group do %>
Expand Down
27 changes: 27 additions & 0 deletions app/views/shared/_reminders_disabled_notice.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<%#
Shown in place of reminder-configuration fields when the organization has
turned the relevant reminder emails off on its settings page.

Locals:
reminder_type - :deadline, :distribution, or :all
%>
<div class="callout callout-info">
<h5 class="font-weight-bold">
<% case reminder_type %>
<% when :distribution %>
Day-before distribution reminder emails are turned off
<% when :all %>
Reminder emails are turned off for your organization
<% else %>
Monthly deadline reminder emails are turned off
<% end %>
</h5>
<p class="mb-0">
Your organization has turned this off, so these emails will not be sent.
<% if can_administrate? %>
<%= link_to "Change this in your organization settings", edit_organization_path %>.
<% else %>
Contact your organization administrator to change this.
<% end %>
</p>
</div>
15 changes: 15 additions & 0 deletions db/migrate/20260829112930_add_reminder_toggles_to_organizations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class AddReminderTogglesToOrganizations < ActiveRecord::Migration[8.1]
def up
add_column :organizations, :deadline_reminders_enabled, :boolean, null: false, default: false
add_column :organizations, :distribution_reminders_enabled, :boolean, null: false, default: false

# Set the existing orgs value to true.
# This preserves current behavior since emails only get sent if the org already has reminder settings.
Organization.update_all(deadline_reminders_enabled: true, distribution_reminders_enabled: true)
end

def down
remove_column :organizations, :distribution_reminders_enabled
remove_column :organizations, :deadline_reminders_enabled
end
end
4 changes: 3 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.1].define(version: 2026_08_28_150820) do
ActiveRecord::Schema[8.1].define(version: 2026_08_29_112930) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"

Expand Down Expand Up @@ -469,8 +469,10 @@
t.string "city"
t.datetime "created_at", precision: nil, null: false
t.integer "deadline_day"
t.boolean "deadline_reminders_enabled", default: false, null: false
t.integer "default_storage_location"
t.boolean "distribute_monthly", default: false, null: false
t.boolean "distribution_reminders_enabled", default: false, null: false
t.string "email"
t.boolean "enable_child_based_requests", default: true, null: false
t.boolean "enable_individual_requests", default: true, null: false
Expand Down
14 changes: 14 additions & 0 deletions spec/controllers/distributions_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@
context "when partner has enabled send_reminders" do
before(:each) do
partner.send_reminders = true
organization.update!(distribution_reminders_enabled: true)
Comment thread
LizPrescott marked this conversation as resolved.
end
it "should schedule the reminder email" do
subject
Expand All @@ -261,6 +262,18 @@
expect(enqueued_jobs.size).to eq(0)
end
end

context "when the partner has send_reminders enabled but the organization has disabled distribution reminders" do
before do
partner.update!(send_reminders: true)
organization.update!(distribution_reminders_enabled: false)
end

it "should not schedule an email reminder" do
subject
expect(enqueued_jobs.map { |job| job["arguments"][1] }).not_to include("reminder_email")
end
end
end
end

Expand Down Expand Up @@ -416,6 +429,7 @@
context "when partner has enabled send_reminders" do
before(:each) do
partner.send_reminders = true
organization.update!(distribution_reminders_enabled: true)
end
it "should schedule the reminder email" do
subject
Expand Down
Loading
Loading