diff --git a/app/controllers/admin/cx_collection_details_controller.rb b/app/controllers/admin/cx_collection_details_controller.rb index 7ab54b0fe..f2d29e550 100644 --- a/app/controllers/admin/cx_collection_details_controller.rb +++ b/app/controllers/admin/cx_collection_details_controller.rb @@ -18,6 +18,18 @@ def show def new @cx_collection_detail = CxCollectionDetail.new @cx_collection_detail.cx_collection_id = params[:collection_id] + + if params[:form_id] + @form = Form.find_by_short_uuid(params[:form_id]) + @cx_collection_detail.form = @form + @cx_collection_detail.service_stage_id = @form.service_stage_id + @cx_collection_detail.transaction_point = :post_interaction + @cx_collection_detail.survey_type = :thumbs_up_down if @form.kind == "a11_v2" + @cx_collection_detail.survey_title = @form.title + @cx_collection_detail.omb_control_number = @form.omb_approval_number + @cx_collection_detail.trust_question_text = @form.questions.first.text + @cx_collection_detail.volume_of_customers_provided_survey_opportunity = @form.survey_form_activations + end end def edit @@ -35,6 +47,11 @@ def create respond_to do |format| if @cx_collection_detail.save Event.log_event(Event.names[:cx_collection_detail_created], @cx_collection_detail.class.to_s, @cx_collection_detail.id, "CX Collection Detail #{@cx_collection_detail.id} created at #{DateTime.now}", current_user.id) + + if @cx_collection_detail.form + CxCollectionDetailUpload.create!(user: current_user, cx_collection_detail: @cx_collection_detail) + end + format.html { redirect_to upload_admin_cx_collection_detail_url(@cx_collection_detail), notice: "CX Collection Detail was successfully created." } format.json { render :upload, status: :created, location: @cx_collection_detail } else @@ -160,6 +177,22 @@ def set_cx_collections end def cx_collection_detail_params - params.require(:cx_collection_detail).permit(:cx_collection_id, :transaction_point, :channel, :service_stage_id, :volume_of_customers, :volume_of_customers_provided_survey_opportunity, :volume_of_respondents, :omb_control_number, :federal_register_url, :reflection_text, :survey_type, :survey_title, :trust_question_text) + params.require(:cx_collection_detail) + .permit( + :cx_collection_id, + :transaction_point, + :channel, + :service_stage_id, + :volume_of_customers, + :volume_of_customers_provided_survey_opportunity, + :volume_of_respondents, + :omb_control_number, + :federal_register_url, + :reflection_text, + :survey_type, + :survey_title, + :trust_question_text, + :form_id, + ) end end diff --git a/app/controllers/admin/cx_collections_controller.rb b/app/controllers/admin/cx_collections_controller.rb index 2c104ad1c..22f41a9c4 100644 --- a/app/controllers/admin/cx_collections_controller.rb +++ b/app/controllers/admin/cx_collections_controller.rb @@ -26,6 +26,8 @@ def show def new @cx_collection = CxCollection.new + @cx_collection.quarter = FiscalYear.fiscal_year_and_quarter(Date.today)[:quarter] + @cx_collection.fiscal_year = FiscalYear.fiscal_year_and_quarter(Date.today)[:year] end def edit diff --git a/app/models/cx_collection_detail.rb b/app/models/cx_collection_detail.rb index 06470351f..9e5541203 100644 --- a/app/models/cx_collection_detail.rb +++ b/app/models/cx_collection_detail.rb @@ -3,6 +3,7 @@ class CxCollectionDetail < ApplicationRecord belongs_to :cx_collection belongs_to :service_stage, optional: true + belongs_to :form, optional: true has_many :cx_responses, dependent: :delete_all has_many :cx_collection_detail_uploads has_one :service_provider, through: :cx_collection diff --git a/app/models/cx_collection_detail_upload.rb b/app/models/cx_collection_detail_upload.rb index db825fb80..5436f2a7a 100644 --- a/app/models/cx_collection_detail_upload.rb +++ b/app/models/cx_collection_detail_upload.rb @@ -6,7 +6,7 @@ class CxCollectionDetailUpload < ApplicationRecord belongs_to :cx_collection_detail has_many :cx_responses, dependent: :delete_all - after_create :process_csv_in_a_worker + after_create :process_records_in_a_worker aasm do state :created, initial: true @@ -24,8 +24,15 @@ class CxCollectionDetailUpload < ApplicationRecord end end - def process_csv_in_a_worker - process_csv + def process_records_in_a_worker + if self.key? + process_csv + elsif self.cx_collection_detail.form + fiscal_quarter_dates = FiscalYear.fiscal_quarter_dates(self.cx_collection_detail.cx_collection.fiscal_year, self.cx_collection_detail.cx_collection.quarter) + start_date = fiscal_quarter_dates[:start_date] + end_date = fiscal_quarter_dates[:end_date] + upload_form_results(form_id: self.cx_collection_detail.form_id, start_date:, end_date:) + end end def process_csv @@ -73,4 +80,39 @@ def process_csv end end + def upload_form_results(form_id:, start_date:, end_date:) + @form = Form.find(form_id) + + job_id = SecureRandom.hex[0..9] + update_attribute(:job_id, job_id) + + responses = @form.to_a11_v2_array(start_date:, end_date:) + responses.each do |response| + # Create the CxResponse record + CxResponse.create!({ + cx_collection_detail_id: cx_collection_detail.id, + cx_collection_detail_upload_id: self.id, + job_id: job_id, + external_id: response[0], + question_1: response[:answer_01], + positive_effectiveness: response[:answer_02_effectiveness], + positive_ease: response[:answer_02_ease], + positive_efficiency: response[:answer_02_efficiency], + positive_transparency: response[:answer_02_transparency], + positive_humanity: response[:answer_02_humanity], + positive_employee: response[:answer_02_employee], + positive_other: response[:answer_02_other], + negative_effectiveness: response[:answer_03_effectiveness], + negative_ease: response[:answer_03_ease], + negative_efficiency: response[:answer_03_efficiency], + negative_transparency: response[:answer_03_transparency], + negative_humanity: response[:answer_03_humanity], + negative_employee: response[:answer_03_employee], + negative_other: response[:answer_03_other], + question_4: response[:answer_04], + }) + end + + end + end diff --git a/app/models/form.rb b/app/models/form.rb index e509630de..709881915 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -448,6 +448,39 @@ def to_a11_v2_csv(start_date: nil, end_date: nil) end end + def to_a11_v2_array(start_date: nil, end_date: nil) + non_flagged_submissions = submissions + .non_flagged + .where(created_at: start_date..end_date) + .order('created_at') + return nil if non_flagged_submissions.blank? + + answer_02_options = self.questions.where(answer_field: "answer_02").first.question_options.collect(&:value) + answer_03_options = self.questions.where(answer_field: "answer_03").first.question_options.collect(&:value) + + non_flagged_submissions.map do |submission| + { + id: submission.id, + answer_01: submission.answer_01, + answer_02_effectiveness: submission.answer_02 && submission.answer_02.split(",").include?("effectiveness") ? 1 :(answer_02_options.include?("effectiveness") ? 0 : 'null'), + answer_02_ease: submission.answer_02 && submission.answer_02.split(",").include?("ease") ? 1 : (answer_02_options.include?("ease") ? 0 : 'null'), + answer_02_efficiency: submission.answer_02 && submission.answer_02.split(",").include?("efficiency") ? 1 : (answer_02_options.include?("efficiency") ? 0 : 'null'), + answer_02_transparency: submission.answer_02 && submission.answer_02.split(",").include?("transparency") ? 1 : (answer_02_options.include?("transparency") ? 0 : 'null'), + answer_02_humanity: submission.answer_02 && submission.answer_02.split(",").include?("humanity") ? 1 : (answer_02_options.include?("humanity") ? 0 : 'null'), + answer_02_employee: submission.answer_02 && submission.answer_02.split(",").include?("employee") ? 1 : (answer_02_options.include?("employee") ? 0 : 'null'), + answer_02_other: submission.answer_02 && submission.answer_02.split(",").include?("other") ? 1 : (answer_02_options.include?("other") ? 0 : 'null'), + answer_03_effectiveness: submission.answer_03 && submission.answer_03.split(",").include?("effectiveness") ? 1 : (answer_03_options.include?("effectiveness") ? 0 : 'null'), + answer_03_ease: submission.answer_03 && submission.answer_03.split(",").include?("ease") ? 1 : (answer_03_options.include?("ease") ? 0 : 'null'), + answer_03_efficiency: submission.answer_03 && submission.answer_03.split(",").include?("efficiency") ? 1 : (answer_03_options.include?("efficiency") ? 0 : 'null'), + answer_03_transparency: submission.answer_03 && submission.answer_03.split(",").include?("transparency") ? 1 : (answer_03_options.include?("transparency") ? 0 : 'null'), + answer_03_humanity: submission.answer_03 && submission.answer_03.split(",").include?("humanity") ? 1 : (answer_03_options.include?("humanity") ? 0 : 'null'), + answer_03_employee: submission.answer_03 && submission.answer_03.split(",").include?("employee") ? 1 : (answer_03_options.include?("employee") ? 0 : 'null'), + answer_03_other: submission.answer_03 && submission.answer_03.split(",").include?("other") ? 1 : (answer_03_options.include?("other") ? 0 : 'null'), + answer_04: submission.answer_04, + } + end + end + def user_role?(user:) role = user_roles.find_by_user_id(user.id) role.present? ? role.role : nil diff --git a/app/views/admin/cx_collection_details/_form.html.erb b/app/views/admin/cx_collection_details/_form.html.erb index 7cd41dff1..792742210 100644 --- a/app/views/admin/cx_collection_details/_form.html.erb +++ b/app/views/admin/cx_collection_details/_form.html.erb @@ -132,6 +132,17 @@
+ <% if @cx_collection_detail.form %> + <%= form.hidden_field :form_id, value: @cx_collection_detail.form_id %> +
+
+

+ CxResponses will be created for the form titled "<%= @cx_collection_detail.form.title %>" + for Q<%= @cx_collection_detail.cx_collection.quarter %>FY<%= @cx_collection_detail.cx_collection.fiscal_year %>. +

+
+
+ <% else %>

@@ -140,6 +151,7 @@

+ <% end %>

<%= form.submit class: "usa-button" %> diff --git a/app/views/admin/cx_collection_details/upload.html.erb b/app/views/admin/cx_collection_details/upload.html.erb index 30eb620ae..f3c0defba 100644 --- a/app/views/admin/cx_collection_details/upload.html.erb +++ b/app/views/admin/cx_collection_details/upload.html.erb @@ -61,7 +61,7 @@ <% end %> - +
@@ -70,9 +70,9 @@ <%- if service_manager_permissions? %> - - - + + + <% end %> @@ -87,7 +87,7 @@ <%= upload.user.email %>
UserTimestamp Uploaded record countJob IDProcess file?Delete?
- <%= link_to "Uploaded file", s3_presigned_url(upload.key) %> + <%= link_to "Uploaded file", s3_presigned_url(upload.key) if upload.key %> <%= upload.size %> diff --git a/app/views/admin/cx_collections/_form.html.erb b/app/views/admin/cx_collections/_form.html.erb index 7491e10a9..ab045f5b8 100644 --- a/app/views/admin/cx_collections/_form.html.erb +++ b/app/views/admin/cx_collections/_form.html.erb @@ -74,7 +74,7 @@

- After creating this collection, you can add survey results in the following screen. + After creating this collection, you can add survey results on the following screen.

diff --git a/app/views/admin/cx_collections/show.html.erb b/app/views/admin/cx_collections/show.html.erb index 8ae93a66e..953a8deb8 100644 --- a/app/views/admin/cx_collections/show.html.erb +++ b/app/views/admin/cx_collections/show.html.erb @@ -144,12 +144,23 @@
<%- if @cx_collection.draft? %> -

+

<%= link_to new_admin_cx_collection_detail_path(collection_id: @cx_collection.id), class: "usa-button usa-button--outline" do %> Add detail record <% end %> -

+ +
+ <% Form.where(kind: "a11_v2").where(service_id: @cx_collection.service_id).each do |form| %> + <%= link_to new_admin_cx_collection_detail_path(collection_id: @cx_collection.id, form_id: form.to_param), class: "usa-button usa-button--outline" do %> + + Add detail record from A-11 v2 form + <% end %> + <%= form.name %> - + <%= form.title %> + <% end %> +
+
<% end %> diff --git a/app/views/admin/forms/_form_manager_options.html.erb b/app/views/admin/forms/_form_manager_options.html.erb index fc04762ec..f7c49ee9d 100644 --- a/app/views/admin/forms/_form_manager_options.html.erb +++ b/app/views/admin/forms/_form_manager_options.html.erb @@ -92,7 +92,7 @@
YYYY-MM-DD
- <%= f.text_field :expiration_date, class: "usa-input" %> + <%= f.date_field :expiration_date, class: "usa-input" %>
diff --git a/app/views/admin/services/show.html.erb b/app/views/admin/services/show.html.erb index 2a2756f51..e0a0267bb 100644 --- a/app/views/admin/services/show.html.erb +++ b/app/views/admin/services/show.html.erb @@ -284,7 +284,7 @@ CX Data Collections (V2) <%- if @cx_collections.present? %> - +
diff --git a/db/migrate/20250206004746_add_form_id_to_cx_collection_details.rb b/db/migrate/20250206004746_add_form_id_to_cx_collection_details.rb new file mode 100644 index 000000000..371146ed1 --- /dev/null +++ b/db/migrate/20250206004746_add_form_id_to_cx_collection_details.rb @@ -0,0 +1,5 @@ +class AddFormIdToCxCollectionDetails < ActiveRecord::Migration[7.2] + def change + add_column :cx_collection_details, :form_id, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 1ca396e5a..d3be86d25 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -97,6 +97,7 @@ t.text "trust_question_text" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "form_id" end create_table "cx_collections", force: :cascade do |t| diff --git a/lib/fiscal_year.rb b/lib/fiscal_year.rb index 25bb8f6d5..732f7e762 100644 --- a/lib/fiscal_year.rb +++ b/lib/fiscal_year.rb @@ -1,4 +1,21 @@ module FiscalYear + def self.fiscal_quarter_dates(fiscal_year, fiscal_quarter) + fiscal_year = fiscal_year.to_i + start_date, end_date = case fiscal_quarter.to_i + when 1 then [Date.new(fiscal_year - 1, 10, 1), Date.new(fiscal_year - 1, 12, 31)] # Q1: Oct - Dec of the prior calendar year relativ to the fiscal year + when 2 then [Date.new(fiscal_year, 1, 1), Date.new(fiscal_year, 3, 31)] # Q2: Jan - Mar + when 3 then [Date.new(fiscal_year, 4, 1), Date.new(fiscal_year, 6, 30)] # Q3: Apr - Jun + when 4 then [Date.new(fiscal_year, 7, 1), Date.new(fiscal_year, 9, 30)] # Q4: Jul - Sep + else + raise ArgumentError, "Invalid quarter: #{fiscal_quarter}. Must be 1, 2, 3, or 4." + end + + { + start_date: start_date.beginning_of_day, + end_date: end_date.end_of_day + } + end + def self.first_day_of_fiscal_quarter(date) # Adjust the month to align with the fiscal year starting in October fiscal_month = (date.month - 10) % 12 + 1 diff --git a/spec/features/admin/cx_collections_spec.rb b/spec/features/admin/cx_collections_spec.rb index 816ee583b..e59e0f119 100644 --- a/spec/features/admin/cx_collections_spec.rb +++ b/spec/features/admin/cx_collections_spec.rb @@ -98,7 +98,7 @@ let(:current_year) { Time.zone.now.strftime('%Y') } before do - expect(page).to have_content('After creating this collection, you can add survey results in the following screen.') + expect(page).to have_content('After creating this collection, you can add survey results on the following screen.') fill_in("cx_collection_service_provider_id", with: service_provider.name) find("#cx_collection_service_provider_id--list").click fill_in("cx_collection_service_id", with: service.name) diff --git a/spec/features/admin/forms_spec.rb b/spec/features/admin/forms_spec.rb index 592debe8a..68e89c75e 100644 --- a/spec/features/admin/forms_spec.rb +++ b/spec/features/admin/forms_spec.rb @@ -740,7 +740,7 @@ describe 'editing form PRA info' do before do fill_in 'form_omb_approval_number', with: 'OAN-1234' - fill_in 'form_expiration_date', with: '2022-01-30' + fill_in 'form_expiration_date', with: '01/30/2022' click_on 'Update Form Options' expect(page).to have_content('Form Manager forms options updated successfully') end diff --git a/spec/lib/fiscal_year_spec.rb b/spec/lib/fiscal_year_spec.rb index d4a189bf1..737cd623c 100644 --- a/spec/lib/fiscal_year_spec.rb +++ b/spec/lib/fiscal_year_spec.rb @@ -3,6 +3,34 @@ describe FiscalYear do include ActiveSupport::Testing::TimeHelpers + describe '#fiscal_quarter_dates' do + context 'when the current date is before October 1st' do + it 'returns the start and end dates for Q2 2025' do + start_and_end_dates = FiscalYear.fiscal_quarter_dates(2025, 2) + expect(start_and_end_dates[:start_date]).to eq(Date.parse("2025-01-01").beginning_of_day) + expect(start_and_end_dates[:end_date]).to eq(Date.parse("2025-03-31").end_of_day) + end + + it 'returns the start and end dates for Q3 2026' do + start_and_end_dates = FiscalYear.fiscal_quarter_dates(2026, 3) + expect(start_and_end_dates[:start_date]).to eq(Date.parse("2026-04-01").beginning_of_day) + expect(start_and_end_dates[:end_date]).to eq(Date.parse("2026-06-30").end_of_day) + end + + it 'returns the start and end dates for Q4 2027' do + start_and_end_dates = FiscalYear.fiscal_quarter_dates(2027, 4) + expect(start_and_end_dates[:start_date]).to eq(Date.parse("2027-07-01").beginning_of_day) + expect(start_and_end_dates[:end_date]).to eq(Date.parse("2027-09-30").end_of_day) + end + + it 'returns the start and end dates for Q1 2028' do + start_and_end_dates = FiscalYear.fiscal_quarter_dates(2028, 1) + expect(start_and_end_dates[:start_date]).to eq(Date.parse("2027-10-01").beginning_of_day) + expect(start_and_end_dates[:end_date]).to eq(Date.parse("2027-12-31").end_of_day) + end + end + end + describe '#fiscal_year_and_quarter' do context 'when the current date is before October 1st' do it 'returns the correct fiscal year and quarter for First Day of the Fiscal Year' do