From ff10d0f652fdb78cf35e7964cbb5618b256a7f8e Mon Sep 17 00:00:00 2001 From: jenny-heath <43800769+jenny-heath@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:44:13 -0800 Subject: [PATCH] FYST-1827 Show errors for 1099-R on income review (#5657) Co-authored-by: Arin Choi --- .../questions/income_review_controller.rb | 6 +- .../questions/retirement_income_controller.rb | 24 ++++--- app/models/state_file1099_r.rb | 39 ++++++----- .../questions/income_review/edit.html.erb | 5 ++ config/locales/en.yml | 5 +- config/locales/es.yml | 5 +- .../income_review_controller_spec.rb | 62 ++++++++++++++---- .../retirement_income_controller_spec.rb | 33 +++++++++- .../test/miranda_1099r_with_df_w2_error.xml | 4 +- spec/models/state_file1099_r_spec.rb | 65 +++++++++++-------- 10 files changed, 173 insertions(+), 75 deletions(-) diff --git a/app/controllers/state_file/questions/income_review_controller.rb b/app/controllers/state_file/questions/income_review_controller.rb index a5817c607b..dcd37395e2 100644 --- a/app/controllers/state_file/questions/income_review_controller.rb +++ b/app/controllers/state_file/questions/income_review_controller.rb @@ -38,10 +38,14 @@ def update private def invalid_income_form?(intake) - intake.allows_w2_editing? && @w2s.any? do |w2| + has_invalid_w2 = intake.allows_w2_editing? && @w2s.any? do |w2| w2.check_box14_limits = true !w2.valid?(:state_file_income_review) end + has_invalid_1099r = intake.allows_1099_r_editing? && intake.state_file1099_rs.any? do |form1099r| + !form1099r.valid?(:income_review) + end + has_invalid_w2 || has_invalid_1099r end def should_show_warning?(w2, w2_count_for_filer) diff --git a/app/controllers/state_file/questions/retirement_income_controller.rb b/app/controllers/state_file/questions/retirement_income_controller.rb index dd5447dad2..1ca669f73e 100644 --- a/app/controllers/state_file/questions/retirement_income_controller.rb +++ b/app/controllers/state_file/questions/retirement_income_controller.rb @@ -2,21 +2,14 @@ module StateFile module Questions class RetirementIncomeController < QuestionsController before_action :allows_1099_r_editing, only: [:edit, :update] + before_action :load_1099_r + before_action :load_warnings, only: [:edit] def prev_path StateFile::Questions::IncomeReviewController.to_path_helper(return_to_review: params[:return_to_review]) end - def show - @state_file1099_r = current_intake.state_file1099_rs.find(params[:id]) - end - - def edit - @state_file1099_r = current_intake.state_file1099_rs.find(params[:id]) - end - def update - @state_file1099_r = current_intake.state_file1099_rs.find(params[:id]) @state_file1099_r.assign_attributes(state_file1099_r_params) if @state_file1099_r.valid?(:retirement_income_intake) @@ -29,6 +22,19 @@ def update private + def load_1099_r + @state_file1099_r = current_intake.state_file1099_rs.find(params[:id]) + end + + def load_warnings + if @state_file1099_r.state_tax_withheld_amount == 0 || @state_file1099_r.state_tax_withheld_amount.nil? + @state_file1099_r.errors.add(:state_tax_withheld_amount, I18n.t("state_file.questions.retirement_income.edit.state_tax_withheld_absent_warning")) + end + if @state_file1099_r.state_tax_withheld_amount.present? && @state_file1099_r.state_tax_withheld_amount > @state_file1099_r.gross_distribution_amount + @state_file1099_r.errors.add(:state_tax_withheld_amount, I18n.t("activerecord.errors.models.state_file1099_r.errors.must_be_less_than_gross_distribution", gross_distribution_amount: @state_file1099_r.gross_distribution_amount)) + end + end + def state_file1099_r_params params.require(:state_file1099_r).permit( :state_tax_withheld_amount, diff --git a/app/models/state_file1099_r.rb b/app/models/state_file1099_r.rb index 142a4604a5..741c998b3d 100644 --- a/app/models/state_file1099_r.rb +++ b/app/models/state_file1099_r.rb @@ -53,32 +53,31 @@ class StateFile1099R < ApplicationRecord # Not adding validations for fields we just copy over from the DF XML, since we have no recourse if they fail with_options on: :retirement_income_intake do - validate :less_than_gross_distribution, if: -> { gross_distribution_amount.present? } validates :gross_distribution_amount, numericality: { greater_than: 0 } - validates :state_tax_withheld_amount, - numericality: { - greater_than_or_equal_to: 0, - }, - presence: { - message: proc { I18n.t('forms.errors.no_money_amount') } - } - validates :state_distribution_amount, - numericality: { - greater_than_or_equal_to: 0, - }, - presence: { - message: proc { I18n.t('forms.errors.no_money_amount') } - } + numericality: { + greater_than_or_equal_to: 0, + }, + presence: { + message: proc { I18n.t('forms.errors.no_money_amount') } + } validates :payer_state_identification_number, presence: true, length: { maximum: 16 }, if: -> { state_tax_withheld_amount&.positive? } end + with_options on: [:income_review, :retirement_income_intake] do + validate :less_than_gross_distribution, if: -> { gross_distribution_amount.present? } + validates :state_tax_withheld_amount, + numericality: { + greater_than_or_equal_to: 0, + }, + presence: { + message: proc { I18n.t('forms.errors.no_money_amount') } + } + end + def less_than_gross_distribution - [:state_tax_withheld_amount, :state_distribution_amount].each do |attr_name| - value = send(attr_name) - if value.present? && value > gross_distribution_amount - errors.add(attr_name, I18n.t("activerecord.errors.models.state_file_1099_r.errors.must_be_less_than_gross_distribution")) - end + if state_tax_withheld_amount.present? && state_tax_withheld_amount > gross_distribution_amount + errors.add(:state_tax_withheld_amount, I18n.t("activerecord.errors.models.state_file1099_r.errors.must_be_less_than_gross_distribution", gross_distribution_amount: gross_distribution_amount)) end end end diff --git a/app/views/state_file/questions/income_review/edit.html.erb b/app/views/state_file/questions/income_review/edit.html.erb index ab7b0a997e..d04cbc76d4 100644 --- a/app/views/state_file/questions/income_review/edit.html.erb +++ b/app/views/state_file/questions/income_review/edit.html.erb @@ -83,6 +83,11 @@ ), class: "button--small" %> + <% if !state_1099r.valid?(:income_review) %> +
+

<%= t(".warning") %>

+
+ <% end %> <% else %> <%= link_to t(".review_state_info"), StateFile::Questions::RetirementIncomeController.to_path_helper( diff --git a/config/locales/en.yml b/config/locales/en.yml index f8b59ae1b0..200790c9de 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -19,9 +19,9 @@ en: invalid: should be a five digit number beginning with 2 date_of_contribution: inclusion: Date must be valid and in the current tax year - state_file_1099_r: + state_file1099_r: errors: - must_be_less_than_gross_distribution: Must be less than gross distribution amount + must_be_less_than_gross_distribution: Box 14 State tax withheld cannot be greater than %{gross_distribution_amount} attributes: confirm_primary_ssn: Confirm SSN or ITIN confirm_spouse_ssn: Confirm spouse's SSN or ITIN @@ -3987,6 +3987,7 @@ en: box14_html: "Box 14, State tax withheld" box15_html: "Box 15, State/Payer’s State Number" box16_html: "Box 16, State distribution" + state_tax_withheld_absent_warning: Please confirm that the amount in Box 14 on your 1099-R is 0 title_html: Please review your retirement income (1099-R) details for %{payer_name}. retirement_income_subtraction: doc_1099r_label: 1099-R diff --git a/config/locales/es.yml b/config/locales/es.yml index 06e5d4fba9..1a3f8b00cb 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -19,9 +19,9 @@ es: invalid: Debe ser un número de cinco dígitos que comience con 2 date_of_contribution: inclusion: La fecha debe ser válida y corresponder al año fiscal actual. - state_file_1099_r: + state_file1099_r: errors: - must_be_less_than_gross_distribution: Debe ser menor que el monto bruto de distribución + must_be_less_than_gross_distribution: Box 14 State tax withheld cannot be greater than %{gross_distribution_amount} attributes: confirm_primary_ssn: Confirme el Número de Seguro Social (SSN) o Número de Identificación Personal (ITIN) confirm_spouse_ssn: Confirme el Número de Seguro Social (SSN) o Número de Identificación Personal (ITIN) de su cónyuge @@ -3973,6 +3973,7 @@ es: box14_html: "Casilla 14, Impuesto estatal retenido" box15_html: "Casilla 15, Número estatal del pagador" box16_html: "Casilla 16, Distribución estatal" + state_tax_withheld_absent_warning: Please confirm that the amount in Box 14 on your 1099-R is 0 title_html: Por favor, revisa los detalles de los ingresos estatales (1099-R) que recibiste de %{payer_name}. retirement_income_subtraction: doc_1099r_label: 1099-R diff --git a/spec/controllers/state_file/questions/income_review_controller_spec.rb b/spec/controllers/state_file/questions/income_review_controller_spec.rb index bc80996a7c..50485bd3c6 100644 --- a/spec/controllers/state_file/questions/income_review_controller_spec.rb +++ b/spec/controllers/state_file/questions/income_review_controller_spec.rb @@ -57,7 +57,7 @@ let(:form_params) { params } end - context "W-2 validity" do + context "income form validity" do let(:mock_next_path) { root_path } before do allow(subject).to receive(:next_path).and_return mock_next_path @@ -115,6 +115,13 @@ include_examples "proceeds as if there are no errors" end + + context "with 1099rs having invalid Box 14 values" do + let(:intake) { create(:state_file_md_intake) } + let!(:state_file1099_r) { create(:state_file1099_r, intake: intake, gross_distribution_amount: 500, state_tax_withheld_amount: 550) } + + include_examples "shows error and does not proceed" + end end end @@ -367,19 +374,50 @@ describe "1099R card" do render_views - context "when there are state 1099Rs" do - it "shows a summary of each" do - primary_1099r = create(:state_file1099_r, intake: intake, payer_name: "Payeur", recipient_name: 'Prim Rose') - spouse_1099r = create(:state_file1099_r, intake: intake, payer_name: "Payure", recipient_name: 'Sprout Vine') + shared_examples "does not display 1099R warning" do + it "has no warnings" do + get :edit, params: params + expect(response.body).not_to have_text I18n.t("state_file.questions.income_review.edit.warning") + end + end + + shared_examples "displays one 1099R warning" do + it "displays warning in 1099R card" do get :edit, params: params + expect(response.body.scan(I18n.t("state_file.questions.income_review.edit.warning")).size).to eq(1) + end + end + + context "when there are state 1099Rs" do + context "when 1099Rs are valid" do + let!(:primary_1099r) { create(:state_file1099_r, intake: intake, payer_name: "Payeur", recipient_name: "Prim Rose") } + let!(:spouse_1099r) { create(:state_file1099_r, intake: intake, payer_name: "Payure", recipient_name: "Sprout Vine") } + + it_behaves_like "does not display 1099R warning" + + it "shows a summary of each" do + get :edit, params: params + + expect(response.body).to have_text "Retirement income (1099-R)" + expect(response.body).to have_text "Payeur" + expect(response.body).to have_text "Prim Rose" + expect(response.body).to have_link(href: edit_retirement_income_path(id: primary_1099r.id)) + expect(response.body).to have_text "Payure" + expect(response.body).to have_text "Sprout Vine" + expect(response.body).to have_link(href: edit_retirement_income_path(id: spouse_1099r.id)) + end + end + + context "when a 1099R is blocking-invalid" do + let!(:invalid_1099r) { create(:state_file1099_r, intake: intake, gross_distribution_amount: 500, state_tax_withheld_amount: 550) } + + it_behaves_like "displays one 1099R warning" + end + + context "when a 1099R is nonblocking-invalid" do + let!(:invalid_1099r) { create(:state_file1099_r, intake: intake, payer_state_identification_number: "123456789asdfghjklqwertyuiop") } - expect(response.body).to have_text "Retirement income (1099-R)" - expect(response.body).to have_text "Payeur" - expect(response.body).to have_text "Prim Rose" - expect(response.body).to have_link(href: edit_retirement_income_path(id: primary_1099r.id)) - expect(response.body).to have_text "Payure" - expect(response.body).to have_text "Sprout Vine" - expect(response.body).to have_link(href: edit_retirement_income_path(id: spouse_1099r.id)) + it_behaves_like "does not display 1099R warning" end end diff --git a/spec/controllers/state_file/questions/retirement_income_controller_spec.rb b/spec/controllers/state_file/questions/retirement_income_controller_spec.rb index b992dee3f5..5bcf08299a 100644 --- a/spec/controllers/state_file/questions/retirement_income_controller_spec.rb +++ b/spec/controllers/state_file/questions/retirement_income_controller_spec.rb @@ -32,7 +32,6 @@ end end - describe "#edit" do let(:client) { intake.client } let!(:form1099r) do @@ -58,6 +57,38 @@ expect(response.body).to include("30") expect(response.body).to include("40") end + + context "when there are box 14 warnings" do + context "state tax withheld more than gross distribution amount" do + let!(:form1099r) { create :state_file1099_r, state_tax_withheld_amount: 40, gross_distribution_amount: 30, intake: intake } + + it "displays the errors on edit" do + get :edit, params: params + + expect(response.body).to include I18n.t("activerecord.errors.models.state_file1099_r.errors.must_be_less_than_gross_distribution", gross_distribution_amount: 30) + end + end + + context "state tax withheld amount nil" do + let!(:form1099r) { create :state_file1099_r, state_tax_withheld_amount: nil, intake: intake } + + it "displays the errors on edit" do + get :edit, params: params + + expect(response.body).to include I18n.t("state_file.questions.retirement_income.edit.state_tax_withheld_absent_warning") + end + end + + context "state tax withheld amount is 0" do + let!(:form1099r) { create :state_file1099_r, state_tax_withheld_amount: 0, intake: intake } + + it "displays the errors on edit" do + get :edit, params: params + + expect(response.body).to include I18n.t("state_file.questions.retirement_income.edit.state_tax_withheld_absent_warning") + end + end + end end context "when the intake's 1099R is not editable" do diff --git a/spec/fixtures/state_file/fed_return_xmls/test/miranda_1099r_with_df_w2_error.xml b/spec/fixtures/state_file/fed_return_xmls/test/miranda_1099r_with_df_w2_error.xml index 2b4dad7059..eb8150d579 100644 --- a/spec/fixtures/state_file/fed_return_xmls/test/miranda_1099r_with_df_w2_error.xml +++ b/spec/fixtures/state_file/fed_return_xmls/test/miranda_1099r_with_df_w2_error.xml @@ -111,9 +111,9 @@ ID - 123456789asdfghjklqwertyuiop + 123456789 2000 - 10 + 4001 2000 How Town diff --git a/spec/models/state_file1099_r_spec.rb b/spec/models/state_file1099_r_spec.rb index a1c32a7f3d..a07213eb16 100644 --- a/spec/models/state_file1099_r_spec.rb +++ b/spec/models/state_file1099_r_spec.rb @@ -61,43 +61,57 @@ .with_message(I18n.t('forms.errors.no_money_amount')) end - context "retirement_income_intake" do + context "both contexts" do let!(:state_file1099_r) { create(:state_file1099_r, intake: create(:state_file_nc_intake)) } - let(:context) { :retirement_income_intake } - context "state_tax_withheld_amount and state_distribution_amount must be less than gross_distribution_amount" do - let(:state_file1099_r) { - create :state_file1099_r, - intake: create(:state_file_nc_intake), - gross_distribution_amount: gross_distribution_amount, - state_tax_withheld_amount: state_tax_withheld_amount, - state_distribution_amount: state_distribution_amount - } + [:retirement_income_intake, :income_review].each do |context_name| + context "state_tax_withheld_amount must be less than gross_distribution_amount" do + let(:state_file1099_r) { + create :state_file1099_r, + intake: create(:state_file_nc_intake), + gross_distribution_amount: gross_distribution_amount, + state_tax_withheld_amount: state_tax_withheld_amount + } - context "when the gross_distribution_amount is present" do - let(:gross_distribution_amount) { 100 } + context "when the gross_distribution_amount is present" do + let(:gross_distribution_amount) { 100 } - context "other values are less" do - let(:state_tax_withheld_amount) { 50 } - let(:state_distribution_amount) { 50 } + context "state tax withheld is less" do + let(:state_tax_withheld_amount) { 50 } - it "is valid" do - expect(state_file1099_r).to be_valid(context) + it "is valid" do + expect(state_file1099_r).to be_valid(context_name) + end end - end - context "other values are greater" do - let(:state_tax_withheld_amount) { 200 } - let(:state_distribution_amount) { 200 } + context "state tax withheld is greater" do + let(:state_tax_withheld_amount) { 200 } - it "is invalid" do - expect(state_file1099_r).not_to be_valid(context) - expect(state_file1099_r.errors[:state_tax_withheld_amount]).to be_present - expect(state_file1099_r.errors[:state_distribution_amount]).to be_present + it "is invalid" do + expect(state_file1099_r).not_to be_valid(context_name) + expect(state_file1099_r.errors[:state_tax_withheld_amount]).to be_present + end end end end + + it "validates state_tax_withheld_amount is number if present" do + ['string', -1].each do |val| + state_file1099_r.state_tax_withheld_amount = val + expect(state_file1099_r.valid?(context_name)).to eq false + end + + [0, 1].each do |val| + state_file1099_r.state_tax_withheld_amount = val + expect(state_file1099_r.valid?(context_name)).to eq true + end + end end + end + + context "retirement_income_intake" do + let!(:state_file1099_r) { create(:state_file1099_r, intake: create(:state_file_nc_intake)) } + let(:context) { :retirement_income_intake } it "validates gross_distribution_amount is present and a positive number" do state_file1099_r.state_tax_withheld_amount = 0 @@ -128,7 +142,6 @@ expect(state_file1099_r.valid?(context)).to eq true end end - end context "payer_state_identification_number" do