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';
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);
+});
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';
'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
'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'),
];
--- /dev/null
+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<T extends keyof Form1099RInput> extends InputLine<Form1099RInput, T> {};
+
+export default class Form1099R extends Form<Form1099R['_lines'], Form1099RInput> {
+ 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'),
+ };
+};
--- /dev/null
+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);
+});
--- /dev/null
+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<T extends keyof Form8606Input> extends InputLine<Form8606Input, T> {};
+
+export default class Form8606 extends Form<Form8606['_lines'], Form8606Input> {
+ 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')),
+ };
+};