diff --git a/.allow_skipping_tests b/.allow_skipping_tests
index 53c4343744..70f79741f3 100644
--- a/.allow_skipping_tests
+++ b/.allow_skipping_tests
@@ -9,11 +9,13 @@ controllers/learning_hour_types_controller.rb
controllers/mileage_reports_controller.rb
controllers/placements_controller.rb
controllers/users/sessions_controller.rb
+controllers/learning_hour_topics_controller.rb
datatables/application_datatable.rb
decorators/application_decorator.rb
decorators/case_assignment_decorator.rb
decorators/court_date_decorator.rb
decorators/learning_hour_decorator.rb
+decorators/learning_hour_topic_decorator.rb
helpers/all_casa_admins/casa_orgs_helper.rb
helpers/api_base_helper.rb
helpers/date_helper.rb
@@ -35,6 +37,7 @@ notifications/youth_birthday_notification.rb
policies/fund_request_policy.rb
policies/learning_hour_policy.rb
policies/learning_hour_type_policy.rb
+policies/learning_hour_topic_policy.rb
policies/note_policy.rb
presenters/base_presenter.rb
presenters/case_contact_presenter.rb
diff --git a/app/controllers/casa_org_controller.rb b/app/controllers/casa_org_controller.rb
index 6dda47ad19..115645b78e 100644
--- a/app/controllers/casa_org_controller.rb
+++ b/app/controllers/casa_org_controller.rb
@@ -4,6 +4,7 @@ class CasaOrgController < ApplicationController
before_action :set_hearing_types, only: %i[edit update]
before_action :set_judges, only: %i[edit update]
before_action :set_learning_hour_types, only: %i[edit update]
+ before_action :set_learning_hour_topics, only: %i[edit update]
before_action :set_sent_emails, only: %i[edit update]
before_action :require_organization!
after_action :verify_authorized
@@ -52,7 +53,8 @@ def casa_org_update_params
:twilio_phone_number,
:twilio_api_key_sid,
:twilio_api_key_secret,
- :twilio_enabled
+ :twilio_enabled,
+ :learning_topic_active
)
end
@@ -76,4 +78,8 @@ def set_learning_hour_types
def set_sent_emails
@sent_emails = SentEmail.for_organization(@casa_org).order("created_at DESC").limit(10)
end
+
+ def set_learning_hour_topics
+ @learning_hour_topics = LearningHourTopic.for_organization(@casa_org)
+ end
end
diff --git a/app/controllers/learning_hour_topics_controller.rb b/app/controllers/learning_hour_topics_controller.rb
new file mode 100644
index 0000000000..7063e9ff65
--- /dev/null
+++ b/app/controllers/learning_hour_topics_controller.rb
@@ -0,0 +1,46 @@
+class LearningHourTopicsController < ApplicationController
+ before_action :set_learning_hour_topic, only: %i[edit update]
+ after_action :verify_authorized
+
+ def new
+ authorize LearningHourTopic
+ @learning_hour_topic = LearningHourTopic.new
+ end
+
+ def edit
+ authorize @learning_hour_topic
+ end
+
+ def create
+ authorize LearningHourTopic
+ @learning_hour_topic = LearningHourTopic.new(learning_hour_topic_params)
+
+ if @learning_hour_topic.save
+ redirect_to edit_casa_org_path(current_organization), notice: "Learning Topic was successfully created."
+ else
+ render :new
+ end
+ end
+
+ def update
+ authorize @learning_hour_topic
+
+ if @learning_hour_topic.update(learning_hour_topic_params)
+ redirect_to edit_casa_org_path(current_organization), notice: "Learning Topic was successfully updated."
+ else
+ render :edit
+ end
+ end
+
+ private
+
+ def set_learning_hour_topic
+ @learning_hour_topic = LearningHourTopic.find(params[:id])
+ end
+
+ def learning_hour_topic_params
+ params.require(:learning_hour_topic).permit(:name, :active).merge(
+ casa_org: current_organization
+ )
+ end
+end
diff --git a/app/controllers/learning_hours_controller.rb b/app/controllers/learning_hours_controller.rb
index 93644b210a..1728c319ae 100644
--- a/app/controllers/learning_hours_controller.rb
+++ b/app/controllers/learning_hours_controller.rb
@@ -58,11 +58,11 @@ def set_learning_hour
def learning_hours_params
params.require(:learning_hour).permit(:occurred_at, :duration_minutes, :duration_hours, :name, :user_id,
- :learning_hour_type_id)
+ :learning_hour_type_id, :learning_hour_topic_id)
end
def update_learning_hours_params
params.require(:learning_hour).permit(:occurred_at, :duration_minutes, :duration_hours, :name,
- :learning_hour_type_id)
+ :learning_hour_type_id, :learning_hour_topic_id)
end
end
diff --git a/app/decorators/learning_hour_topic_decorator.rb b/app/decorators/learning_hour_topic_decorator.rb
new file mode 100644
index 0000000000..13b9a0051a
--- /dev/null
+++ b/app/decorators/learning_hour_topic_decorator.rb
@@ -0,0 +1,12 @@
+class LearningHourTopicDecorator < ApplicationDecorator
+ delegate_all
+
+ # Define presentation-specific methods here. Helpers are accessed through
+ # `helpers` (aka `h`). You can override attributes, for example:
+ #
+ # def created_at
+ # helpers.content_tag :span, class: 'time' do
+ # object.created_at.strftime("%a %m/%d/%y")
+ # end
+ # end
+end
diff --git a/app/models/casa_org.rb b/app/models/casa_org.rb
index 6ba3158377..e92af6e465 100644
--- a/app/models/casa_org.rb
+++ b/app/models/casa_org.rb
@@ -22,6 +22,7 @@ class CasaOrg < ApplicationRecord
has_many :placements, through: :casa_cases
has_many :banners, dependent: :destroy
has_many :learning_hour_types, dependent: :destroy
+ has_many :learning_hour_topics, dependent: :destroy
has_many :case_groups, dependent: :destroy
has_one_attached :logo
has_one_attached :court_report_template
@@ -131,6 +132,7 @@ def normalize_phone_number
# address :string
# display_name :string
# footer_links :string default([]), is an Array
+# learning_topic_active :boolean default(FALSE)
# name :string not null
# show_driving_reimbursement :boolean default(TRUE)
# show_fund_request :boolean default(FALSE)
diff --git a/app/models/learning_hour.rb b/app/models/learning_hour.rb
index 8967657267..b9fb3565d9 100644
--- a/app/models/learning_hour.rb
+++ b/app/models/learning_hour.rb
@@ -1,12 +1,14 @@
class LearningHour < ApplicationRecord
belongs_to :user
belongs_to :learning_hour_type
+ belongs_to :learning_hour_topic, optional: true
validates :duration_minutes, presence: true
validates :duration_minutes, numericality: {greater_than: 0}, if: :zero_duration_hours?
validates :name, presence: {message: "/ Title cannot be blank"}
validates :occurred_at, presence: true
validate :occurred_at_not_in_future
+ validates :learning_hour_topic, presence: true, if: :user_org_learning_topic_enable?
private
@@ -21,26 +23,32 @@ def occurred_at_not_in_future
errors.add(:date, "cannot be in the future")
end
end
+
+ def user_org_learning_topic_enable?
+ user.casa_org.learning_topic_active
+ end
end
# == Schema Information
#
# Table name: learning_hours
#
-# id :bigint not null, primary key
-# duration_hours :integer not null
-# duration_minutes :integer not null
-# name :string not null
-# occurred_at :datetime not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# learning_hour_type_id :bigint
-# user_id :bigint not null
+# id :bigint not null, primary key
+# duration_hours :integer not null
+# duration_minutes :integer not null
+# name :string not null
+# occurred_at :datetime not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# learning_hour_topic_id :bigint
+# learning_hour_type_id :bigint
+# user_id :bigint not null
#
# Indexes
#
-# index_learning_hours_on_learning_hour_type_id (learning_hour_type_id)
-# index_learning_hours_on_user_id (user_id)
+# index_learning_hours_on_learning_hour_topic_id (learning_hour_topic_id)
+# index_learning_hours_on_learning_hour_type_id (learning_hour_type_id)
+# index_learning_hours_on_user_id (user_id)
#
# Foreign Keys
#
diff --git a/app/models/learning_hour_topic.rb b/app/models/learning_hour_topic.rb
new file mode 100644
index 0000000000..5e7da7f6e0
--- /dev/null
+++ b/app/models/learning_hour_topic.rb
@@ -0,0 +1,32 @@
+class LearningHourTopic < ApplicationRecord
+ belongs_to :casa_org
+ validates :name, presence: true, uniqueness: {scope: %i[casa_org], case_sensitive: false}
+ before_validation :strip_name
+ scope :for_organization, ->(org) { where(casa_org: org).order(:name) }
+
+ private
+
+ def strip_name
+ self.name = name.strip if name
+ end
+end
+
+# == Schema Information
+#
+# Table name: learning_hour_topics
+#
+# id :bigint not null, primary key
+# name :string not null
+# position :integer default(1)
+# created_at :datetime not null
+# updated_at :datetime not null
+# casa_org_id :bigint not null
+#
+# Indexes
+#
+# index_learning_hour_topics_on_casa_org_id (casa_org_id)
+#
+# Foreign Keys
+#
+# fk_rails_... (casa_org_id => casa_orgs.id)
+#
diff --git a/app/policies/learning_hour_topic_policy.rb b/app/policies/learning_hour_topic_policy.rb
new file mode 100644
index 0000000000..1e4f0d92f6
--- /dev/null
+++ b/app/policies/learning_hour_topic_policy.rb
@@ -0,0 +1,2 @@
+class LearningHourTopicPolicy < ApplicationPolicy
+end
diff --git a/app/views/casa_org/_learning_hour_topics.html.erb b/app/views/casa_org/_learning_hour_topics.html.erb
new file mode 100644
index 0000000000..c53925f5bc
--- /dev/null
+++ b/app/views/casa_org/_learning_hour_topics.html.erb
@@ -0,0 +1,50 @@
+
+
+
+
+
+
Learning Topic
+
+
+
+
+ <%= link_to new_learning_hour_topic_path, class: "btn-sm main-btn primary-btn btn-hover" do %>
+
+ New Learning Topic
+ <% end %>
+
+
+
+
+
+
+
+
+ Name |
+ Actions |
+
+
+
+ <% @learning_hour_topics.each do |learning_hour_topic| %>
+
+
+ <%= learning_hour_topic.name %>
+ |
+
+
+ <%= link_to edit_learning_hour_topic_path(learning_hour_topic) do %>
+
+
+
+ <% end %>
+ |
+
+ <% end %>
+
+
+
+
+
+
diff --git a/app/views/casa_org/edit.html.erb b/app/views/casa_org/edit.html.erb
index 26a7426157..35da5b82a9 100644
--- a/app/views/casa_org/edit.html.erb
+++ b/app/views/casa_org/edit.html.erb
@@ -45,6 +45,10 @@
<%= form.check_box :show_driving_reimbursement, class: 'form-check-input' %>
<%= form.label :show_driving_reimbursement, "Show driving reimbursement", class: 'form-check-label mb-2' %>
+
+ <%= form.check_box :learning_topic_active, class: 'form-check-input' %>
+ <%= form.label :learning_topic_active, "Enable Learning Topic", class: 'form-check-label mb-2' %>
+
-
+ <% if current_user.casa_org.learning_topic_active %>
+
+ <%= form.label :learning_hour_topic_id, "Learning Topic" %>
+
+ <%= form.collection_select :learning_hour_topic_id,
+ LearningHourTopic.for_organization(current_user.casa_org),
+ :id,
+ :name,
+ prompt: "Select learning topic",
+ value: @learning_hour.learning_hour_topic_id %>
+
+
+ <% end %>
+
Learning Duration
diff --git a/app/views/learning_hours/_update_form.html.erb b/app/views/learning_hours/_update_form.html.erb
index b24d1ca79e..ea7e585a66 100644
--- a/app/views/learning_hours/_update_form.html.erb
+++ b/app/views/learning_hours/_update_form.html.erb
@@ -26,6 +26,19 @@
value: @learning_hour.learning_hour_type_id %>
+ <% if current_user.casa_org.learning_topic_active %>
+
+ <%= form.label :learning_hour_topic_id, "Learning Topic" %>
+
+ <%= form.collection_select :learning_hour_topic_id,
+ LearningHourTopic.for_organization(current_user.casa_org),
+ :id,
+ :name,
+ prompt: "Select learning topic",
+ value: @learning_hour.learning_hour_topic_id %>
+
+
+ <% end %>
Learning Duration
diff --git a/config/routes.rb b/config/routes.rb
index c648995436..9b8ac8faa3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -110,6 +110,7 @@
resources :missing_data_reports, only: %i[index]
resources :learning_hours_reports, only: %i[index]
resources :learning_hour_types, only: %i[new create edit update]
+ resources :learning_hour_topics, only: %i[new create edit update]
resources :followup_reports, only: :index
resources :placement_reports, only: :index
resources :banners, only: %i[index new edit create update destroy]
diff --git a/db/migrate/20230817144910_create_learning_hour_topics.rb b/db/migrate/20230817144910_create_learning_hour_topics.rb
new file mode 100644
index 0000000000..74d1e2cace
--- /dev/null
+++ b/db/migrate/20230817144910_create_learning_hour_topics.rb
@@ -0,0 +1,11 @@
+class CreateLearningHourTopics < ActiveRecord::Migration[7.0]
+ def change
+ create_table :learning_hour_topics do |t|
+ t.string :name, null: false
+ t.references :casa_org, null: false, foreign_key: true
+ t.integer :position, default: 1
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20230819124840_add_learning_hour_topic_id_to_learning_hour.rb b/db/migrate/20230819124840_add_learning_hour_topic_id_to_learning_hour.rb
new file mode 100644
index 0000000000..fd1fbc56ad
--- /dev/null
+++ b/db/migrate/20230819124840_add_learning_hour_topic_id_to_learning_hour.rb
@@ -0,0 +1,7 @@
+class AddLearningHourTopicIdToLearningHour < ActiveRecord::Migration[7.0]
+ disable_ddl_transaction!
+
+ def change
+ add_reference :learning_hours, :learning_hour_topic, index: {algorithm: :concurrently}
+ end
+end
diff --git a/db/migrate/20230819132316_add_learning_topic_active_to_casa_org.rb b/db/migrate/20230819132316_add_learning_topic_active_to_casa_org.rb
new file mode 100644
index 0000000000..536728d54a
--- /dev/null
+++ b/db/migrate/20230819132316_add_learning_topic_active_to_casa_org.rb
@@ -0,0 +1,5 @@
+class AddLearningTopicActiveToCasaOrg < ActiveRecord::Migration[7.0]
+ def change
+ add_column :casa_orgs, :learning_topic_active, :boolean, default: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 85c450ac5f..b6a7999e3f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -159,6 +159,7 @@
t.string "twilio_api_key_secret"
t.boolean "twilio_enabled", default: false
t.boolean "additional_expenses_enabled", default: false
+ t.boolean "learning_topic_active", default: false
t.index ["slug"], name: "index_casa_orgs_on_slug", unique: true
end
@@ -370,6 +371,15 @@
t.index ["casa_org_id"], name: "index_languages_on_casa_org_id"
end
+ create_table "learning_hour_topics", force: :cascade do |t|
+ t.string "name", null: false
+ t.bigint "casa_org_id", null: false
+ t.integer "position", default: 1
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["casa_org_id"], name: "index_learning_hour_topics_on_casa_org_id"
+ end
+
create_table "learning_hour_types", force: :cascade do |t|
t.bigint "casa_org_id", null: false
t.string "name"
@@ -389,6 +399,8 @@
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "learning_hour_type_id"
+ t.bigint "learning_hour_topic_id"
+ t.index ["learning_hour_topic_id"], name: "index_learning_hours_on_learning_hour_topic_id"
t.index ["learning_hour_type_id"], name: "index_learning_hours_on_learning_hour_type_id"
t.index ["user_id"], name: "index_learning_hours_on_user_id"
end
@@ -618,6 +630,7 @@
add_foreign_key "followups", "users", column: "creator_id"
add_foreign_key "judges", "casa_orgs"
add_foreign_key "languages", "casa_orgs"
+ add_foreign_key "learning_hour_topics", "casa_orgs"
add_foreign_key "learning_hour_types", "casa_orgs"
add_foreign_key "learning_hours", "learning_hour_types"
add_foreign_key "learning_hours", "users"
diff --git a/db/seeds.rb b/db/seeds.rb
index 1026c42493..1300fdd00c 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -63,6 +63,7 @@ def active_record_classes
Judge,
Language,
LearningHourType,
+ LearningHourTopic,
MileageRate,
Supervisor,
SupervisorVolunteer,
diff --git a/db/seeds/db_populator.rb b/db/seeds/db_populator.rb
index 2dac6e18ea..f14db750cf 100644
--- a/db/seeds/db_populator.rb
+++ b/db/seeds/db_populator.rb
@@ -51,6 +51,7 @@ def create_org(options_hash)
create_languages(casa_org)
create_mileage_rates(casa_org)
create_learning_hour_types(casa_org)
+ create_learning_hour_topics(casa_org)
casa_org
end
@@ -377,6 +378,14 @@ def create_learning_hour_types(casa_org)
end
end
+ def create_learning_hour_topics(casa_org)
+ learning_topics = %w[cases reimbursements court_reports]
+ learning_topics.each do |learning_topic|
+ learning_hour_topic = casa_org.learning_hour_topics.new(name: learning_topic.humanize.capitalize)
+ learning_hour_topic.save
+ end
+ end
+
def most_recent_past_court_date(casa_case_id)
CourtDate.where(
"date < ? AND casa_case_id = ?",
diff --git a/spec/factories/learning_hour_topics.rb b/spec/factories/learning_hour_topics.rb
new file mode 100644
index 0000000000..975eb565a1
--- /dev/null
+++ b/spec/factories/learning_hour_topics.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :learning_hour_topic do
+ casa_org { CasaOrg.first || create(:casa_org) }
+ sequence(:name) { |n| "Learning Hour Type #{n}" }
+ position { 1 }
+ end
+end
diff --git a/spec/models/learning_hour_spec.rb b/spec/models/learning_hour_spec.rb
index cf6d370248..19afbc75c5 100644
--- a/spec/models/learning_hour_spec.rb
+++ b/spec/models/learning_hour_spec.rb
@@ -39,4 +39,17 @@
learning_hour = build_stubbed(:learning_hour, occurred_at: 1.day.from_now.strftime("%d %b %Y"))
expect(learning_hour).to_not be_valid
end
+
+ it "does not require learning_hour_topic if casa_org learning_hour_topic disabled" do
+ learning_hour = build_stubbed(:learning_hour, learning_hour_topic: nil)
+ expect(learning_hour).to be_valid
+ end
+
+ it "requires learning_hour_topic if casa_org learning_hour_topic enabled" do
+ casa_org = build(:casa_org, learning_topic_active: true)
+ user = build(:user, casa_org: casa_org)
+ learning_hour = build(:learning_hour, user: user)
+ expect(learning_hour).to_not be_valid
+ expect(learning_hour.errors[:learning_hour_topic]).to eq(["can't be blank"])
+ end
end
diff --git a/spec/models/learning_hour_topic_spec.rb b/spec/models/learning_hour_topic_spec.rb
new file mode 100644
index 0000000000..9600fd861c
--- /dev/null
+++ b/spec/models/learning_hour_topic_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe LearningHourTopic, type: :model do
+ it { is_expected.to belong_to(:casa_org) }
+ it { is_expected.to validate_presence_of(:name) }
+
+ it "has a valid factory" do
+ expect(build(:learning_hour_topic).valid?).to be true
+ end
+
+ it "has unique names for the specified organization" do
+ casa_org_one = create(:casa_org)
+ casa_org_two = create(:casa_org)
+ create(:learning_hour_topic, casa_org: casa_org_one, name: "Ethics")
+ expect { create(:learning_hour_topic, casa_org: casa_org_one, name: "Ethics") }
+ .to raise_error(ActiveRecord::RecordInvalid)
+ expect { create(:learning_hour_topic, casa_org: casa_org_one, name: "Ethics ") }
+ .to raise_error(ActiveRecord::RecordInvalid)
+ expect { create(:learning_hour_topic, casa_org: casa_org_one, name: "ethics") }
+ .to raise_error(ActiveRecord::RecordInvalid)
+ expect { create(:learning_hour_topic, casa_org: casa_org_two, name: "Ethics") }
+ .to_not raise_error
+ end
+
+ describe "for_organization" do
+ let!(:casa_org_one) { create(:casa_org) }
+ let!(:casa_org_two) { create(:casa_org) }
+ let!(:record_1) { create(:learning_hour_topic, casa_org: casa_org_one) }
+ let!(:record_2) { create(:learning_hour_topic, casa_org: casa_org_two) }
+
+ it "returns only records matching the specified organization" do
+ expect(described_class.for_organization(casa_org_one)).to eq([record_1])
+ expect(described_class.for_organization(casa_org_two)).to eq([record_2])
+ end
+ end
+end
diff --git a/spec/system/volunteers/new_spec.rb b/spec/system/volunteers/new_spec.rb
index 511ac0c559..1fbb6f80be 100644
--- a/spec/system/volunteers/new_spec.rb
+++ b/spec/system/volunteers/new_spec.rb
@@ -31,9 +31,9 @@
fill_in "Email", with: "new_volunteer2@example.com"
fill_in "Display name", with: "New Volunteer Display Name 2"
- expect {
+ expect do
click_on "Create Volunteer"
- }.to change(User, :count).by(1)
+ end.to change(User, :count).by(1)
end
end
@@ -48,4 +48,22 @@
expect(page).to have_selector(".alert", text: "Sorry, you are not authorized to perform this action.")
end
end
+
+ it "displays learning hour topic for volunteers when enabled", js: true do
+ organization = create(:casa_org, learning_topic_active: true)
+ volunteer = create(:volunteer, casa_org: organization)
+
+ sign_in volunteer
+ visit new_volunteer_learning_hour_path(volunteer)
+ expect(page).to have_text("Learning Topic")
+ end
+
+ it "learning hour topic hidden when disabled", js: true do
+ organization = create(:casa_org)
+ volunteer = create(:volunteer, casa_org: organization)
+
+ sign_in volunteer
+ visit new_volunteer_learning_hour_path(volunteer)
+ expect(page).to_not have_text("Learning Topic")
+ end
end
diff --git a/spec/views/casa_orgs/edit.html.erb_spec.rb b/spec/views/casa_orgs/edit.html.erb_spec.rb
index e08833db09..ec89074bfe 100644
--- a/spec/views/casa_orgs/edit.html.erb_spec.rb
+++ b/spec/views/casa_orgs/edit.html.erb_spec.rb
@@ -7,6 +7,7 @@
assign(:hearing_types, [])
assign(:judges, [])
assign(:learning_hour_types, [])
+ assign(:learning_hour_topics, [])
assign(:sent_emails, [])
sign_in build_stubbed(:casa_admin)