Skip to content

Commit

Permalink
feat: add contact_topics to org and case contact
Browse files Browse the repository at this point in the history
contact_topics is a json column on both org and case_contact

create topic defaults, set in file data/default_contact_topics.yml

changes method to generate org defaults:
- it now also generates topic defaults
- adds an error if generation fails validation
- renamed to be more generic

adds topic generation to seeding

adds validation for contact topics for org and case_contact:
- expects topics to be a hash or array of hashes
- PERMITTED_KEYS in ContactTopics class defines what hashes can contain

adds model tests for ContactTopics, CasaOrg and CaseContact

adds request tests for case_contacts_controller and form_controller:
- currently these only test updating and creating records

adds a trait to casa_org to disable twilio, needed to pass tests
  • Loading branch information
elasticspoon committed Feb 1, 2024
1 parent f62f955 commit 0c8bd08
Show file tree
Hide file tree
Showing 21 changed files with 599 additions and 181 deletions.
2 changes: 1 addition & 1 deletion app/controllers/all_casa_admins/casa_orgs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def create
@casa_org = CasaOrg.new(casa_org_params)

if @casa_org.save
@casa_org.generate_contact_types_and_hearing_types
@casa_org.generate_defaults
respond_to do |format|
format.html do
redirect_to all_casa_admins_casa_org_path(@casa_org),
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/casa_org_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ def casa_org_update_params
:twilio_api_key_sid,
:twilio_api_key_secret,
:twilio_enabled,
:learning_topic_active
:learning_topic_active,
contact_topics: ContactTopics::PERMITTED_ATTRIBUTES
)
end

Expand Down
26 changes: 13 additions & 13 deletions app/controllers/case_contacts/form_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@ def update
remove_nil_draft_ids
if @case_contact.update(case_contact_params)
respond_to do |format|
format.html {
format.html do
if step == steps.last
finish_editing
else
render_wizard @case_contact, {}, {case_contact_id: @case_contact.id}
end
}
end
format.json { head :ok }
end
else
respond_to do |format|
format.html {
format.html do
get_cases_and_contact_types
render step
}
end
format.json { head :internal_server_error }
end
end
Expand Down Expand Up @@ -80,9 +80,9 @@ def finish_editing
end

def send_reimbursement_email(case_contact)
if case_contact.should_send_reimbursement_email?
SupervisorMailer.reimbursement_request_email(case_contact.creator, case_contact.supervisor).deliver_later
end
return unless case_contact.should_send_reimbursement_email?

SupervisorMailer.reimbursement_request_email(case_contact.creator, case_contact.supervisor).deliver_later
end

def update_volunteer_address(case_contact)
Expand Down Expand Up @@ -124,15 +124,15 @@ def case_contact_params
# Deletes the current associations (from the join table) only if the submitted form body has the parameters for
# the contact_type ids.
def remove_unwanted_contact_types
if params.dig(:case_contact, :case_contact_contact_type_attributes)
@case_contact.case_contact_contact_type.destroy_all
end
return unless params.dig(:case_contact, :case_contact_contact_type_attributes)

@case_contact.case_contact_contact_type.destroy_all
end

def remove_nil_draft_ids
if params.dig(:case_contact, :draft_case_ids)
params[:case_contact][:draft_case_ids] -= [""]
end
return unless params.dig(:case_contact, :draft_case_ids)

params[:case_contact][:draft_case_ids] -= [""]
end

def set_progress
Expand Down
8 changes: 5 additions & 3 deletions app/controllers/case_contacts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def new
[]
end

@case_contact = CaseContact.create(creator: current_user, draft_case_ids: draft_case_ids)
@case_contact = CaseContact.create(creator: current_user, draft_case_ids:)
@case_contact.contact_topics_from(current_organization)
redirect_to case_contact_form_path(CaseContact::FORM_STEPS.first, case_contact_id: @case_contact.id)
end

Expand Down Expand Up @@ -84,9 +85,10 @@ def leave
def update_or_create_additional_expense(all_ae_params, cc)
all_ae_params.each do |ae_params|
id = ae_params[:id]
current = AdditionalExpense.find_by(id: id)
current = AdditionalExpense.find_by(id:)
if current
current.assign_attributes(other_expense_amount: ae_params[:other_expense_amount], other_expenses_describe: ae_params[:other_expenses_describe])
current.assign_attributes(other_expense_amount: ae_params[:other_expense_amount],
other_expenses_describe: ae_params[:other_expenses_describe])
save_or_add_error(current, cc)
else
create_new_exp = cc.additional_expenses.build(ae_params)
Expand Down
38 changes: 27 additions & 11 deletions app/models/casa_org.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class CasaOrg < ApplicationRecord
CASA_DEFAULT_COURT_REPORT = File.new(Rails.root.join("app", "documents", "templates", "default_report_template.docx"), "r")
CASA_DEFAULT_COURT_REPORT = File.new(
Rails.root.join("app", "documents", "templates", "default_report_template.docx"), "r"
)
CASA_DEFAULT_LOGO = Rails.root.join("public", "logo.jpeg")

scope :with_logo, -> { joins(:logo_attachment) }
Expand All @@ -10,7 +12,10 @@ class CasaOrg < ApplicationRecord

validates :name, presence: true, uniqueness: true
validates_with CasaOrgValidator
validate :validate_twilio_credentials, if: -> { twilio_enabled || twilio_account_sid.present? || twilio_api_key_sid.present? || twilio_api_key_secret.present? }, on: :update
validate :validate_twilio_credentials, if: lambda {
twilio_enabled || twilio_account_sid.present? || twilio_api_key_sid.present? || twilio_api_key_secret.present?
}, on: :update
validate :validate_contact_topics

has_many :users, dependent: :destroy
has_many :casa_cases, dependent: :destroy
Expand Down Expand Up @@ -85,11 +90,17 @@ def set_slug
self.slug = name.parameterize
end

def generate_contact_types_and_hearing_types
def generate_defaults
ActiveRecord::Base.transaction do
ContactTypeGroup.generate_for_org!(self)
HearingType.generate_for_org!(self)
ContactTopics.generate_for_org!(self)
end
# FIXME: is this needed? do we care about the case where the defaults
# are invalid?
rescue ActiveRecord::RecordInvalid => e
errors.add(:base,
"Failed to generate default contact type groups, hearing types, or contact topics: #{e.message}")
end

def contact_types_by_group
Expand All @@ -114,11 +125,11 @@ def has_alternate_active_banner?(current_banner_id)
private

def sanitize_svg
if attachment_changes["logo"]
file = attachment_changes["logo"].attachable
sanitized_file = SvgSanitizerService.sanitize(file)
logo.unfurl(sanitized_file)
end
return unless attachment_changes["logo"]

file = attachment_changes["logo"].attachable
sanitized_file = SvgSanitizerService.sanitize(file)
logo.unfurl(sanitized_file)
end

# def to_param
Expand All @@ -135,10 +146,14 @@ def validate_twilio_credentials
end
end

def validate_contact_topics
ContactTopics.validate(self)
end

def normalize_phone_number
if twilio_phone_number&.length == 10
self.twilio_phone_number = "+1#{twilio_phone_number}"
end
return unless twilio_phone_number&.length == 10

self.twilio_phone_number = "+1#{twilio_phone_number}"
end
end

Expand All @@ -149,6 +164,7 @@ def normalize_phone_number
# id :bigint not null, primary key
# additional_expenses_enabled :boolean default(FALSE)
# address :string
# contact_topics :jsonb
# display_name :string
# footer_links :string default([]), is an Array
# learning_topic_active :boolean default(FALSE)
Expand Down
102 changes: 57 additions & 45 deletions app/models/case_contact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ class CaseContact < ApplicationRecord
attr_accessor :duration_hours

validate :contact_made_chosen
validates :miles_driven, numericality: {greater_than_or_equal_to: 0, less_than: 10000}
validates :miles_driven, numericality: {greater_than_or_equal_to: 0, less_than: 10_000}
validates :medium_type, presence: true, if: :active_or_details?
validates :occurred_at, presence: true, if: :active_or_details?
validates :duration_minutes, presence: true, if: :active_or_details?
validate :occurred_at_not_in_future
validate :reimbursement_only_when_miles_driven, if: :active_or_expenses?
validate :volunteer_address_when_reimbursement_wanted, if: :active_or_expenses?
validate :volunteer_address_is_valid, if: :active_or_expenses?
validate :validate_contact_topics

belongs_to :creator, class_name: "User"
has_one :supervisor_volunteer, -> {
has_one :supervisor_volunteer, lambda {
where(is_active: true)
}, primary_key: :creator_id, foreign_key: :volunteer_id
has_one :supervisor, through: :creator
Expand Down Expand Up @@ -50,71 +51,71 @@ def active_or_expenses?
accepts_nested_attributes_for :case_contact_contact_type
accepts_nested_attributes_for :casa_case

scope :supervisors, ->(supervisor_ids = nil) {
joins(:supervisor_volunteer).where(supervisor_volunteers: {supervisor_id: supervisor_ids}) if supervisor_ids.present?
scope :supervisors, lambda { |supervisor_ids = nil|
if supervisor_ids.present?
joins(:supervisor_volunteer).where(supervisor_volunteers: {supervisor_id: supervisor_ids})
end
}
scope :creators, ->(creator_ids = nil) {
scope :creators, lambda { |creator_ids = nil|
where(creator_id: creator_ids) if creator_ids.present?
}
scope :casa_org, ->(casa_org_id = nil) {
joins(:casa_case).where(casa_cases: {casa_org_id: casa_org_id}) if casa_org_id.present?
scope :casa_org, lambda { |casa_org_id = nil|
joins(:casa_case).where(casa_cases: {casa_org_id:}) if casa_org_id.present?
}
scope :occurred_between, ->(start_date = nil, end_date = nil) {
scope :occurred_between, lambda { |start_date = nil, end_date = nil|
where("occurred_at BETWEEN ? AND ?", start_date, end_date) if start_date.present? && end_date.present?
}
scope :occurred_starting_at, ->(start_date = nil) {
scope :occurred_starting_at, lambda { |start_date = nil|
where("occurred_at >= ?", start_date) if start_date.present?
}
scope :occurred_ending_at, ->(end_date = nil) {
scope :occurred_ending_at, lambda { |end_date = nil|
where("occurred_at <= ?", end_date) if end_date.present?
}
scope :created_max_ago, ->(time_range = nil) {
scope :created_max_ago, lambda { |time_range = nil|
where("case_contacts.created_at > ?", time_range) if time_range.present?
}
scope :contact_made, ->(contact_made = nil) {
where(contact_made: contact_made) if /true|false/.match?(contact_made.to_s)
scope :contact_made, lambda { |contact_made = nil|
where(contact_made:) if /true|false/.match?(contact_made.to_s)
}
scope :has_transitioned, ->(has_transitioned = nil) {
scope :has_transitioned, lambda { |has_transitioned = nil|
if /true|false/.match?(has_transitioned.to_s)
operator = has_transitioned ? "<=" : ">"

joins(:casa_case).where("casa_cases.birth_month_year_youth #{operator} ?", CasaCase::TRANSITION_AGE.years.ago)
end
}
scope :want_driving_reimbursement, ->(want_driving_reimbursement = nil) {
if /true|false/.match?(want_driving_reimbursement.to_s)
where(want_driving_reimbursement: want_driving_reimbursement)
end
scope :want_driving_reimbursement, lambda { |want_driving_reimbursement = nil|
where(want_driving_reimbursement:) if /true|false/.match?(want_driving_reimbursement.to_s)
}
scope :contact_type, ->(contact_type_ids = nil) {
scope :contact_type, lambda { |contact_type_ids = nil|
includes(:contact_types).where("contact_types.id": [contact_type_ids]) if contact_type_ids.present?
}
scope :contact_types, ->(contact_type_id_list = nil) {
scope :contact_types, lambda { |contact_type_id_list = nil|
contact_type_id_list.reject! { |id| id.blank? }

return if contact_type_id_list.blank?

includes(:contact_types).where("contact_types.id": contact_type_id_list)
}
scope :contact_type_groups, ->(contact_type_group_ids = nil) {
scope :contact_type_groups, lambda { |contact_type_group_ids = nil|
# to handle case when passing ids == [''] && ids == nil
if contact_type_group_ids&.join&.length&.positive?
joins(contact_types: :contact_type_group)
.where(contact_type_groups: {id: contact_type_group_ids})
.group(:id)
end
}
scope :grab_all, ->(current_user) {
with_deleted if current_user.is_a?(CasaAdmin) # TODO since this cases on user type it should be in a Policy file
scope :grab_all, lambda { |current_user|
with_deleted if current_user.is_a?(CasaAdmin) # TODO: since this cases on user type it should be in a Policy file
}

scope :contact_medium, ->(medium_type) {
where(medium_type: medium_type) if medium_type.present?
scope :contact_medium, lambda { |medium_type|
where(medium_type:) if medium_type.present?
}

scope :filter_by_reimbursement_status, ->(boolean) { where reimbursement_complete: boolean }

scope :sorted_by, ->(sort_option) {
scope :sorted_by, lambda { |sort_option|
direction = /desc$/.match?(sort_option) ? "desc" : "asc"

case sort_option.to_s
Expand All @@ -133,23 +134,23 @@ def active_or_expenses?
end
}

scope :with_casa_case, ->(case_ids) {
scope :with_casa_case, lambda { |case_ids|
where(casa_case_id: case_ids) if case_ids.present?
}

scope :no_drafts, ->(checked) { (checked == 1) ? where(status: "active") : all }

filterrific(
default_filter_params: {sorted_by: "occurred_at_desc"},
available_filters: [
:sorted_by,
:occurred_starting_at,
:occurred_ending_at,
:contact_type,
:contact_made,
:contact_medium,
:want_driving_reimbursement,
:no_drafts
available_filters: %i[
sorted_by
occurred_starting_at
occurred_ending_at
contact_type
contact_made
contact_medium
want_driving_reimbursement
no_drafts
]
)

Expand Down Expand Up @@ -195,17 +196,16 @@ def reimbursement_only_when_miles_driven
end

def volunteer_address_when_reimbursement_wanted
if want_driving_reimbursement && volunteer_address&.empty?
errors.add(:base, "Must enter a valid mailing address for the reimbursement.")
end
return unless want_driving_reimbursement && volunteer_address&.empty?

errors.add(:base, "Must enter a valid mailing address for the reimbursement.")
end

def volunteer_address_is_valid
if volunteer_address&.present?
if Address.new(user_id: creator.id, content: volunteer_address).invalid?
errors.add(:base, "The volunteer's address is not valid.")
end
end
return unless volunteer_address&.present?
return unless Address.new(user_id: creator.id, content: volunteer_address).invalid?

errors.add(:base, "The volunteer's address is not valid.")
end

def contact_made_chosen
Expand Down Expand Up @@ -257,14 +257,25 @@ def volunteer
end
end

def validate_contact_topics
ContactTopics.validate(self)
end

def contact_topics_from(source)
self.contact_topics = source.contact_topics
save!
end

def self.options_for_sorted_by
sorted_by_params.each.map { |option_pair| option_pair.reverse }
end

def self.case_hash_from_cases(cases)
casa_case_ids = cases.map(&:draft_case_ids).flatten.uniq.sort
casa_case_ids.each_with_object({}) do |casa_case_id, hash|
hash[casa_case_id] = cases.select { |c| c.casa_case_id == casa_case_id || c.draft_case_ids.include?(casa_case_id) }
hash[casa_case_id] = cases.select do |c|
c.casa_case_id == casa_case_id || c.draft_case_ids.include?(casa_case_id)
end
end
end

Expand All @@ -290,6 +301,7 @@ def self.case_hash_from_cases(cases)
#
# id :bigint not null, primary key
# contact_made :boolean default(FALSE)
# contact_topics :jsonb
# deleted_at :datetime
# draft_case_ids :integer default([]), is an Array
# duration_minutes :integer
Expand Down
Loading

0 comments on commit 0c8bd08

Please sign in to comment.