Skip to content

Commit

Permalink
feat: add contact topics to reports
Browse files Browse the repository at this point in the history
In #5388 I added ContacTopics and ContactTopicAnswers to CaseContacts.
This allowed CasaOrgs to set topics and Users to create
ContactTopicAnswers on specific CaseContacts.

Now we want to have those answers appear in the court reports.

This PR modifies CourtReportContext to include topic information, as
well as modifies CourtReport templates to accept that information.

Modified the templates to render the correct information. If an org has
no topics the fallback is the old values.

Additionally if a org has no topics the form/details page should not
show the card for court topics.

Adds after_party update task to update prod organizations with new
templates.
  • Loading branch information
elasticspoon committed Apr 20, 2024
1 parent c652ecc commit 6377f2b
Show file tree
Hide file tree
Showing 12 changed files with 284 additions and 2 deletions.
Binary file modified app/documents/templates/default_report_template.docx
100755 → 100644
Binary file not shown.
Binary file modified app/documents/templates/howard_county_report_template.docx
Binary file not shown.
Binary file modified app/documents/templates/montgomery_report_template.docx
100755 → 100644
Binary file not shown.
Binary file modified app/documents/templates/prince_george_report_template.docx
100755 → 100644
Binary file not shown.
48 changes: 47 additions & 1 deletion app/models/case_court_report_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ def context
latest_hearing_date: latest_hearing_date,
org_address: org_address(@path_to_template),
volunteer: volunteer_info,
hearing_type_name: @court_date&.hearing_type&.name || "None"
hearing_type_name: @court_date&.hearing_type&.name || "None",
case_topics: court_topics.values
}
end

Expand Down Expand Up @@ -88,6 +89,51 @@ def org_address(path_to_template)
@volunteer.casa_org.address if @volunteer && is_default_template
end

# Sample output
#
# expected_topics = {
# "Question 1" => {topic: "Question 1", details: "Details 1", answers: [
# {date: "12/02/20", medium: "Type A1, Type B1", value: "Answer 1"},
# {date: "12/03/20", medium: "Type A2, Type B2", value: "Answer 3"}
# ]},
# "Question 2" => {topic: "Question 2", details: "Details 2", answers: [
# {date: "12/02/20", medium: "Type A1, Type B1", value: "Answer 2"},
# {date: "12/04/20", medium: "Type A3, Type B3", value: "Answer 5"}
# ]},
# "Question 3" => {topic: "Question 3", details: "Details 3", answers: [
# {date: "12/03/20", medium: "Type A2, Type B2", value: "No Answer Provided"},
# {date: "12/04/20", medium: "Type A3, Type B3", value: "No Answer Provided"}
# ]}
# }
def court_topics
topics = ContactTopic
.joins(contact_topic_answers: {case_contact: [:casa_case, :contact_types]}).distinct
.where("casa_cases.id": @casa_case.id)
.where("case_contacts.occurred_at": @date_range)
.order(:occurred_at, :value)
.select(:details, :question, :occurred_at, :value, :contact_made,
"STRING_AGG(contact_types.name, ', ' ORDER BY contact_types.name) AS medium_types")
.group(:details, :question, :occurred_at, :value, :contact_made)

topics.each_with_object({}) do |topic, hash|
hash[topic.question] ||= {
answers: [],
topic: topic.question,
details: topic.details
}

formatted_date = CourtReportFormatContactDate.new(topic).format_long
answer_value = topic.value.blank? ? "No Answer Provided" : topic.value
answer = {
date: formatted_date,
medium: topic.medium_types,
value: answer_value
}

hash[topic.question][:answers].push(answer)
end
end

private

def calculate_date_range(args)
Expand Down
4 changes: 4 additions & 0 deletions app/services/court_report_format_contact_date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ def initialize(case_contact)
def format
I18n.l(@case_contact.occurred_at, format: :short_date, default: nil).concat(@case_contact.contact_made ? "" : CONTACT_UNSUCCESSFUL_PREFIX)
end

def format_long
I18n.l(@case_contact.occurred_at, format: :long_date, default: nil)
end
end
2 changes: 2 additions & 0 deletions app/views/case_contacts/form/details.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
</div>
</div>

<% if @case_contact.contact_topic_answers.any? %>
<div class="card-style-1 pl-25 mb-10">
<h4 class="mb-20 details__topics-label">Court report <%= "topic".pluralize(@case_contact.contact_topic_answers.count) %> <span class="content-1">(optional)</span></label></h4>
<div class="">
Expand All @@ -90,6 +91,7 @@
<% end %>
</div>
</div>
<% end %>
<div class="actions mb-10 d-flex justify-content-between">
<%= link_to leave_case_contacts_form_path, class: "btn-sm main-btn #{@case_contact.draft_case_ids.empty? ? 'danger' : 'primary'}-btn-outline btn-hover", data: { controller: "alert", "action": "alert#confirm", "alert-ignore-value": !@case_contact.draft_case_ids.empty?, "alert-title-value": "Discard draft?", "alert-message-value": "Are you sure? If you don't save and continue to the next step, this draft will not be recoverable." } do %>
Back
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ en:
full: "%B %-d, %Y"
youth_date_of_birth: "%B %Y"
short_date: "%-m/%d"
long_date: "%m/%d/%y"
edit_profile: "%B %d, %Y at %I:%M %p %Z"
time_on_date: "%-I:%-M %p on %m-%e-%Y"
date:
Expand Down
29 changes: 29 additions & 0 deletions lib/tasks/deployment/20240420230126_update_org_templates.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace :after_party do
desc "Deployment task: Updates_production_casa_orgs_with_new_templates"
task update_org_templates: :environment do
puts "Running deploy task 'update_org_templates'"

mapping = {
"Howard County CASA" => "howard_county_report_template.docx",
"Voices for Children Montgomery" => "montgomery_report_template.docx",
"Prince George CASA" => "prince_george_report_template.docx"
}

mapping.each do |casa_org_name, template_file_name|
casa_org = CasaOrg.find_by(name: casa_org_name)
if casa_org
casa_org.court_report_template.attach(
io: File.new(Rails.root.join("app", "documents", "templates", template_file_name)),
filename: template_file_name
)
else
Bugsnag.notify("No #{casa_org_name} found for rake task update_org_templates")
end
end

# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
end
80 changes: 79 additions & 1 deletion spec/models/case_court_report_context_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
allow(context).to receive(:org_address).and_return(nil)
allow(context).to receive(:volunteer_info).and_return({})
allow(context).to receive(:latest_hearing_date).and_return("")
allow(context).to receive(:court_topics).and_return({})

expected_shape = {
created_date: "January 1, 2021",
Expand All @@ -36,7 +37,8 @@
latest_hearing_date: "",
org_address: nil,
volunteer: {},
hearing_type_name: court_date.hearing_type.name
hearing_type_name: court_date.hearing_type.name,
case_topics: []
}

expect(context.context).to eq(expected_shape)
Expand Down Expand Up @@ -176,6 +178,82 @@
end
end

describe "#court_topics" do
let(:org) { create(:casa_org) }
let(:casa_case) { create(:casa_case, casa_org: org) }
let(:topics) { [1, 2, 3].map { |i| create(:contact_topic, casa_org: org, question: "Question #{i}", details: "Details #{i}") } }
let(:contacts) do
[1, 2, 3, 4].map do |i|
create(:case_contact,
casa_case: casa_case,
occurred_at: 1.month.ago + i.days,
contact_types: [
create(:contact_type, name: "Type A#{i}"),
create(:contact_type, name: "Type B#{i}")
])
end
end
# let(:contacts) { create_list(:case_contact, 4, casa_case: casa_case, occurred_at: 1.month.ago) }

context "when given data" do
# Add some values that should get filtered out
before do
contact_one = create(:case_contact, casa_case: casa_case, medium_type: "in-person", occurred_at: 1.day.ago)
create_list(:contact_topic_answer, 2, case_contact: contact_one, contact_topic: topics[0], value: "Not included")

contact_two = create(:case_contact, casa_case: casa_case, medium_type: "in-person", occurred_at: 50.day.ago)
create_list(:contact_topic_answer, 2, case_contact: contact_two, contact_topic: topics[0], value: "Not included")

other_case = create(:casa_case, casa_org: org)
contact_three = create(:case_contact, casa_case: other_case, medium_type: "in-person", occurred_at: 50.day.ago)
create_list(:contact_topic_answer, 2, case_contact: contact_three, contact_topic: topics[0], value: "Not included")
end

it "generates correctly shaped data" do
# Contact 1 Answers
create(:contact_topic_answer, case_contact: contacts[0], contact_topic: topics[0], value: "Answer 1")
create(:contact_topic_answer, case_contact: contacts[0], contact_topic: topics[1], value: "Answer 2")

# Contact 2 Answers
create(:contact_topic_answer, case_contact: contacts[1], contact_topic: topics[0], value: "Answer 3")
create(:contact_topic_answer, case_contact: contacts[1], contact_topic: topics[2], value: nil)

# Contact 3 Answers
create(:contact_topic_answer, case_contact: contacts[2], contact_topic: topics[1], value: "Answer 5")
create(:contact_topic_answer, case_contact: contacts[2], contact_topic: topics[2], value: "")

# Contact 4 Answers
# No Answers

expected_topics = {
"Question 1" => {topic: "Question 1", details: "Details 1", answers: [
{date: "12/02/20", medium: "Type A1, Type B1", value: "Answer 1"},
{date: "12/03/20", medium: "Type A2, Type B2", value: "Answer 3"}
]},
"Question 2" => {topic: "Question 2", details: "Details 2", answers: [
{date: "12/02/20", medium: "Type A1, Type B1", value: "Answer 2"},
{date: "12/04/20", medium: "Type A3, Type B3", value: "Answer 5"}
]},
"Question 3" => {topic: "Question 3", details: "Details 3", answers: [
{date: "12/03/20", medium: "Type A2, Type B2", value: "No Answer Provided"},
{date: "12/04/20", medium: "Type A3, Type B3", value: "No Answer Provided"}
]}
}

court_report_context = build(:case_court_report_context, start_date: 45.day.ago.to_s, end_date: 5.day.ago.to_s, casa_case: casa_case)

expect(court_report_context.court_topics).to eq(expected_topics)
end
end

context "when there are no contact topics" do
it "returns an empty hash" do
court_report_context = build(:case_court_report_context, start_date: 45.day.ago.to_s, end_date: 5.day.ago.to_s, casa_case: casa_case)
expect(court_report_context.court_topics).to eq({})
end
end
end

describe "#filtered_interviewees" do
it "filters based on date range" do
casa_case = create(:casa_case)
Expand Down
113 changes: 113 additions & 0 deletions spec/models/case_court_report_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,114 @@
let(:path_to_report) { Rails.root.join("tmp", "test_report.docx").to_s }

context "#generate_to_string" do
let(:full_context) do
{
created_date: "April 9, 2024",
casa_case: {
court_date: "April 23, 2024",
case_number: "A-CASA-CASE-NUMBER-12345",
dob: "April 2012",
is_transitioning: false,
judge_name: "Judge Judy"
},
case_contacts: [
{name: "Some Name", type: "Type 1", dates: "4/09*", dates_by_medium_type: {"in-person" => "4/09*"}},
{name: "Some Other Name", type: "Type 4", dates: "4/09*", dates_by_medium_type: {"in-person" => "4/09*"}}
],
case_court_orders: [
{order: "case_court_order_text", status: "Partially implemented"}
],
case_mandates: [
{order: "case_mandates_text", status: "Partially implemented"}
],
latest_hearing_date: "___<LATEST HEARING DATE>____",
org_address: "596 Unique Avenue Seattle, Washington",
volunteer: {
name: "name_of_volunteer",
supervisor_name: "name_of_supervisor",
assignment_date: "February 9, 2024"
},
hearing_type_name: "None",
case_topics: [
{topic: "Question 1", details: "Details 1", answers: [
{date: "12/01/20", medium: "Type A1, Type B1", value: "Answer 1"},
{date: "12/02/20", medium: "Type A2, Type B2", value: "Answer 3"}
]},
{topic: "Question 2", details: "Details 2", answers: [
{date: "12/01/20", medium: "Type A1, Type B1", value: "Answer 2"},
{date: "12/02/20", medium: "Type A3, Type B3", value: "Answer 5"}
]},
{topic: "Question 3", details: "Details 3", answers: [
{date: "12/01/20", medium: "Type A3, Type B3", value: "No Answer Provided"},
{date: "12/02/20", medium: "Type A2, Type B2", value: "No Answer Provided"}
]}
]
}
end
describe "contact_topics" do
it "all contact topics are present in the report" do
docx_response = generate_doc(full_context, path_to_template)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 1.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 2.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 3.*/)
end

it "all topic details are present in the report" do
docx_response = generate_doc(full_context, path_to_template)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 1.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 2.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 3.*/)
end

it "all answers are present with correct format" do
docx_response = generate_doc(full_context, path_to_template)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A1, Type B1 \(12\/01\/20\): Answer 1.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A2, Type B2 \(12\/02\/20\): Answer 3.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A1, Type B1 \(12\/01\/20\): Answer 2.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A3, Type B3 \(12\/02\/20\): Answer 5.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A3, Type B3 \(12\/01\/20\): No Answer Provided.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A2, Type B2 \(12\/02\/20\): No Answer Provided.*/)
end

context "when there are topics but no answers" do
let(:curr_context) do
full_context[:case_topics] = [
{topic: "Question 1", details: "Details 1", answers: []},
{topic: "Question 2", details: "Details 2", answers: []},
{topic: "Question 3", details: "Details 3", answers: []}
]
end

it "all contact topics are present in the report" do
docx_response = generate_doc(full_context, path_to_template)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 1.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 2.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Question 3.*/)
end
it "all topic details are present in the report" do
docx_response = generate_doc(full_context, path_to_template)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 1.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 2.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Details 3.*/)
end
end

context "when there no topics" do
it "report does not error and puts old defaults" do
full_context[:case_topics] = []
docx_response = nil
expect {
docx_response = generate_doc(full_context, path_to_template)
}.not_to raise_error

expect(docx_response).not_to be_nil
expect(docx_response.paragraphs.map(&:to_s)).to include(/Placement.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Education\/Vocation.*/)
expect(docx_response.paragraphs.map(&:to_s)).to include(/Objective Information.*/)
end
end
end

describe "when receiving valid case, volunteer, and path_to_template" do
let(:volunteer) { create(:volunteer, :with_cases_and_contacts, :with_assigned_supervisor) }
let(:casa_case_with_contacts) { volunteer.casa_cases.first }
Expand Down Expand Up @@ -367,3 +475,8 @@
end
end
end

def generate_doc(context, path_to_template)
report = CaseCourtReport.new(path_to_template: path_to_template, context: context)
Docx::Document.open(StringIO.new(report.generate_to_string))
end
9 changes: 9 additions & 0 deletions spec/requests/case_contacts/form_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@
end
end

context "when an org has no topics" do
let(:organization) { create(:casa_org) }
let!(:case_contact) { create(:case_contact, :details_status, casa_case: casa_case) }

it "does not show contact topic card" do
page = request.parsed_body.to_html
expect(page).to_not include("Court report topics")
end
end
context "when the org has topics assigned" do
let(:contact_topics) {
[
Expand Down

0 comments on commit 6377f2b

Please sign in to comment.