From 5921515d21fe3a2fffe862314fb401d283a42734 Mon Sep 17 00:00:00 2001 From: alexwitedja Date: Mon, 19 May 2025 05:38:05 +0000 Subject: [PATCH 1/2] annual report across years, implementation and unit tests --- .../reports/annual_reports_controller.rb | 9 +++ .../export_yearly_report_csv_service.rb | 39 ++++++++++ app/services/reports.rb | 29 +++++++ .../reports/annual_reports/index.html.erb | 8 ++ .../export_yearly_report_csv_service_spec.rb | 78 +++++++++++++++++++ 5 files changed, 163 insertions(+) create mode 100644 app/services/exports/export_yearly_report_csv_service.rb create mode 100644 spec/services/exports/export_yearly_report_csv_service_spec.rb diff --git a/app/controllers/reports/annual_reports_controller.rb b/app/controllers/reports/annual_reports_controller.rb index 76bc4aff23..d6473b3c79 100644 --- a/app/controllers/reports/annual_reports_controller.rb +++ b/app/controllers/reports/annual_reports_controller.rb @@ -11,6 +11,15 @@ def index @years = (foundation_year...@actual_year).to_a @month_remaining_to_report = 12 - Time.current.month + + respond_to do |format| + format.html + format.csv do + yearly_reports = Reports.reports_across_the_year(organization: current_organization) + send_data Exports::ExportYearlyReportCSVService.new(yearly_reports:).generate_csv, + filename: "NdbnYearlyReports-#{Time.zone.today}.csv" + end + end end def show diff --git a/app/services/exports/export_yearly_report_csv_service.rb b/app/services/exports/export_yearly_report_csv_service.rb new file mode 100644 index 0000000000..0c98e0d33a --- /dev/null +++ b/app/services/exports/export_yearly_report_csv_service.rb @@ -0,0 +1,39 @@ +# Exports yearly reports to csv +# expects a report with a report method returns a hash with keys corresponding to columns and values corresponding to the reported value +# It should also have a columns_for_csv method to determine the order of the columns +module Exports + class ExportYearlyReportCSVService + def initialize(yearly_reports:) + @yearly_reports = yearly_reports + end + + def generate_csv + csv_data = generate_csv_data + + ::CSV.generate(headers: true) do |csv| + csv_data.each { |row| csv << row } + end + end + + def generate_csv_data + csv_data = [] + + @yearly_reports.each do |yearly_report| + headers = [] + data = [] + yearly_report.each do |report| + headers.concat(report["entries"].keys) + data.concat(report["entries"].values) + end + + if csv_data.empty? + csv_data << headers + end + + csv_data << data + end + + csv_data + end + end +end diff --git a/app/services/reports.rb b/app/services/reports.rb index 6af011bfba..bfe6a692cd 100644 --- a/app/services/reports.rb +++ b/app/services/reports.rb @@ -29,5 +29,34 @@ def retrieve_report(year:, organization:, recalculate: false) end report end + + # @param organization [Organization] + # @return [Array] + def reports_across_the_year(organization:) + foundation_year = organization.earliest_reporting_year + last_completed_year = 1.year.ago.year + years = (foundation_year..last_completed_year).to_a + reports_across_the_year = [] + years.each do |year| + report = retrieve_report(year: year, organization: organization) + unless report.all_reports.blank? + reports_across_the_year << report.all_reports.unshift(year_hash(year)) + end + end + reports_across_the_year + end + + private + + # @param year [Integer] + # @return [Hash] + def year_hash(year) + { + "name" => "Report Year", + "entries" => { + "Year" => year.to_s + } + } + end end end diff --git a/app/views/reports/annual_reports/index.html.erb b/app/views/reports/annual_reports/index.html.erb index fbfffdfdb3..04c5d603f3 100644 --- a/app/views/reports/annual_reports/index.html.erb +++ b/app/views/reports/annual_reports/index.html.erb @@ -27,6 +27,14 @@
Reports are available at the end of every year.
<%= "#{@actual_year} (available in #{pluralize(@month_remaining_to_report, 'month')})" %> +
+ <%= + download_button_to( + reports_annual_reports_path(format: :csv), + text: "Export Yearly Reports" + ) + %> +
diff --git a/spec/services/exports/export_yearly_report_csv_service_spec.rb b/spec/services/exports/export_yearly_report_csv_service_spec.rb new file mode 100644 index 0000000000..19acb26062 --- /dev/null +++ b/spec/services/exports/export_yearly_report_csv_service_spec.rb @@ -0,0 +1,78 @@ +RSpec.describe Exports::ExportYearlyReportCSVService do + describe ".generate_csv_data" do + it "creates CSV data including headers" do + yearly_reports = [ + [ + { + "entries" => { + "Year" => 2023 + } + }, + { + "entries" => { + "First" => 1, + "Second" => 2 + } + } + ], + [ + { + "entries" => { + "Year" => 2024 + } + }, + { + "entries" => { + "First" => 5, + "Second" => 10 + } + } + ] + ] + + result = described_class.new(yearly_reports:).generate_csv_data + + expect(result.first).to eq(%w[Year First Second]) + expect(result.last).to eq([2024, 5, 10]) + end + + it "creates a CSV string" do + yearly_reports = [ + [ + { + "entries" => { + "Year" => 2023 + } + }, + { + "entries" => { + "First" => 1, + "Second" => 2 + } + } + ], + [ + { + "entries" => { + "Year" => 2024 + } + }, + { + "entries" => { + "First" => 5, + "Second" => 10 + } + } + ] + ] + + result = described_class.new(yearly_reports:).generate_csv + expected_result = <<~CSV + Year,First,Second + 2023,1,2 + 2024,5,10 + CSV + expect(result).to eq(expected_result) + end + end +end From 5b1fa5c52075ac0e2e8c254a0811ec096046d4a0 Mon Sep 17 00:00:00 2001 From: alexwitedja Date: Mon, 19 May 2025 05:57:22 +0000 Subject: [PATCH 2/2] add margin to button --- app/views/reports/annual_reports/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/reports/annual_reports/index.html.erb b/app/views/reports/annual_reports/index.html.erb index 04c5d603f3..5cd8648287 100644 --- a/app/views/reports/annual_reports/index.html.erb +++ b/app/views/reports/annual_reports/index.html.erb @@ -27,7 +27,7 @@
Reports are available at the end of every year.
<%= "#{@actual_year} (available in #{pluralize(@month_remaining_to_report, 'month')})" %>
-
+
<%= download_button_to( reports_annual_reports_path(format: :csv),