diff --git a/app/controllers/casa_admins_controller.rb b/app/controllers/casa_admins_controller.rb index 515f8fed9b..a1436af255 100644 --- a/app/controllers/casa_admins_controller.rb +++ b/app/controllers/casa_admins_controller.rb @@ -135,6 +135,10 @@ def set_admin end def update_casa_admin_params - CasaAdminParameters.new(params).with_only(:email, :display_name, :phone_number) + CasaAdminParameters.new(params).with_only(:email, :display_name, :phone_number, :monthly_learning_hours_report) + end + + def learning_hours_checked? + ActiveModel::Type::Boolean.new.cast(params[:monthly_learning_hours_report]) end end diff --git a/app/controllers/supervisors_controller.rb b/app/controllers/supervisors_controller.rb index 6cde7928b3..b4834fce01 100644 --- a/app/controllers/supervisors_controller.rb +++ b/app/controllers/supervisors_controller.rb @@ -125,7 +125,19 @@ def supervisor_values end def supervisor_params - params.require(:supervisor).permit(:display_name, :email, :old_emails, :phone_number, :active, :receive_reimbursement_email, volunteer_ids: [], supervisor_volunteer_ids: []) + params.require(:supervisor) + .permit( + :display_name, + :email, + :old_emails, + :phone_number, + :active, + :monthly_learning_hours_report, + :receive_reimbursement_email, + :monthly_learning_hours_report, + volunteer_ids: [], + supervisor_volunteer_ids: [] + ) end def update_supervisor_params diff --git a/app/mailers/learning_hours_mailer.rb b/app/mailers/learning_hours_mailer.rb new file mode 100644 index 0000000000..e5ee654d9f --- /dev/null +++ b/app/mailers/learning_hours_mailer.rb @@ -0,0 +1,17 @@ +class LearningHoursMailer < ApplicationMailer + def learning_hours_report_email(user) + # user is either an Admin or Supervisor, this mailer is invoked through the rake task :monthly_learning_hours_report.rake + @user = user + @casa_org = @user.casa_org + + # Generate the learning hours CSV for the current month + start_date = Date.today.beginning_of_month + end_date = Date.today.end_of_month + learning_hours = LearningHour.where(user: @casa_org.users, occurred_at: start_date..end_date) + csv_data = LearningHoursExportCsvService.new(learning_hours).perform + + attachments["learning-hours-report-#{Date.today}.csv"] = csv_data + + mail(to: @user.email, subject: "Learning Hours Report for #{end_date.strftime("%B, %Y")}.") + end +end diff --git a/app/models/casa_admin.rb b/app/models/casa_admin.rb index cda73e3160..4bd2021ef9 100644 --- a/app/models/casa_admin.rb +++ b/app/models/casa_admin.rb @@ -20,39 +20,40 @@ def change_to_supervisor! # # Table name: users # -# id :bigint not null, primary key -# active :boolean default(TRUE) -# confirmation_sent_at :datetime -# confirmation_token :string -# confirmed_at :datetime -# current_sign_in_at :datetime -# current_sign_in_ip :string -# display_name :string default(""), not null -# email :string default(""), not null -# encrypted_password :string default(""), not null -# invitation_accepted_at :datetime -# invitation_created_at :datetime -# invitation_limit :integer -# invitation_sent_at :datetime -# invitation_token :string -# invitations_count :integer default(0) -# invited_by_type :string -# last_sign_in_at :datetime -# last_sign_in_ip :string -# old_emails :string default([]), is an Array -# phone_number :string default("") -# receive_email_notifications :boolean default(TRUE) -# receive_reimbursement_email :boolean default(FALSE) -# receive_sms_notifications :boolean default(FALSE), not null -# reset_password_sent_at :datetime -# reset_password_token :string -# sign_in_count :integer default(0), not null -# type :string -# unconfirmed_email :string -# created_at :datetime not null -# updated_at :datetime not null -# casa_org_id :bigint not null -# invited_by_id :bigint +# id :bigint not null, primary key +# active :boolean default(TRUE) +# confirmation_sent_at :datetime +# confirmation_token :string +# confirmed_at :datetime +# current_sign_in_at :datetime +# current_sign_in_ip :string +# display_name :string default(""), not null +# email :string default(""), not null +# encrypted_password :string default(""), not null +# invitation_accepted_at :datetime +# invitation_created_at :datetime +# invitation_limit :integer +# invitation_sent_at :datetime +# invitation_token :string +# invitations_count :integer default(0) +# invited_by_type :string +# last_sign_in_at :datetime +# last_sign_in_ip :string +# monthly_learning_hours_report :boolean default(FALSE), not null +# old_emails :string default([]), is an Array +# phone_number :string default("") +# receive_email_notifications :boolean default(TRUE) +# receive_reimbursement_email :boolean default(FALSE) +# receive_sms_notifications :boolean default(FALSE), not null +# reset_password_sent_at :datetime +# reset_password_token :string +# sign_in_count :integer default(0), not null +# type :string +# unconfirmed_email :string +# created_at :datetime not null +# updated_at :datetime not null +# casa_org_id :bigint not null +# invited_by_id :bigint # # Indexes # diff --git a/app/models/supervisor.rb b/app/models/supervisor.rb index ad49691336..afec012012 100644 --- a/app/models/supervisor.rb +++ b/app/models/supervisor.rb @@ -47,39 +47,40 @@ def recently_unassigned_volunteers # # Table name: users # -# id :bigint not null, primary key -# active :boolean default(TRUE) -# confirmation_sent_at :datetime -# confirmation_token :string -# confirmed_at :datetime -# current_sign_in_at :datetime -# current_sign_in_ip :string -# display_name :string default(""), not null -# email :string default(""), not null -# encrypted_password :string default(""), not null -# invitation_accepted_at :datetime -# invitation_created_at :datetime -# invitation_limit :integer -# invitation_sent_at :datetime -# invitation_token :string -# invitations_count :integer default(0) -# invited_by_type :string -# last_sign_in_at :datetime -# last_sign_in_ip :string -# old_emails :string default([]), is an Array -# phone_number :string default("") -# receive_email_notifications :boolean default(TRUE) -# receive_reimbursement_email :boolean default(FALSE) -# receive_sms_notifications :boolean default(FALSE), not null -# reset_password_sent_at :datetime -# reset_password_token :string -# sign_in_count :integer default(0), not null -# type :string -# unconfirmed_email :string -# created_at :datetime not null -# updated_at :datetime not null -# casa_org_id :bigint not null -# invited_by_id :bigint +# id :bigint not null, primary key +# active :boolean default(TRUE) +# confirmation_sent_at :datetime +# confirmation_token :string +# confirmed_at :datetime +# current_sign_in_at :datetime +# current_sign_in_ip :string +# display_name :string default(""), not null +# email :string default(""), not null +# encrypted_password :string default(""), not null +# invitation_accepted_at :datetime +# invitation_created_at :datetime +# invitation_limit :integer +# invitation_sent_at :datetime +# invitation_token :string +# invitations_count :integer default(0) +# invited_by_type :string +# last_sign_in_at :datetime +# last_sign_in_ip :string +# monthly_learning_hours_report :boolean default(FALSE), not null +# old_emails :string default([]), is an Array +# phone_number :string default("") +# receive_email_notifications :boolean default(TRUE) +# receive_reimbursement_email :boolean default(FALSE) +# receive_sms_notifications :boolean default(FALSE), not null +# reset_password_sent_at :datetime +# reset_password_token :string +# sign_in_count :integer default(0), not null +# type :string +# unconfirmed_email :string +# created_at :datetime not null +# updated_at :datetime not null +# casa_org_id :bigint not null +# invited_by_id :bigint # # Indexes # diff --git a/app/models/user.rb b/app/models/user.rb index bb47cb8ade..041322cbdf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -185,39 +185,40 @@ def normalize_phone_number # # Table name: users # -# id :bigint not null, primary key -# active :boolean default(TRUE) -# confirmation_sent_at :datetime -# confirmation_token :string -# confirmed_at :datetime -# current_sign_in_at :datetime -# current_sign_in_ip :string -# display_name :string default(""), not null -# email :string default(""), not null -# encrypted_password :string default(""), not null -# invitation_accepted_at :datetime -# invitation_created_at :datetime -# invitation_limit :integer -# invitation_sent_at :datetime -# invitation_token :string -# invitations_count :integer default(0) -# invited_by_type :string -# last_sign_in_at :datetime -# last_sign_in_ip :string -# old_emails :string default([]), is an Array -# phone_number :string default("") -# receive_email_notifications :boolean default(TRUE) -# receive_reimbursement_email :boolean default(FALSE) -# receive_sms_notifications :boolean default(FALSE), not null -# reset_password_sent_at :datetime -# reset_password_token :string -# sign_in_count :integer default(0), not null -# type :string -# unconfirmed_email :string -# created_at :datetime not null -# updated_at :datetime not null -# casa_org_id :bigint not null -# invited_by_id :bigint +# id :bigint not null, primary key +# active :boolean default(TRUE) +# confirmation_sent_at :datetime +# confirmation_token :string +# confirmed_at :datetime +# current_sign_in_at :datetime +# current_sign_in_ip :string +# display_name :string default(""), not null +# email :string default(""), not null +# encrypted_password :string default(""), not null +# invitation_accepted_at :datetime +# invitation_created_at :datetime +# invitation_limit :integer +# invitation_sent_at :datetime +# invitation_token :string +# invitations_count :integer default(0) +# invited_by_type :string +# last_sign_in_at :datetime +# last_sign_in_ip :string +# monthly_learning_hours_report :boolean default(FALSE), not null +# old_emails :string default([]), is an Array +# phone_number :string default("") +# receive_email_notifications :boolean default(TRUE) +# receive_reimbursement_email :boolean default(FALSE) +# receive_sms_notifications :boolean default(FALSE), not null +# reset_password_sent_at :datetime +# reset_password_token :string +# sign_in_count :integer default(0), not null +# type :string +# unconfirmed_email :string +# created_at :datetime not null +# updated_at :datetime not null +# casa_org_id :bigint not null +# invited_by_id :bigint # # Indexes # diff --git a/app/models/volunteer.rb b/app/models/volunteer.rb index 0dbb3e7bb9..b68b20a862 100644 --- a/app/models/volunteer.rb +++ b/app/models/volunteer.rb @@ -140,39 +140,40 @@ def cases_where_contact_made_in_days(num_days = CONTACT_MADE_IN_DAYS_NUM) # # Table name: users # -# id :bigint not null, primary key -# active :boolean default(TRUE) -# confirmation_sent_at :datetime -# confirmation_token :string -# confirmed_at :datetime -# current_sign_in_at :datetime -# current_sign_in_ip :string -# display_name :string default(""), not null -# email :string default(""), not null -# encrypted_password :string default(""), not null -# invitation_accepted_at :datetime -# invitation_created_at :datetime -# invitation_limit :integer -# invitation_sent_at :datetime -# invitation_token :string -# invitations_count :integer default(0) -# invited_by_type :string -# last_sign_in_at :datetime -# last_sign_in_ip :string -# old_emails :string default([]), is an Array -# phone_number :string default("") -# receive_email_notifications :boolean default(TRUE) -# receive_reimbursement_email :boolean default(FALSE) -# receive_sms_notifications :boolean default(FALSE), not null -# reset_password_sent_at :datetime -# reset_password_token :string -# sign_in_count :integer default(0), not null -# type :string -# unconfirmed_email :string -# created_at :datetime not null -# updated_at :datetime not null -# casa_org_id :bigint not null -# invited_by_id :bigint +# id :bigint not null, primary key +# active :boolean default(TRUE) +# confirmation_sent_at :datetime +# confirmation_token :string +# confirmed_at :datetime +# current_sign_in_at :datetime +# current_sign_in_ip :string +# display_name :string default(""), not null +# email :string default(""), not null +# encrypted_password :string default(""), not null +# invitation_accepted_at :datetime +# invitation_created_at :datetime +# invitation_limit :integer +# invitation_sent_at :datetime +# invitation_token :string +# invitations_count :integer default(0) +# invited_by_type :string +# last_sign_in_at :datetime +# last_sign_in_ip :string +# monthly_learning_hours_report :boolean default(FALSE), not null +# old_emails :string default([]), is an Array +# phone_number :string default("") +# receive_email_notifications :boolean default(TRUE) +# receive_reimbursement_email :boolean default(FALSE) +# receive_sms_notifications :boolean default(FALSE), not null +# reset_password_sent_at :datetime +# reset_password_token :string +# sign_in_count :integer default(0), not null +# type :string +# unconfirmed_email :string +# created_at :datetime not null +# updated_at :datetime not null +# casa_org_id :bigint not null +# invited_by_id :bigint # # Indexes # diff --git a/app/values/user_parameters.rb b/app/values/user_parameters.rb index aa7edccd80..a491c52115 100644 --- a/app/values/user_parameters.rb +++ b/app/values/user_parameters.rb @@ -10,6 +10,7 @@ def initialize(params, key = :user) :active, :receive_reimbursement_email, :type, + :monthly_learning_hours_report, address_attributes: [:id, :content] ) diff --git a/app/views/casa_admins/_form.html.erb b/app/views/casa_admins/_form.html.erb index 59fdde9c4c..777575b66a 100644 --- a/app/views/casa_admins/_form.html.erb +++ b/app/views/casa_admins/_form.html.erb @@ -19,6 +19,18 @@
<%= render "/shared/invite_login", resource: casa_admin %>
+ +
+ <% if current_user.casa_admin? %> +
+ <%= form.label :monthly_learning_hours_report, "Receive Monthly Learning Hours Report" %> + <%= form.check_box :monthly_learning_hours_report %> +
+ <% end %> +
+ +
+ <% if casa_admin.persisted? %>
<% if casa_admin.active? %> diff --git a/app/views/learning_hours_mailer/learning_hours_report_email.html.erb b/app/views/learning_hours_mailer/learning_hours_report_email.html.erb new file mode 100644 index 0000000000..75bcf16705 --- /dev/null +++ b/app/views/learning_hours_mailer/learning_hours_report_email.html.erb @@ -0,0 +1,78 @@ + + + + + + Actionable emails e.g. reset password + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + +
+
+
+
+

Learning hours have been added this week!

+

Please see the attached full report or export the full report <%= link_to "here", reports_url %>

+
+ + + + + + + +
+ +
+
+
+ + diff --git a/app/views/supervisors/edit.html.erb b/app/views/supervisors/edit.html.erb index 0a90770288..be7f2d1d81 100644 --- a/app/views/supervisors/edit.html.erb +++ b/app/views/supervisors/edit.html.erb @@ -11,6 +11,16 @@

<%= render "/shared/invite_login", resource: @supervisor %>

+ +
+ <% if current_user.casa_admin? || current_user.supervisor? %> +
+ <%= form.label :monthly_learning_hours_report, "Receive Monthly Learning Hours Report" %> + <%= form.check_box :monthly_learning_hours_report %> +
+ <% end %> +
+

<%= render "manage_active", user: @supervisor %>

@@ -26,6 +36,7 @@
<% end %> +
<% end %> diff --git a/db/migrate/20230902021531_add_monthly_learning_hours_report_to_user.rb b/db/migrate/20230902021531_add_monthly_learning_hours_report_to_user.rb new file mode 100644 index 0000000000..52e11db3f3 --- /dev/null +++ b/db/migrate/20230902021531_add_monthly_learning_hours_report_to_user.rb @@ -0,0 +1,5 @@ +class AddMonthlyLearningHoursReportToUser < ActiveRecord::Migration[7.0] + def change + add_column :users, :monthly_learning_hours_report, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 386822a8af..174c517674 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,9 @@ # # It's strongly recommended that you check this file into your version control system. + ActiveRecord::Schema[7.0].define(version: 2023_09_03_182657) do + # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -597,6 +599,7 @@ t.string "unconfirmed_email" t.string "old_emails", default: [], array: true t.boolean "receive_reimbursement_email", default: false + t.boolean "monthly_learning_hours_report", default: false, null: false t.index ["casa_org_id"], name: "index_users_on_casa_org_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true diff --git a/lib/mailers/previews/learning_hours_mailer_preview.rb b/lib/mailers/previews/learning_hours_mailer_preview.rb new file mode 100644 index 0000000000..1b85543c18 --- /dev/null +++ b/lib/mailers/previews/learning_hours_mailer_preview.rb @@ -0,0 +1,13 @@ +require_relative "../debug_preview_mailer" + +class LearningHoursMailerPreview < ActionMailer::Preview + def learning_hours_report_email + current_user = params.has_key?(:id) ? User.find_by(id: params[:id]) : User.first + + if current_user.nil? + DebugPreviewMailer.invalid_user("user") + else + LearningHoursMailer.learning_hours_report_email(current_user) + end + end +end diff --git a/lib/tasks/monthly_learning_hours_report.rake b/lib/tasks/monthly_learning_hours_report.rake new file mode 100644 index 0000000000..60352a8a88 --- /dev/null +++ b/lib/tasks/monthly_learning_hours_report.rake @@ -0,0 +1,13 @@ +desc "Scheduled once per month in Heroku Scheduler, this task will send a learning hours report to all Casa Admins and Supervisors" +task send_learning_hour_reports: :environment do + admins = CasaAdmin.active.where(monthly_learning_hours_report: true) + supervisors = Supervisor.active.where(monthly_learning_hours_report: true) + + admins.each do |admin| + LearningHoursMailer.learning_hours_report_email(admin).deliver_later + end + + supervisors.each do |supervisor| + LearningHoursMailer.learning_hours_report_email(supervisor).deliver_later + end +end diff --git a/spec/mailers/learning_hours_mailer_spec.rb b/spec/mailers/learning_hours_mailer_spec.rb new file mode 100644 index 0000000000..b074ec9ef8 --- /dev/null +++ b/spec/mailers/learning_hours_mailer_spec.rb @@ -0,0 +1,28 @@ +require "rails_helper" + +RSpec.describe LearningHoursMailer, type: :mailer do + describe "#learning_hours_report_email" do + let(:user) { create(:user) } + let(:casa_org) { create(:casa_org, users: [user]) } + let(:learning_hours) { [instance_double(LearningHour)] } + let(:csv_data) { "dummy,csv,data" } + + before do + allow(LearningHour).to receive(:where).and_return(learning_hours) + allow(LearningHoursExportCsvService).to receive(:new).and_return(instance_double(LearningHoursExportCsvService, perform: csv_data)) + end + + it "sends the email to the provided user with correct subject and attachment" do + mail = LearningHoursMailer.learning_hours_report_email(user) + + expect(mail.to).to eq([user.email]) + + end_date = Date.today.end_of_month + expected_subject = "Learning Hours Report for #{end_date.strftime("%B, %Y")}." + expect(mail.subject).to eq(expected_subject) + + expect(mail.attachments.first.filename).to eq("learning-hours-report-#{Date.today}.csv") + expect(mail.attachments.first.body.raw_source).to eq(csv_data) + end + end +end diff --git a/spec/system/casa_admins/edit_spec.rb b/spec/system/casa_admins/edit_spec.rb index 5d5e5ad30c..7537433881 100644 --- a/spec/system/casa_admins/edit_spec.rb +++ b/spec/system/casa_admins/edit_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe "casa_admins/edit", type: :system do - let(:admin) { create :casa_admin } + let(:admin) { create :casa_admin, monthly_learning_hours_report: false } before { sign_in admin } @@ -14,6 +14,7 @@ fill_in "Display name", with: expected_display_name fill_in "Phone number", with: expected_phone_number + check "Receive Monthly Learning Hours Report" click_on "Submit" @@ -23,6 +24,7 @@ expect(admin.display_name).to eq expected_display_name expect(admin.phone_number).to eq expected_phone_number + expect(admin.monthly_learning_hours_report).to be_truthy end end