Add Form1099R and 8606.
authorRobert Sesek <rsesek@bluestatic.org>
Sat, 7 Mar 2020 22:59:52 +0000 (17:59 -0500)
committerRobert Sesek <rsesek@bluestatic.org>
Sat, 7 Mar 2020 22:59:52 +0000 (17:59 -0500)
src/fed2019/Form1040.test.ts
src/fed2019/Form1040.ts
src/fed2019/Form1099R.ts [new file with mode: 0644]
src/fed2019/Form8606.test.ts [new file with mode: 0644]
src/fed2019/Form8606.ts [new file with mode: 0644]

index 6308113df9f3fd81ed57c6ee47cca0e525a4ac86..3c9aa03c50d1ab39158cb79c9050b86a0bd4f64b 100644 (file)
@@ -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);
+});
index 3af3c051e6f81be3a6d457d1bc9b8d476254f501..16eb259a7515e9b38ec0129f641daa97a6d9ecfd 100644 (file)
@@ -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<Form1040['_lines'], Form1040Input> {
     '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<Form1040['_lines'], Form1040Input> {
     '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 (file)
index 0000000..631a7df
--- /dev/null
@@ -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<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'),
+  };
+};
diff --git a/src/fed2019/Form8606.test.ts b/src/fed2019/Form8606.test.ts
new file mode 100644 (file)
index 0000000..90047a1
--- /dev/null
@@ -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 (file)
index 0000000..6149084
--- /dev/null
@@ -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<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')),
+  };
+};