From 8706be1f94edca02adb409907a52063119314493 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sat, 7 Mar 2020 17:59:52 -0500 Subject: [PATCH] Add Form1099R and 8606. --- src/fed2019/Form1040.test.ts | 45 ++++++++++++++++++++++ src/fed2019/Form1040.ts | 16 ++++++-- src/fed2019/Form1099R.ts | 73 ++++++++++++++++++++++++++++++++++++ src/fed2019/Form8606.test.ts | 43 +++++++++++++++++++++ src/fed2019/Form8606.ts | 64 +++++++++++++++++++++++++++++++ 5 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 src/fed2019/Form1099R.ts create mode 100644 src/fed2019/Form8606.test.ts create mode 100644 src/fed2019/Form8606.ts diff --git a/src/fed2019/Form1040.test.ts b/src/fed2019/Form1040.test.ts index 6308113..3c9aa03 100644 --- a/src/fed2019/Form1040.test.ts +++ b/src/fed2019/Form1040.test.ts @@ -6,7 +6,9 @@ import Form1040, { FilingStatus, Schedule2 } from './Form1040'; import Form1099DIV from './Form1099DIV'; import Form1099INT from './Form1099INT'; import Form1099B, { GainType } from './Form1099B'; +import Form1099R, { Box7Code } from './Form1099R'; import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD'; +import Form8606 from './Form8606'; import Form8959 from './Form8959'; import Form8949 from './Form8949'; import FormW2 from './FormW2'; @@ -123,3 +125,46 @@ test('require Form8959', () => { expect(f1040.getValue(tr, '1')).toBe(400000); expect(f1040.getValue(tr, '8b')).toBe(400000); }); + +test('backdoor and megabackdoor roth', () => { + const p = Person.self('A'); + const tr = new TaxReturn(2019); + tr.addForm(new Form1099R({ + payer: 'Roth', + payee: p, + grossDistribution: 6000, + taxableAmount: 6000, + taxableAmountNotDetermined: true, + totalDistribution: true, + fedIncomeTax: 0, + distributionCodes: [Box7Code._2], + iraSepSimple: true + })); + tr.addForm(new Form1099R({ + payer: '401k', + payee: p, + grossDistribution: 27500, + taxableAmount: 0, + taxableAmountNotDetermined: false, + totalDistribution: false, + fedIncomeTax: 0, + employeeContributionsOrDesignatedRothContributions: 27500, + distributionCodes: [Box7Code.G], + iraSepSimple: false + })); + tr.addForm(new Form8606({ + person: p, + nondeductibleContributions: 6000, + traditionalIraBasis: 0, + distributionFromTradSepOrSimpleIraOrMadeRothConversion: true, + contributionsMadeInCurrentYear: 0, + distributionsFromAllTradSepSimpleIras: 0, + valueOfAllTradSepSimpleIras: 0, + amountConvertedFromTradSepSimpleToRoth: 6000 + })); + const f = new Form1040(); + tr.addForm(f); + + expect(f.getValue(tr, '4a')).toBe(6000); + expect(f.getValue(tr, '4b')).toBe(0); +}); diff --git a/src/fed2019/Form1040.ts b/src/fed2019/Form1040.ts index 3af3c05..16eb259 100644 --- a/src/fed2019/Form1040.ts +++ b/src/fed2019/Form1040.ts @@ -1,11 +1,13 @@ import Form, { FormClass } from '../Form'; import TaxReturn from '../TaxReturn'; -import { Line, AccumulatorLine, ComputedLine, ReferenceLine } from '../Line'; +import { Line, AccumulatorLine, ComputedLine, ReferenceLine, sumLineOfForms } from '../Line'; import { UnsupportedFeatureError } from '../Errors'; +import Form8606 from './Form8606'; import Form8959 from './Form8959'; import Form1099INT from './Form1099INT'; import Form1099DIV from './Form1099DIV'; +import Form1099R, { Box7Code } from './Form1099R'; import FormW2 from './FormW2'; import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD'; @@ -30,8 +32,14 @@ export default class Form1040 extends Form { '2b': new AccumulatorLine(Form1099INT, '1', 'Taxable interest'), '3a': new AccumulatorLine(Form1099DIV, '1b', 'Qualified dividends'), '3b': new AccumulatorLine(Form1099DIV, '1a', 'Ordinary dividends'), - // 4a and 4b are complex - '4b': new ComputedLine(() => 0), + '4a': new ComputedLine((tr): number => { + const f1099Rs = tr.findForms(Form1099R).filter(f => !f.getValue(tr, '7').includes(Box7Code.G)); + return sumLineOfForms(tr, f1099Rs, '1'); + }), + '4b': new ComputedLine((tr): number => { + const f8606s = tr.findForms(Form8606); + return sumLineOfForms(tr, f8606s, '15c') + sumLineOfForms(tr, f8606s, '18'); + }, 'IRA distributions, taxadble amount'), '4d': new ComputedLine(() => 0), // 4c and 4d are not supported // 5a and 5b are not supported @@ -131,7 +139,7 @@ export default class Form1040 extends Form { '17': new ComputedLine((tr): number => { const fedTaxWithheldBoxes = [ new AccumulatorLine(FormW2, '2'), - //new AccumulatorLine(Form1099R, '4'), + new AccumulatorLine(Form1099R, '4'), new AccumulatorLine(Form1099DIV, '4'), new AccumulatorLine(Form1099INT, '4'), ]; diff --git a/src/fed2019/Form1099R.ts b/src/fed2019/Form1099R.ts new file mode 100644 index 0000000..631a7df --- /dev/null +++ b/src/fed2019/Form1099R.ts @@ -0,0 +1,73 @@ +import Form from '../Form'; +import Person from '../Person'; +import TaxReturn from '../TaxReturn'; +import { InputLine } from '../Line'; + +export enum Box7Code { + _1 = '1', // Early distribution, no known exception + _2 = '2', // Early distribution, exception applies + _3 = '3', // Disability + _4 = '4', // Death + _5 = '5', // Prohibited transaction + _6 = '6', // Section 1035 exchange + _7 = '7', // Normal distribution + _8 = '8', // Excess contributions plus earnings/excess deferrals taxable + _9 = '9', // Cost of current life insurance protection + A = 'A', // May be eligible for 10-year tax option + B = 'B', // Designated Roth account distribution + C = 'C', // Reportable death benefits under section 6050Y + D = 'D', // Annuity payments from nonqualified annuities that may be subject to tax under section 1411. + E = 'E', // Distributions under Employee Plans Compliance Resolution System (EPCRS). + F = 'F', // Charitable gift annuity. + G = 'G', // Direct rollover of a distribution to a qualified plan, a section 403(b) plan, a governmental section 457(b) plan, or an IRA. + H = 'H', // Direct rollover of a designated Roth account distribution to a Roth IRA. + J = 'J', // Early distribution from a Roth IRA, no known exception (in most cases, under age 59½). + K = 'K', // Distribution of traditional IRA assets not having a readily available FMV. + L = 'L', // Loans treated as distributions. + M = 'M', // Qualified plan loan offset. + N = 'N', // Recharacterized IRA contribution made for 2019 and recharacterized in 2019. + P = 'P', // Excess contributions plus earnings/excess deferrals (and/or earnings) taxable in 2018. + Q = 'Q', // Qualified distribution from a Roth IRA. R—Recharacterized IRA contribution made for 2018 and recharacterized in 2019. + S = 'S', // Early distribution from a SIMPLE IRA in first 2 years, no known exception (under age 59½). + T = 'T', // Roth IRA distribution, exception applies. + U = 'U', // Dividend distribution from ESOP under section 404(k). + W = 'W', // Charges or payments for purchasing qualified long-term care insurance contracts under combined arrangements. If the IRA/SEP/SIMPLE box is checked,you've received a traditional IRA, SEP, or SIMPLE distribution. +}; + +export interface Form1099RInput { + payer: string; + payee: Person; + grossDistribution: number; + taxableAmount: number; + taxableAmountNotDetermined: boolean; + totalDistribution: boolean; + capitalGain?: number; + fedIncomeTax?: number; + employeeContributionsOrDesignatedRothContributions?: number; + distributionCodes?: Box7Code[]; + iraSepSimple?: boolean; + firstYearOfDesignatedRothContributions?: number; +}; + +class Input extends InputLine {}; + +export default class Form1099R extends Form { + readonly name = '1099-R'; + + readonly supportsMultipleCopies = true; + + protected readonly _lines = { + 'payer': new Input('payer'), + 'recipeint': new Input('payee'), + '1': new Input('grossDistribution'), + '2a': new Input('taxableAmount'), + '2b.1': new Input('taxableAmountNotDetermined'), + '2b.2': new Input('totalDistribution'), + '3': new Input('capitalGain'), + '4': new Input('fedIncomeTax'), + '5': new Input('employeeContributionsOrDesignatedRothContributions'), + '7': new Input('distributionCodes'), + '7.1': new Input('iraSepSimple'), + '11': new Input('firstYearOfDesignatedRothContributions'), + }; +}; diff --git a/src/fed2019/Form8606.test.ts b/src/fed2019/Form8606.test.ts new file mode 100644 index 0000000..90047a1 --- /dev/null +++ b/src/fed2019/Form8606.test.ts @@ -0,0 +1,43 @@ +import TaxReturn from '../TaxReturn'; +import Person from '../Person'; + +import Form1040, { FilingStatus } from './Form1040'; +import Form8606 from './Form8606'; + +test('skip part 1', () => { + const p = Person.self('A'); + const tr = new TaxReturn(2019); + const f = new Form8606({ + person: p, + nondeductibleContributions: 6000, + traditionalIraBasis: 0, + distributionFromTradSepOrSimpleIraOrMadeRothConversion: false + }); + tr.addForm(f); + + expect(f.getValue(tr, '14')).toBe(6000); +}); + +test('Roth conversion no basis', () => { + const p = Person.self('A'); + const tr = new TaxReturn(2019); + const f = new Form8606({ + person: p, + nondeductibleContributions: 6000, + traditionalIraBasis: 0, + distributionFromTradSepOrSimpleIraOrMadeRothConversion: true, + contributionsMadeInCurrentYear: 0, + valueOfAllTradSepSimpleIras: 0, + distributionsFromAllTradSepSimpleIras: 0, + amountConvertedFromTradSepSimpleToRoth: 6000, + }); + tr.addForm(f); + + expect(f.getValue(tr, '9')).toBe(6000); + expect(f.getValue(tr, '13')).toBe(6000); + expect(f.getValue(tr, '14')).toBe(0); + expect(f.getValue(tr, '15c')).toBe(0); + expect(f.getValue(tr, '16')).toBe(6000); + expect(f.getValue(tr, '17')).toBe(6000); + expect(f.getValue(tr, '18')).toBe(0); +}); diff --git a/src/fed2019/Form8606.ts b/src/fed2019/Form8606.ts new file mode 100644 index 0000000..6149084 --- /dev/null +++ b/src/fed2019/Form8606.ts @@ -0,0 +1,64 @@ +import Form from '../Form'; +import TaxReturn from '../TaxReturn'; +import Person from '../Person'; +import { Line, AccumulatorLine, ComputedLine, InputLine, ReferenceLine } from '../Line'; +import { clampToZero } from '../Math'; + +export interface Form8606Input { + person: Person; + nondeductibleContributions: number; + traditionalIraBasis: number; + distributionFromTradSepOrSimpleIraOrMadeRothConversion: boolean; + contributionsMadeInCurrentYear?: number; + valueOfAllTradSepSimpleIras?: number; + distributionsFromAllTradSepSimpleIras?: number; + amountConvertedFromTradSepSimpleToRoth?: number; +}; + +class Input extends InputLine {}; + +export default class Form8606 extends Form { + readonly name = '8606'; + + readonly supportsMultipleCopies = true; + + protected readonly _lines = { + 'person': new Input('person'), + + // Part 1 + '1': new Input('nondeductibleContributions'), + '2': new Input('traditionalIraBasis'), + '3': new ComputedLine((tr): number => this.getValue(tr, '1') + this.getValue(tr, '2')), + '4': new Input('contributionsMadeInCurrentYear'), + '5': new ComputedLine((tr): number => this.getValue(tr, '3') - this.getValue(tr, '4')), + '6': new Input('valueOfAllTradSepSimpleIras'), + '7': new Input('distributionsFromAllTradSepSimpleIras'), + '8': new Input('amountConvertedFromTradSepSimpleToRoth'), + '9': new ComputedLine((tr): number => { + let value = 0; + value += this.getValue(tr, '6') || 0; + value += this.getValue(tr, '7') || 0; + value += this.getValue(tr, '8') || 0; + return value; + }), + '10': new ComputedLine((tr): number => this.getValue(tr, '5') / this.getValue(tr, '9')), + '11': new ComputedLine((tr): number => this.getValue(tr, '8') * this.getValue(tr, '10'), 'Nontaxable portion converted to Roth'), + '12': new ComputedLine((tr): number => this.getValue(tr, '7') * this.getValue(tr, '10'), 'Nontaxable portion of distributions not converted to Roth'), + '13': new ComputedLine((tr): number => this.getValue(tr, '11') + this.getValue(tr, '12'), 'Nontaxable portion of all distributions'), + '14': new ComputedLine((tr): number => { + const l3 = this.getValue(tr, '3'); + if (!this.getInput('distributionFromTradSepOrSimpleIraOrMadeRothConversion')) + return l3; + return l3 - this.getValue(tr, '13'); + }, 'Total basis in Traditional IRAs'), + '15a': new ComputedLine((tr): number => this.getValue(tr, '7') - this.getValue(tr, '12')), + '15b': new ComputedLine((): number => 0, 'Amount attributable to qualified disaster distributions'), + // 15b not supported - amount on line 15a attributable + '15c': new ComputedLine((tr): number => this.getValue(tr, '15a') - this.getValue(tr, '15b'), 'Taxable amount'), + + // Part 2 + '16': new ReferenceLine(Form8606 as any, '8'), + '17': new ReferenceLine(Form8606 as any, '11'), + '18': new ComputedLine((tr): number => this.getValue(tr, '16') - this.getValue(tr, '17')), + }; +}; -- 2.22.5