diff --git a/app/controllers/casa_org_controller.rb b/app/controllers/casa_org_controller.rb index 523268a469..fd3cd9fd59 100644 --- a/app/controllers/casa_org_controller.rb +++ b/app/controllers/casa_org_controller.rb @@ -91,7 +91,7 @@ def set_learning_hour_topics end def set_contact_topics - @contact_topics = @casa_org.contact_topics.where(soft_delete: false) + @contact_topics = @casa_org.contact_topics.where(soft_delete: false).order(:question) end def set_custom_org_links diff --git a/app/controllers/case_court_reports_controller.rb b/app/controllers/case_court_reports_controller.rb index a63d01f5a9..6bedbf6fff 100644 --- a/app/controllers/case_court_reports_controller.rb +++ b/app/controllers/case_court_reports_controller.rb @@ -65,7 +65,7 @@ def generate private def date_range_params - params.permit(:time_zone, case_court_report: %i[start_date end_date]) + params.permit(:time_zone, case_court_report: %i[start_date end_date include_empty_topics]) end def case_params @@ -94,7 +94,8 @@ def generate_report_to_string(casa_case, time_range) path_to_template: template_docx_file.to_path, time_zone: time_range[:time_zone], start_date: time_range[:case_court_report][:start_date], - end_date: time_range[:case_court_report][:end_date] + end_date: time_range[:case_court_report][:end_date], + include_empty_topics: time_range[:case_court_report][:include_empty_topics] } context = CaseCourtReportContext.new(args).context court_report = CaseCourtReport.new(path_to_template: template_docx_file.to_path, context: context) diff --git a/app/models/case_court_report_context.rb b/app/models/case_court_report_context.rb index a0fce80c76..29db173a77 100644 --- a/app/models/case_court_report_context.rb +++ b/app/models/case_court_report_context.rb @@ -12,6 +12,7 @@ def initialize(args = {}) @path_to_template = args[:path_to_template] @court_date = args[:court_date] || @casa_case.next_court_date @case_court_orders = args[:case_court_orders] || @casa_case.case_court_orders + @include_empty_topics = ActiveModel::Type::Boolean.new.cast(args[:include_empty_topics]) @date_range = calculate_date_range(args) end @@ -59,9 +60,11 @@ def case_orders(orders) def filtered_interviewees CaseContactContactType - .joins(:contact_type, case_contact: :casa_case) + .joins(contact_type: :contact_type_group, case_contact: :casa_case) + .includes(:case_contact, contact_type: :contact_type_group) .where("case_contacts.casa_case_id": @casa_case.id) .where("case_contacts.occurred_at": @date_range) + .order("contact_type_groups.name ASC", "contact_types.name ASC", "case_contact_contact_types.id ASC") end def case_details @@ -92,15 +95,15 @@ def org_address(path_to_template) # Sample output # # expected_topics = { - # "Question 1" => {topic: "Question 1", details: "Details 1", answers: [ + # "Question 1" => {topic: "Question 1", details: "", 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: [ + # "Question 2" => {topic: "Question 2", details: "", 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: [ + # "Question 3" => {topic: "Question 3", details: "", 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"} # ]} @@ -112,7 +115,7 @@ def court_topics hash[topic.question] = { answers: answers_by_topic_id.fetch(topic.id, []), topic: topic.question, - details: topic.details + details: "" } end end @@ -120,10 +123,13 @@ def court_topics private def report_topics(answered_topic_ids) - ContactTopic + topics = ContactTopic .where(casa_org: @casa_case.casa_org, exclude_from_court_report: false) - .merge(ContactTopic.active.or(ContactTopic.where(id: answered_topic_ids))) - .order(:id) + .order(:question) + + return topics.where(id: answered_topic_ids) unless @include_empty_topics + + topics.merge(ContactTopic.active.or(ContactTopic.where(id: answered_topic_ids))) end def court_topic_answers diff --git a/app/services/case_contacts_contact_dates.rb b/app/services/case_contacts_contact_dates.rb index 372fc68110..35a23238dd 100644 --- a/app/services/case_contacts_contact_dates.rb +++ b/app/services/case_contacts_contact_dates.rb @@ -4,13 +4,17 @@ def initialize(case_contact_contact_types) end def contact_dates_details - contact_type_names = @case_contact_contact_types.map(&:contact_type).map(&:name).uniq # .sort # TODO sort after refactor - contact_type_names.map do |contact_type_name| - case_contacts = case_contacts_for_type(contact_type_name) + contact_types = @case_contact_contact_types + .map(&:contact_type) + .uniq + .sort_by { |contact_type| [contact_type.contact_type_group.name, contact_type.name] } + + contact_types.map do |contact_type| + case_contacts = case_contacts_for_type(contact_type) { name: "Names of persons involved, starting with the child's name", - type: contact_type_name, + type: contact_type.name, dates: order_and_format(case_contacts), dates_by_medium_type: case_contacts.group_by(&:medium_type).transform_values { |vals| order_and_format(vals) } } @@ -19,9 +23,9 @@ def contact_dates_details private - def case_contacts_for_type(contact_type_name) + def case_contacts_for_type(contact_type) @case_contact_contact_types - .select { |ccct| ccct.contact_type.name == contact_type_name } + .select { |ccct| ccct.contact_type_id == contact_type.id } .map(&:case_contact) end diff --git a/app/views/casa_cases/_court_report_modal.html.erb b/app/views/casa_cases/_court_report_modal.html.erb index 0c003a1839..8c459e19b4 100644 --- a/app/views/casa_cases/_court_report_modal.html.erb +++ b/app/views/casa_cases/_court_report_modal.html.erb @@ -32,6 +32,10 @@ class: "block w-full rounded-lg border border-slate-300 px-3.5 py-2.5 text-sm text-slate-900 shadow-sm focus:border-brand-500 focus:ring-2 focus:ring-brand-500/30 focus:outline-none" %> + <% end %> <%= render Dialog::FooterComponent.new do %> diff --git a/app/views/case_court_reports/_generate_docx.html.erb b/app/views/case_court_reports/_generate_docx.html.erb index d6477a5359..8fe366cef7 100644 --- a/app/views/case_court_reports/_generate_docx.html.erb +++ b/app/views/case_court_reports/_generate_docx.html.erb @@ -57,6 +57,10 @@ data: {court_report_target: "endDate"}, class: input_class %> + <% end %> <%= render Dialog::FooterComponent.new do %> diff --git a/spec/factories/case_court_report_context.rb b/spec/factories/case_court_report_context.rb index 2d064cbda2..0c7db84d78 100644 --- a/spec/factories/case_court_report_context.rb +++ b/spec/factories/case_court_report_context.rb @@ -12,6 +12,7 @@ start_date { nil } end_date { nil } time_zone { nil } + include_empty_topics { false } end initialize_with { @@ -31,7 +32,8 @@ case_court_orders: case_court_orders, start_date: start_date, end_date: end_date, - time_zone: time_zone + time_zone: time_zone, + include_empty_topics: include_empty_topics ) } end diff --git a/spec/models/case_court_report_context_spec.rb b/spec/models/case_court_report_context_spec.rb index ab2e3615ac..6bed463c47 100644 --- a/spec/models/case_court_report_context_spec.rb +++ b/spec/models/case_court_report_context_spec.rb @@ -229,7 +229,7 @@ expect(court_topics.values).to all( a_hash_including( topic: a_kind_of(String), - details: a_kind_of(String), + details: "", answers: all( a_hash_including( date: a_string_matching(/\d{2}\/\d{2}\/\d{2}/), @@ -270,13 +270,21 @@ end context "when some topics have no answers" do - it "includes every topic, with an empty answer list for unanswered ones" do + it "omits unanswered topics by default" do create(:contact_topic_answer, case_contact: contacts[0], contact_topic: topics[0], value: "Answer 1") court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics - expect(court_topics.keys).to eq(["Question 1", "Question 2", "Question 3"]) + expect(court_topics.keys).to eq(["Question 1"]) expect(court_topics["Question 1"][:answers].pluck(:value)).to eq(["Answer 1"]) + end + + it "includes unanswered topics when requested" do + create(:contact_topic_answer, case_contact: contacts[0], contact_topic: topics[0], value: "Answer 1") + + court_topics = build(:case_court_report_context, casa_case: casa_case, include_empty_topics: true).court_topics + + expect(court_topics.keys).to eq(["Question 1", "Question 2", "Question 3"]) expect(court_topics["Question 2"][:answers]).to eq([]) expect(court_topics["Question 3"][:answers]).to eq([]) end @@ -284,7 +292,7 @@ it "does not include unanswered topics that are inactive" do topics[1].update!(active: false) - court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics + court_topics = build(:case_court_report_context, casa_case: casa_case, include_empty_topics: true).court_topics expect(court_topics.keys).to eq(["Question 1", "Question 3"]) end @@ -292,7 +300,7 @@ it "does not include unanswered topics excluded from the court report" do topics[1].update!(exclude_from_court_report: true) - court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics + court_topics = build(:case_court_report_context, casa_case: casa_case, include_empty_topics: true).court_topics expect(court_topics.keys).to eq(["Question 1", "Question 3"]) end @@ -301,21 +309,24 @@ topics create(:contact_topic, question: "Other Org Question") - court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics + court_topics = build(:case_court_report_context, casa_case: casa_case, include_empty_topics: true).court_topics expect(court_topics.keys).to eq(["Question 1", "Question 2", "Question 3"]) end end context "when answers occur in a different order than the topics were created" do - it "orders topics by creation order, not by answer date" do + it "orders topics alphabetically" do + topics[0].update!(question: "Zulu") + topics[1].update!(question: "Alpha") + topics[2].update!(question: "Middle") create(:contact_topic_answer, case_contact: contacts[0], contact_topic: topics[2], value: "Earliest answer") create(:contact_topic_answer, case_contact: contacts[1], contact_topic: topics[1], value: "Middle answer") create(:contact_topic_answer, case_contact: contacts[2], contact_topic: topics[0], value: "Latest answer") court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics - expect(court_topics.keys).to eq(["Question 1", "Question 2", "Question 3"]) + expect(court_topics.keys).to eq(["Alpha", "Middle", "Zulu"]) end end @@ -326,7 +337,7 @@ court_topics = build(:case_court_report_context, casa_case: casa_case).court_topics - expect(court_topics.keys).to eq(["Question 1", "Question 2", "Question 3"]) + expect(court_topics.keys).to eq(["Question 2"]) expect(court_topics["Question 2"][:answers].pluck(:value)).to eq(["Answer before deactivation"]) end end @@ -400,6 +411,20 @@ expect(result).to be_empty end + + it "orders interviewees by contact type group and name" do + casa_case = create(:casa_case) + family_group = create(:contact_type_group, casa_org: casa_case.casa_org, name: "Family") + health_group = create(:contact_type_group, casa_org: casa_case.casa_org, name: "Health") + therapist = create(:contact_type, contact_type_group: health_group, name: "Therapist") + aunt = create(:contact_type, contact_type_group: family_group, name: "Aunt") + counselor = create(:contact_type, contact_type_group: health_group, name: "Counselor") + create(:case_contact, casa_case: casa_case, contact_types: [therapist, aunt, counselor]) + + result = build(:case_court_report_context, casa_case: casa_case).filtered_interviewees + + expect(result.map { |interviewee| interviewee.contact_type.name }).to eq(["Aunt", "Counselor", "Therapist"]) + end end describe "#context" do diff --git a/spec/models/case_court_report_spec.rb b/spec/models/case_court_report_spec.rb index 202211d853..7d934ffe77 100644 --- a/spec/models/case_court_report_spec.rb +++ b/spec/models/case_court_report_spec.rb @@ -104,6 +104,48 @@ expect(docx_response.paragraphs.map(&:to_s)).to include(/Type A2, Type B2 \(12\/02\/20\): No Answer Provided.*/) end + it "omits guidance and unanswered topics from real report templates" do + casa_case = create(:casa_case) + answered_topic = create( + :contact_topic, + casa_org: casa_case.casa_org, + question: "Included topic heading", + details: "Included topic guidance" + ) + create( + :contact_topic, + casa_org: casa_case.casa_org, + question: "Omitted topic heading", + details: "Omitted topic guidance" + ) + case_contact = create(:case_contact, casa_case: casa_case) + create( + :contact_topic_answer, + case_contact: case_contact, + contact_topic: answered_topic, + value: "Included topic answer" + ) + template_paths = %w[ + default_report_template.docx + montgomery_report_template.docx + prince_george_report_template.docx + ].map { |filename| Rails.root.join("app/documents/templates", filename).to_s } + + template_paths.each do |template_path| + topics = CaseCourtReportContext.new(case_id: casa_case.id, path_to_template: template_path).court_topics.values + docx_response = generate_doc(full_context.merge(case_topics: topics), template_path) + document_text = (docx_response.paragraphs.map(&:to_s) + table_text(docx_response)).join(" ") + + aggregate_failures(File.basename(template_path)) do + expect(document_text).to include("Included topic heading") + expect(document_text).to include("Included topic answer") + expect(document_text).not_to include("Included topic guidance") + expect(document_text).not_to include("Omitted topic heading") + expect(document_text).not_to include("Omitted topic guidance") + end + end + end + context "when there are topics but no answers" do let(:curr_context) do full_context[:case_topics] = [ diff --git a/spec/requests/casa_org_spec.rb b/spec/requests/casa_org_spec.rb index 50500e19e7..a397bdc908 100644 --- a/spec/requests/casa_org_spec.rb +++ b/spec/requests/casa_org_spec.rb @@ -17,6 +17,15 @@ end it { is_expected.to be_successful } + + it "orders contact topics alphabetically" do + create(:contact_topic, casa_org: casa_org, question: "Zulu") + create(:contact_topic, casa_org: casa_org, question: "Alpha") + + request + + expect(assigns(:contact_topics).pluck(:question)).to eq(["Alpha", "Zulu"]) + end end describe "PATCH /update" do diff --git a/spec/requests/case_court_reports_spec.rb b/spec/requests/case_court_reports_spec.rb index e3fcdc6c44..9ac2ee7273 100644 --- a/spec/requests/case_court_reports_spec.rb +++ b/spec/requests/case_court_reports_spec.rb @@ -120,6 +120,19 @@ request end + it "passes the empty-topic option to the report context" do + params[:case_court_report][:include_empty_topics] = "1" + context_builder = instance_double(CaseCourtReportContext, context: {}) + report = instance_double(CaseCourtReport, generate_to_string: "report") + allow(CaseCourtReport).to receive(:new).and_return(report) + allow_any_instance_of(CaseCourtReportsController).to receive(:save_report) + expect(CaseCourtReportContext).to receive(:new) + .with(hash_including(include_empty_topics: "1")) + .and_return(context_builder) + + expect(request).to have_http_status(:ok) + end + context "when no custom template is set" do it "sends response as a JSON string", :aggregate_failures do expect(request.content_type).to eq("application/json; charset=utf-8") diff --git a/spec/services/case_contacts_contact_dates_spec.rb b/spec/services/case_contacts_contact_dates_spec.rb index 2b970667dc..ba29e7e0b4 100644 --- a/spec/services/case_contacts_contact_dates_spec.rb +++ b/spec/services/case_contacts_contact_dates_spec.rb @@ -17,9 +17,12 @@ end context "with interviewees" do - let(:contact_type_1) { create(:contact_type, name: "Mental therapist") } - let(:contact_type_2) { create(:contact_type, name: "Physical therapist") } - let(:contact_type_3) { create(:contact_type, name: "Aunt") } + let(:casa_org) { create(:casa_org) } + let(:family_group) { create(:contact_type_group, casa_org: casa_org, name: "Family") } + let(:health_group) { create(:contact_type_group, casa_org: casa_org, name: "Health") } + let(:contact_type_1) { create(:contact_type, contact_type_group: health_group, name: "Mental therapist") } + let(:contact_type_2) { create(:contact_type, contact_type_group: health_group, name: "Physical therapist") } + let(:contact_type_3) { create(:contact_type, contact_type_group: family_group, name: "Aunt") } let(:ccct_1) { create(:case_contact_contact_type, contact_type: contact_type_1) } let(:ccct_2) { create(:case_contact_contact_type, contact_type: contact_type_2) } @@ -41,6 +44,10 @@ it "returns formatted data" do expect(subject).to eq([ + {dates: "4/01*", + dates_by_medium_type: {"in-person" => "4/01*"}, + name: "Names of persons involved, starting with the child's name", + type: "Aunt"}, {dates: "6/01*", dates_by_medium_type: {"in-person" => "6/01*"}, name: "Names of persons involved, starting with the child's name", @@ -48,11 +55,7 @@ {dates: "4/01*, 5/01*, 6/01*", dates_by_medium_type: {"in-person" => "5/01*, 6/01*", "text/email" => "4/01*"}, name: "Names of persons involved, starting with the child's name", - type: "Physical therapist"}, - {dates: "4/01*", - dates_by_medium_type: {"in-person" => "4/01*"}, - name: "Names of persons involved, starting with the child's name", - type: "Aunt"} + type: "Physical therapist"} ]) end end diff --git a/spec/system/casa_cases/show_spec.rb b/spec/system/casa_cases/show_spec.rb index 8d8a50fb9d..7927aafddb 100644 --- a/spec/system/casa_cases/show_spec.rb +++ b/spec/system/casa_cases/show_spec.rb @@ -61,6 +61,7 @@ within("#generate-court-report") do expect(page).to have_content(casa_case.case_number) + expect(page).to have_unchecked_field("Include sections with no entries") # No past hearing, so the window starts the day the case was opened in CASA -- not "today", # which would be an empty window. (The case is created before this example freezes the clock.) expect(page.find("#start_date").value).to eq(casa_case.created_at.to_date.to_s) diff --git a/spec/system/case_court_reports/index_spec.rb b/spec/system/case_court_reports/index_spec.rb index e24e6bb086..40fe34a4cc 100644 --- a/spec/system/case_court_reports/index_spec.rb +++ b/spec/system/case_court_reports/index_spec.rb @@ -62,6 +62,7 @@ it "shows the Generate button and a searchable picker", :aggregate_failures do expect(page).to have_selector "#btnGenerateReport", text: "Generate report", visible: :visible expect(page).to have_css "#generate-docx-report-modal .ts-wrapper" + expect(page).to have_unchecked_field("Include sections with no entries") end it "shows both dates empty until a case is chosen", :aggregate_failures do