diff --git a/app/lib/efile/line_data.yml b/app/lib/efile/line_data.yml index f16be3766c..104ea8cc17 100644 --- a/app/lib/efile/line_data.yml +++ b/app/lib/efile/line_data.yml @@ -490,6 +490,12 @@ IT214_LINE_33: label: '33 Enter the amount from line 32 or 31, whichever is less. This is the credit for your household.' IT227_PART_2_LINE_1: label: '' +NCD400_LINE_10B: + label: '10B Child Deduction: On Line 10b, enter the amount of the child deduction.' +NCD400_LINE_11: + label: '11 N.C. Standard Deduction' +NCD400_LINE_12A: + label: '12A NCAGIAddition Add Lines 9, 10b, and 11.' NCD400_LINE_20A: label: '20a For each W2, if EmployeeSSN = PrimarySSN, add StateIncomeTaxAmt to sum' NCD400_LINE_20B: diff --git a/app/lib/efile/nc/d400_calculator.rb b/app/lib/efile/nc/d400_calculator.rb index bfd74a9573..1dfb21e950 100644 --- a/app/lib/efile/nc/d400_calculator.rb +++ b/app/lib/efile/nc/d400_calculator.rb @@ -4,6 +4,9 @@ class D400Calculator < ::Efile::TaxCalculator attr_reader :lines def calculate + set_line(:NCD400_LINE_10B, :calculate_line_10b) + set_line(:NCD400_LINE_11, :calculate_line_11) + set_line(:NCD400_LINE_12A, :calculate_line_12a) set_line(:NCD400_LINE_20A, :calculate_line_20a) set_line(:NCD400_LINE_20B, :calculate_line_20b) set_line(:NCD400_LINE_23, :calculate_line_23) @@ -16,6 +19,39 @@ def refund_or_owed_amount private + def calculate_line_10b + income_ranges = if filing_status_single? || filing_status_mfs? + [0..20_000, 20_001..30_000, 30_001..40_000, 40_001..50_000, 50_001..60_000, 60_001..70_000, 70_001..Float::INFINITY] + elsif filing_status_hoh? + [0..30_000, 30_001..45_000, 45_001..60_000, 60_001..75_000, 75_001..90_000, 90_001..105_000, 105_001..Float::INFINITY] + elsif filing_status_mfj? || filing_status_qw? + [0..40_000, 40_001..60_000, 60_001..80_000, 80_001..100_000, 100_001..120_000, 120_001..140_000, 140_001..Float::INFINITY] + end + income_range_index = income_ranges.find_index { |range| range.include?(@direct_file_data.fed_agi) } + + deduction_amounts = [3000, 2500, 2000, 1500, 1000, 500, 0] + amount_per_child = deduction_amounts[income_range_index] + + amount_per_child * @direct_file_data.qualifying_children_under_age_ssn_count.to_i + end + + STANDARD_DEDUCTIONS = { + head_of_household: 19125, + married_filing_jointly: 25500, + married_filing_separately: 12750, + qualifying_widow: 25500, + single: 12750, + }.freeze + def calculate_line_11 + STANDARD_DEDUCTIONS[@intake.filing_status] + end + + def calculate_line_12a + # Add Lines 9, 10b, and 11 + # line 9 DeductionsFromFAGI is blank + line_or_zero(:NCD400_LINE_10B) + line_or_zero(:NCD400_LINE_11) + end + def calculate_line_20a @direct_file_data.w2s.reduce(0) do |sum, w2| if w2.EmployeeSSN == @direct_file_data.primary_ssn diff --git a/app/lib/pdf_filler/nc_d400_pdf.rb b/app/lib/pdf_filler/nc_d400_pdf.rb index ff38e7fd14..e90ffb8f7e 100644 --- a/app/lib/pdf_filler/nc_d400_pdf.rb +++ b/app/lib/pdf_filler/nc_d400_pdf.rb @@ -55,6 +55,9 @@ def hash_for_pdf y_d400wf_li20b_pg2_good: @xml_document.at('IncTaxWithSpouse')&.text, y_d400wf_li23_pg2_good: @xml_document.at('NCTaxPaid')&.text, y_d400wf_li25_pg2_good: @xml_document.at('RemainingPayment')&.text, + y_d400wf_li10a_good: @xml_document.at('NumChildrenAllowed')&.text, + y_d400wf_li10b_good: @xml_document.at('ChildDeduction')&.text, + y_d400wf_li12a_pg1_good: @xml_document.at('NCAGIAddition')&.text, y_d400wf_dayphone: @xml_document.at('ReturnHeaderState Filer Primary USPhone')&.text } end diff --git a/app/lib/submission_builder/ty2024/states/nc/documents/d400.rb b/app/lib/submission_builder/ty2024/states/nc/documents/d400.rb index d2f0ed67f0..3ed9119b6d 100644 --- a/app/lib/submission_builder/ty2024/states/nc/documents/d400.rb +++ b/app/lib/submission_builder/ty2024/states/nc/documents/d400.rb @@ -14,14 +14,6 @@ class D400 < SubmissionBuilder::Document single: "Single", }.freeze - STANDARD_DEDUCTIONS = { - head_of_household: 19125, - married_filing_jointly: 25500, - married_filing_separately: 12750, - qualifying_widow: 25500, - single: 12750, - }.freeze - def document build_xml_doc("FormNCD400") do |xml| xml.ResidencyStatusPrimary true @@ -42,7 +34,10 @@ def document # line 7 AdditionsToFAGI is blank xml.FAGIPlusAdditions @submission.data_source.direct_file_data.fed_agi # line 9 DeductionsFromFAGI is blank - xml.NCStandardDeduction standard_deduction + xml.NumChildrenAllowed @submission.data_source.direct_file_data.qualifying_children_under_age_ssn_count + xml.ChildDeduction calculated_fields.fetch(:NCD400_LINE_10B) + xml.NCStandardDeduction calculated_fields.fetch(:NCD400_LINE_11) + xml.NCAGIAddition calculated_fields.fetch(:NCD400_LINE_12A) # line 16 TaxCredits is blank xml.IncTaxWith calculated_fields.fetch(:NCD400_LINE_20A) xml.IncTaxWithSpouse calculated_fields.fetch(:NCD400_LINE_20B) @@ -57,10 +52,6 @@ def filing_status FILING_STATUS_OPTIONS[@submission.data_source.filing_status] end - def standard_deduction - STANDARD_DEDUCTIONS[@submission.data_source.filing_status] - end - def calculated_fields @calculated_fields ||= @submission.data_source.tax_calculator.calculate end diff --git a/app/models/direct_file_data.rb b/app/models/direct_file_data.rb index 1c83653768..c94404d67c 100644 --- a/app/models/direct_file_data.rb +++ b/app/models/direct_file_data.rb @@ -66,6 +66,7 @@ class DirectFileData < DfXmlAccessor spouse_name: 'IRS1040 SpouseNm', non_resident_alien: 'IRS1040 NRALiteralCd', interest_reported_amount: 'IRS1040 InterestReported', # fake + qualifying_children_under_age_ssn_count: 'IRS1040Schedule8812 QlfyChildUnderAgeSSNCnt' }.freeze def initialize(raw_xml) @@ -609,6 +610,10 @@ def interest_reported_amount=(value) write_df_xml_value(__method__, value) end + def qualifying_children_under_age_ssn_count=(value) + write_df_xml_value(__method__, value) + end + def w2_nodes parsed_xml.css('IRSW2') end diff --git a/spec/factories/state_file_nc_intakes.rb b/spec/factories/state_file_nc_intakes.rb index faa751edbf..055f267492 100644 --- a/spec/factories/state_file_nc_intakes.rb +++ b/spec/factories/state_file_nc_intakes.rb @@ -113,7 +113,7 @@ end trait :head_of_household do - raw_direct_file_data { StateFile::XmlReturnSampleService.new.read('shiloh_mfs') } + raw_direct_file_data { StateFile::XmlReturnSampleService.new.read('shiloh_hoh') } end trait :married_filing_separately do diff --git a/spec/lib/efile/nc/d400_calculator_spec.rb b/spec/lib/efile/nc/d400_calculator_spec.rb index 7041a1661c..fddca3c847 100644 --- a/spec/lib/efile/nc/d400_calculator_spec.rb +++ b/spec/lib/efile/nc/d400_calculator_spec.rb @@ -9,6 +9,84 @@ ) end + describe "Line 10b: Child Deduction" do + [ + [["single", "married_filing_separately"], [ + [20_000, 6000], + [30_000, 5000], + [40_000, 4000], + [50_000, 3000], + [60_000, 2000], + [70_000, 1000], + [70_001, 0], + ]], + [["head_of_household"], [ + [30_000, 6000], + [45_000, 5000], + [60_000, 4000], + [75_000, 3000], + [90_000, 2000], + [105_000, 1000], + [105_001, 0] + ]], + [["married_filing_jointly", "qualifying_widow"], [ + [40_000, 6000], + [60_000, 5000], + [80_000, 4000], + [100_000, 3000], + [120_000, 2000], + [140_000, 1000], + [140_001, 0] + ]] + ].each do |filing_statuses, agis_to_deductions| + filing_statuses.each do |filing_status| + context "#{filing_status}" do + let(:intake) { create(:state_file_nc_intake, filing_status: filing_status, raw_direct_file_data: StateFile::XmlReturnSampleService.new.read("nc_shiloh_hoh")) } + let(:calculator_instance) { described_class.new(year: MultiTenantService.statefile.current_tax_year, intake: intake) } + + agis_to_deductions.each do |fagi, deduction_amount| + it "returns the value corresponding to #{fagi} FAGI multiplied by number of QCs" do + intake.direct_file_data.fed_agi = fagi + intake.direct_file_data.qualifying_children_under_age_ssn_count = 2 + + calculator_instance.calculate + expect(calculator_instance.lines[:NCD400_LINE_10B].value).to eq(deduction_amount) + end + end + end + end + end + end + + describe "Line 11: Standard Deduction" do + [ + ["head_of_household", 19125], + ["married_filing_jointly", 25500], + ["married_filing_separately", 12750], + ["qualifying_widow", 25500], + ["single", 12750], + ].each do |filing_status, deduction_amount| + context "#{filing_status} filer" do + let(:intake) { create :state_file_nc_intake, filing_status: filing_status } + + it "returns the correct value" do + instance.calculate + expect(instance.lines[:NCD400_LINE_11].value).to eq(deduction_amount) + end + end + end + end + + describe "Line 12a: NCAGIAddition" do + it "sums lines 10b and 11 (9 is blank)" do + allow(instance).to receive(:calculate_line_10b).and_return 10 + allow(instance).to receive(:calculate_line_11).and_return 10 + + instance.calculate + expect(instance.lines[:NCD400_LINE_12A].value).to eq 20 + end + end + describe "Line 20a: North Carolina Income Tax Withheld" do let(:intake) { create(:state_file_nc_intake, :df_data_2_w2s) } diff --git a/spec/lib/pdf_filler/nc_d400_pdf_spec.rb b/spec/lib/pdf_filler/nc_d400_pdf_spec.rb index 3f27e3344b..6f8709bc47 100644 --- a/spec/lib/pdf_filler/nc_d400_pdf_spec.rb +++ b/spec/lib/pdf_filler/nc_d400_pdf_spec.rb @@ -54,6 +54,23 @@ expect(pdf_fields['y_d400wf_li25_pg2_good']).to eq '15' expect(pdf_fields['y_d400wf_dayphone']).to eq '9845559876' end + + context "CTC fields" do + let(:intake) { create(:state_file_nc_intake, filing_status: "single", raw_direct_file_data: StateFile::XmlReturnSampleService.new.read("nc_shiloh_hoh")) } + let(:child_deduction) { 2000 } + let(:nc_agi_addition) { 8000 } + before do + intake.direct_file_data.qualifying_children_under_age_ssn_count = 5 + allow_any_instance_of(Efile::Nc::D400Calculator).to receive(:calculate_line_10b).and_return child_deduction + allow_any_instance_of(Efile::Nc::D400Calculator).to receive(:calculate_line_12a).and_return nc_agi_addition + end + + it "sets the correct values" do + expect(pdf_fields["y_d400wf_li10a_good"]).to eq "5" + expect(pdf_fields["y_d400wf_li10b_good"]).to eq child_deduction.to_s + expect(pdf_fields["y_d400wf_li12a_pg1_good"]).to eq nc_agi_addition.to_s + end + end end context "mfj filers" do diff --git a/spec/lib/submission_builder/ty2024/states/nc/documents/d400_spec.rb b/spec/lib/submission_builder/ty2024/states/nc/documents/d400_spec.rb index ae8e4a6c30..f5fccb6009 100644 --- a/spec/lib/submission_builder/ty2024/states/nc/documents/d400_spec.rb +++ b/spec/lib/submission_builder/ty2024/states/nc/documents/d400_spec.rb @@ -13,8 +13,12 @@ let(:income_tax_withheld) { 2000 } let(:income_tax_withheld_spouse) { 1000 } let(:tax_paid) { 3000 } + let(:standard_deduction) { 12750 } + let(:nc_agi_addition) { 18750 } before do intake.direct_file_data.fed_agi = 10000 + allow_any_instance_of(Efile::Nc::D400Calculator).to receive(:calculate_line_11).and_return standard_deduction + allow_any_instance_of(Efile::Nc::D400Calculator).to receive(:calculate_line_12a).and_return nc_agi_addition allow_any_instance_of(Efile::Nc::D400Calculator).to receive(:calculate_line_20a).and_return income_tax_withheld allow_any_instance_of(Efile::Nc::D400Calculator).to receive(:calculate_line_20b).and_return income_tax_withheld_spouse allow_any_instance_of(Efile::Nc::D400Calculator).to receive(:calculate_line_23).and_return tax_paid @@ -27,8 +31,8 @@ expect(xml.document.at('FilingStatus').text).to eq "Single" expect(xml.document.at('FAGI').text).to eq "10000" expect(xml.document.at('FAGIPlusAdditions').text).to eq "10000" - expect(xml.document.at('NCStandardDeduction').text).to eq "12750" - # 12a = (11)NCStandardDeduction + (10b)ChildDeduction expect(xml.document.at('NCAGIAddition').text).to eq "" + expect(xml.document.at('NCStandardDeduction').text).to eq standard_deduction.to_s + expect(xml.document.at('NCAGIAddition').text).to eq nc_agi_addition.to_s expect(xml.document.at('IncTaxWith').text).to eq income_tax_withheld.to_s expect(xml.document.at('IncTaxWithSpouse').text).to eq income_tax_withheld_spouse.to_s expect(xml.document.at('NCTaxPaid').text).to eq tax_paid.to_s @@ -39,6 +43,21 @@ intake.update(primary_veteran: "yes") expect(xml.document.at('VeteranInfoPrimary').text).to eq "1" end + + context "CTC-related values" do + let(:intake) { create(:state_file_nc_intake, filing_status: "head_of_household", raw_direct_file_data: StateFile::XmlReturnSampleService.new.read("nc_shiloh_hoh")) } + let(:child_deduction) { 2000 } + + before do + intake.direct_file_data.qualifying_children_under_age_ssn_count = 3 + allow_any_instance_of(Efile::Nc::D400Calculator).to receive(:calculate_line_10b).and_return child_deduction + end + + it "pulls from DF" do + expect(xml.document.at('NumChildrenAllowed').text).to eq "3" + expect(xml.document.at('ChildDeduction').text).to eq child_deduction.to_s + end + end end context "mfj filers" do @@ -47,7 +66,6 @@ it "correctly fills spouse-specific answers" do expect(xml.document.at('ResidencyStatusSpouse').text).to eq "true" expect(xml.document.at('FilingStatus').text).to eq "MFJ" - expect(xml.document.at('NCStandardDeduction').text).to eq "25500" end it "correctly fills veteran info for both primary and spouse" do @@ -64,7 +82,6 @@ expect(xml.document.at('FilingStatus').text).to eq "MFS" expect(xml.document.at('MFSSpouseName').text).to eq "Sophie Cave" expect(xml.document.at('MFSSpouseSSN').text).to eq "600000030" - expect(xml.document.at('NCStandardDeduction').text).to eq "12750" end end @@ -73,7 +90,6 @@ it "correctly fills head-of-household-specific answers" do expect(xml.document.at('FilingStatus').text).to eq "HOH" - expect(xml.document.at('NCStandardDeduction').text).to eq "19125" end end @@ -86,7 +102,6 @@ it "correctly fills qualifying-widow-specific answers" do expect(xml.document.at('FilingStatus').text).to eq "QW" expect(xml.document.at('QWYearSpouseDied').text).to eq "2023" - expect(xml.document.at('NCStandardDeduction').text).to eq "25500" end end end diff --git a/spec/models/direct_file_data_spec.rb b/spec/models/direct_file_data_spec.rb index cf69972e85..4a60a8925d 100644 --- a/spec/models/direct_file_data_spec.rb +++ b/spec/models/direct_file_data_spec.rb @@ -30,6 +30,7 @@ ["fed_total_income_exclusion_amount", 600], ["fed_housing_deduction_amount", 700], ["fed_gross_income_exclusion_amount", 900], + ["qualifying_children_under_age_ssn_count", "1"], ].each do |node_name, current_value| describe "##{node_name}" do it "returns the value" do @@ -61,6 +62,14 @@ end end + describe "#qualifying_children_under_age_ssn_count=" do + it "writes to the value when the node is present" do + direct_file_data.qualifying_children_under_age_ssn_count = 3 + + expect(direct_file_data.qualifying_children_under_age_ssn_count).to eq "3" + end + end + describe '#ny_public_employee_retirement_contributions' do let(:desc1) { '414H' } let(:desc2) { '414 (H)' }