Use the original idea for Form8949, which is simpler.
authorRobert Sesek <rsesek@bluestatic.org>
Sun, 23 Feb 2020 05:50:23 +0000 (00:50 -0500)
committerRobert Sesek <rsesek@bluestatic.org>
Sun, 23 Feb 2020 05:50:23 +0000 (00:50 -0500)
src/Form.ts
src/fed2019/Form1040.test.ts
src/fed2019/Form8949.test.ts
src/fed2019/Form8949.ts
src/fed2019/ScheduleD.ts

index 7f2356d8fc63e0b7cd70134f59067408bdf9dc45..3d9d54b928d6796642a5a7efc2c0bfe2ba8088ab 100644 (file)
@@ -45,6 +45,10 @@ export default abstract class Form<L extends { [key: string]: Line<any> },
     }
     return this._input[name];
   }
+
+  hasInput<K extends keyof I>(name: K): boolean {
+    return this._input !== undefined && name in this._input;
+  }
 };
 
 export type FormClass<T extends Form<any>> = new (...args: any[]) => T;
index 7d8b77b64e1561685e0a1123d26868dbad53aa1e..6efea22e79d3834c3259e8f649751293c7da4ab0 100644 (file)
@@ -92,7 +92,7 @@ test('capital gain/loss', () => {
     gainType: GainType.LongTerm,
     basisReportedToIRS: true
   }));
-  Form8949.addForms(tr, []);
+  tr.addForm(new Form8949);
   tr.addForm(new ScheduleD());
   tr.getForm(ScheduleD).getValue(tr, '21');
 });
index 0ec6323a6ac7d605386953c2e373ac9b6b612aa5..d966e33ce73017e2f986f4c4817fd50d3da297fa 100644 (file)
@@ -3,7 +3,7 @@ import Person from '../Person';
 
 import Form1040, { FilingStatus } from './Form1040';
 import Form1099B, { GainType } from './Form1099B';
-import Form8949, { Form8949Box } from './Form8949';
+import Form8949, { Form8949Box, Form8949Total } from './Form8949';
 
 describe('single form', () => {
   for (const box of [Form8949Box.A, Form8949Box.B, Form8949Box.D, Form8949Box.E]) {
@@ -20,21 +20,27 @@ describe('single form', () => {
         gainType: (box == Form8949Box.A || box == Form8949Box.B) ? GainType.ShortTerm : GainType.LongTerm,
         basisReportedToIRS: (box == Form8949Box.A || box == Form8949Box.D),
       }));
-      Form8949.addForms(tr, []);
-
-      const f8949s = tr.findForms(Form8949);
-      expect(f8949s.length).toBe(6);
-
-      for (let form of f8949s) {
-        if (form.getValue(tr, 'Box') == box) {
-          expect(form.getValue(tr, '2(d)')).toBe(100);
-          expect(form.getValue(tr, '2(e)')).toBe(110);
-          expect(form.getValue(tr, '2(g)')).toBe(0);
-        } else {
-          expect(form.getValue(tr, '2(d)')).toBe(0);
-          expect(form.getValue(tr, '2(e)')).toBe(0);
-          expect(form.getValue(tr, '2(g)')).toBe(0);
-        }
+
+      const form = new Form8949();
+      tr.addForm(form);
+
+      const allBoxes: (keyof Form8949['lines'])[] = ['boxA', 'boxB', 'boxC', 'boxD', 'boxE', 'boxF'];
+      const otherBoxes = allBoxes.filter(b => b != `box${box}`);
+      const thisBox = `box${box}` as keyof Form8949['lines'];
+
+      let total = form.getValue(tr, thisBox);
+
+      expect(total.proceeds).toBe(100);
+      expect(total.costBasis).toBe(110);
+      expect(total.adjustments).toBe(0);
+      expect(total.gainOrLoss).toBe(-10);
+
+      for (let otherBox of otherBoxes) {
+        total = form.getValue(tr, otherBox);
+        expect(total.proceeds).toBe(0);
+        expect(total.costBasis).toBe(0);
+        expect(total.adjustments).toBe(0);
+        expect(total.gainOrLoss).toBe(0);
       }
     });
   }
@@ -71,26 +77,29 @@ test('multiple forms', () => {
     gainType: GainType.LongTerm,
     basisReportedToIRS: false,
   }));
-  Form8949.addForms(tr, []);
-
-  const f8949s = tr.findForms(Form8949);
-  expect(f8949s.length).toBe(6);
-
-  const boxA = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.A).pop();
-  expect(boxA.getValue(tr, '2(d)')).toBe(55);
-  expect(boxA.getValue(tr, '2(e)')).toBe(50);
-  expect(boxA.getValue(tr, '2(g)')).toBe(0);
-
-  const boxE = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.E).pop();
-  expect(boxE.getValue(tr, '2(d)')).toBe(77.40);
-  expect(boxE.getValue(tr, '2(e)')).toBe(60.10);
-  expect(boxE.getValue(tr, '2(g)')).toBe(0);
-
-  const otherBoxes = f8949s.filter(f => ![Form8949Box.A, Form8949Box.E].includes(f.getValue(tr, 'Box')));
-  for (const other of otherBoxes) {
-    expect(other.getValue(tr, '2(d)')).toBe(0);
-    expect(other.getValue(tr, '2(e)')).toBe(0);
-    expect(other.getValue(tr, '2(g)')).toBe(0);
+
+  const form = new Form8949();
+  tr.addForm(form);
+
+  const boxA = form.getValue(tr, 'boxA');
+  expect(boxA.proceeds).toBe(55);
+  expect(boxA.costBasis).toBe(50);
+  expect(boxA.adjustments).toBe(0);
+  expect(boxA.gainOrLoss).toBe(5);
+
+  const boxE = form.getValue(tr, 'boxE');
+  expect(boxE.proceeds).toBe(77.40);
+  expect(boxE.costBasis).toBe(60.10);
+  expect(boxE.adjustments).toBe(0);
+  expect(boxE.gainOrLoss).toBeCloseTo(17.3);
+
+  const otherBoxes: (keyof Form8949['lines'])[] = ['boxB', 'boxC', 'boxD', 'boxF'];
+  for (const otherBox of otherBoxes) {
+    const other = form.getValue(tr, otherBox);
+    expect(other.proceeds).toBe(0);
+    expect(other.costBasis).toBe(0);
+    expect(other.adjustments).toBe(0);
+    expect(other.gainOrLoss).toBe(0);
   }
 });
 
@@ -127,33 +136,39 @@ test('adjustments', () => {
     gainType: GainType.LongTerm,
     basisReportedToIRS: true,
   }));
-  Form8949.addForms(tr, [
-    { entry: b1, code: 'W', amount: -10 },
-    { entry: b2, code: 'W', amount: 90 },
-  ]);
-
-  const f8949s = tr.findForms(Form8949);
-  expect(f8949s.length).toBe(6);
-
-  const boxA = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.B).pop();
-  expect(boxA.getValue(tr, '2(d)')).toBe(55);
-  expect(boxA.getValue(tr, '2(e)')).toBe(50);
-  expect(boxA.getValue(tr, '2(g)')).toBe(-10);
-
-  const boxD = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.D).pop();
-  expect(boxD.getValue(tr, '2(d)')).toBe(22.40);
-  expect(boxD.getValue(tr, '2(e)')).toBe(10.10);
-  expect(boxD.getValue(tr, '2(g)')).toBe(0);
-
-  const boxE = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.E).pop();
-  expect(boxE.getValue(tr, '2(d)')).toBe(18);
-  expect(boxE.getValue(tr, '2(e)')).toBe(25);
-  expect(boxE.getValue(tr, '2(g)')).toBe(90);
-
-  const otherBoxes = f8949s.filter(f => ![Form8949Box.B, Form8949Box.D, Form8949Box.E].includes(f.getValue(tr, 'Box')));
-  for (const other of otherBoxes) {
-    expect(other.getValue(tr, '2(d)')).toBe(0);
-    expect(other.getValue(tr, '2(e)')).toBe(0);
-    expect(other.getValue(tr, '2(g)')).toBe(0);
+
+  const form = new Form8949({
+    adjustments: [
+      { entry: b1, code: 'W', amount: -10 },
+      { entry: b2, code: 'W', amount: 90 },
+    ]
+  });
+  tr.addForm(form);
+
+  const boxB = form.getValue(tr, 'boxB');
+  expect(boxB.proceeds).toBe(55);
+  expect(boxB.costBasis).toBe(50);
+  expect(boxB.adjustments).toBe(-10);
+  expect(boxB.gainOrLoss).toBe(-5);
+
+  const boxE = form.getValue(tr, 'boxE');
+  expect(boxE.proceeds).toBe(18);
+  expect(boxE.costBasis).toBe(25);
+  expect(boxE.adjustments).toBe(90);
+  expect(boxE.gainOrLoss).toBe(83);
+
+  const boxD = form.getValue(tr, 'boxD');
+  expect(boxD.proceeds).toBe(22.40);
+  expect(boxD.costBasis).toBe(10.10);
+  expect(boxD.adjustments).toBe(0);
+  expect(boxD.gainOrLoss).toBeCloseTo(12.30);
+
+  const otherBoxes: (keyof Form8949['lines'])[] = ['boxA', 'boxC', 'boxF'];
+  for (const otherBox of otherBoxes) {
+    const other = form.getValue(tr, otherBox);
+    expect(other.proceeds).toBe(0);
+    expect(other.costBasis).toBe(0);
+    expect(other.adjustments).toBe(0);
+    expect(other.gainOrLoss).toBe(0);
   }
 });
index 9cd9ed9d41b52f207be53813b16b2681fc6a4f20..944061fa0d451abae107b95c4ef917f9723ab95c 100644 (file)
@@ -21,10 +21,16 @@ export interface Adjustment {
 };
 
 export interface Form8949Input {
-  box: Form8949Box;
   adjustments?: Adjustment[];
 };
 
+export interface Form8949Total {
+  proceeds: number;
+  costBasis: number;
+  adjustments: number;
+  gainOrLoss: number;
+};
+
 function matching1099Bs(tr: TaxReturn, box: Form8949Box): Form1099B[] {
   return tr.findForms(Form1099B).filter(f => {
     const gainType: GainType = f.getValue(tr, '2');
@@ -45,109 +51,29 @@ function matching1099Bs(tr: TaxReturn, box: Form8949Box): Form1099B[] {
   });
 }
 
-class Form8949Line extends Line<number> {
-  private _box: Form8949Box;
-  private _line: keyof Form1099B['lines'];
-
-  constructor(f8949Functor: () => Form8949, line: keyof Form1099B['lines'], description: string) { 
-    const box = f8949Functor().getInput('box');
-    super(`Form 8949 Box ${box} total of 1099-B ${line} (${description})`);
-    this._box = box;
-    this._line = line;
-  }
-
-  value(tr: TaxReturn): number {
-    const f1099bs = matching1099Bs(tr, this._box);
-    return sumLineOfForms(tr, f1099bs, this._line);
-  }
-};
-
-export default class Form8949 extends Form<Form8949['_lines'], Form8949Input> {
-  readonly name = '8949';
-
-  readonly supportsMultipleCopies = true;
-
-  protected readonly _lines = {
-    'Box': new InputLine<Form8949Input>('box'),
-    '2(d)': new Form8949Line(() => this, '1d', 'proceeds'),
-    '2(e)': new Form8949Line(() => this, '1e', 'cost'),
-    '2(g)': new ComputedLine((tr: TaxReturn): number => {
-      const f1099bs = matching1099Bs(tr, this.getInput('box'));
-      const adjustments = this.getInput('adjustments').filter((a: Adjustment): boolean => {
-        return f1099bs.includes(a.entry);
-      });
-      return adjustments.reduce((acc, curr) => acc + curr.amount, 0);
-    }, 'adjustments')
-  };
-
-  static addForms(tr: TaxReturn, adjustments: Adjustment[]) {
-    tr.addForm(new Form8949({ box: Form8949Box.A, adjustments }));
-    tr.addForm(new Form8949({ box: Form8949Box.B, adjustments }));
-    tr.addForm(new Form8949({ box: Form8949Box.C, adjustments }));
-    tr.addForm(new Form8949({ box: Form8949Box.D, adjustments }));
-    tr.addForm(new Form8949({ box: Form8949Box.E, adjustments }));
-    tr.addForm(new Form8949({ box: Form8949Box.F, adjustments }));
-  }
-};
-
-/*
-export interface Adjustment {
-  entry: Form1099B;
-  code: string;
-  amount: number;
-};
-
-export interface Form8949Input {
-  adjustments?: []Adjustment;
-};
-
-export interface Form8949Total {
-  proceeds: number;
-  costBasis: number;
-  adjustmentAmount: number;
-  gainOrLoss: number;
-};
-
 class Form8949Line extends Line<Form8949Total> {
   private _box: Form8949Box;
+  private _line: keyof Form1099B['lines'];
 
-  constructor(description: string, box: Form8949Input) {
-    super(description);
+  constructor(box: Form8949Box) {
+    super(`Form 8949 Box ${box} Total`);
     this._box = box;
   }
 
   value(tr: TaxReturn): Form8949Total {
-    const lineShortTerm = this._box == Form8949Box.A || this._box == Form8949Box.B || this._box == Form8949Box.C;
-    const lineBasisReported = this._box == Form8949Box.A || this._box == Form8949Box.D;
-
-    const f1099bs = tr.findForms(Form1099B);
-    const relevant1099bs: Form1099B[] = [];
-    for (const form of f1099bs) {
-      const gainType = form.getValue(tr, '2');
-      const basisReported = form.getValue(tr, '12');
-
-      if (lineBasisReported != basisReported)
-        continue;
-
-      if (gainType == GainType.ShortTerm && lineShortTerm) {
-        relevant1099bs.push(form);
-      } else if (gainType == GainType.LongTerm && !lineShortTerm) {
-        relevant1099bs.push(form);
-      }
-    }
-
-    const sumValues = (line: keyof Form1099B['lines']) =>
-        relevant1099bs.map((f: Form1099B): number => f.getValue(tr, line))
-          .reduce((acc, curr) => acc + curr, 0);
-
-    const proceeds = sumValues('1d');
-    const costBasis = sumValues('1e');
-
+    const f1099bs = matching1099Bs(tr, this._box);
+    const proceeds = sumLineOfForms(tr, f1099bs, '1d');
+    const costBasis = sumLineOfForms(tr, f1099bs, '1e');
+    const f8949 = tr.getForm(Form8949);
+    const adjustments = !f8949.hasInput('adjustments') ? 0 :
+        f8949.getInput('adjustments')
+          .filter(a => f1099bs.includes(a.entry))
+          .reduce((acc, curr) => acc + curr.amount, 0);
     return {
       proceeds,
       costBasis,
-      adjustmentAmount: 0,
-      gainOrLoss: costBasis - proceeds,
+      adjustments,
+      gainOrLoss: proceeds - costBasis + adjustments,
     };
   }
 };
@@ -155,13 +81,14 @@ class Form8949Line extends Line<Form8949Total> {
 export default class Form8949 extends Form<Form8949['_lines'], Form8949Input> {
   readonly name = '8949';
 
+  readonly supportsMultipleCopies = true;
+
   protected readonly _lines = {
-    boxATotals: new Form8949Line('Short-term basis reported', Form8949Box.A), 
-    boxBTotals: new Form8949Line('Short-term basis NOT reported', Form8949Box.B), 
-    boxCTotals: new Form8949Line('Short-term unreported', Form8949Box.C), 
-    boxDTotals: new Form8949Line('Long-term basis reported', Form8949Box.D), 
-    boxETotals: new Form8949Line('Long-term basis NOT reported', Form8949Box.E), 
-    boxFTotals: new Form8949Line('Long-term unreported', Form8949Box.F)
+    'boxA': new Form8949Line(Form8949Box.A),
+    'boxB': new Form8949Line(Form8949Box.B),
+    'boxC': new Form8949Line(Form8949Box.C),
+    'boxD': new Form8949Line(Form8949Box.D),
+    'boxE': new Form8949Line(Form8949Box.E),
+    'boxF': new Form8949Line(Form8949Box.F),
   };
 };
-*/
index ccf1e346581d5fe46a178363d7aa6b441882cda2..14ca67264daeb0c053b6c127e595c02e1a794a55 100644 (file)
@@ -7,81 +7,26 @@ import Form8949, { Form8949Box } from './Form8949';
 import Form1099DIV from './Form1099DIV';
 import Form1040, { FilingStatus } from './Form1040';
 
-class ScheduleDTotal extends Line<number> {
-  private _line: keyof Form8949['lines'];
-  private _box: Form8949Box;
-
-  constructor(description: string, line: keyof Form8949['lines'], box: Form8949Box) {
-    super(description);
-    this._line = line;
-    this._box = box;
-  }
-
-  value(tr: TaxReturn): number {
-    const forms: Form8949[] = tr.findForms(Form8949).filter(f => f.getValue(tr, 'Box') == this._box);
-    return sumLineOfForms(tr, forms, this._line);
-  }
-};
-
 export default class ScheduleD extends Form<ScheduleD['_lines']> {
   readonly name = 'Schedule D';
 
   protected readonly _lines = {
     // 1a not supported (Totals for all short-term transactions reported on Form 1099-B for which basis was reported to the IRS and for which you have no adjustments)
-
-    '1b(d)': new ScheduleDTotal('Proceeds of short-term basis reported', '2(d)', Form8949Box.A),
-    '1b(e)': new ScheduleDTotal('Cost basis of short-term basis-reported', '2(e)', Form8949Box.A),
-    '1b(g)': new ScheduleDTotal('Adjustments to short-term basis-reported', '2(g)', Form8949Box.A),
-    '1b(h)': new ComputedLine((tr: TaxReturn): number => {
-      return (this.getValue(tr, '1b(d)') - this.getValue(tr, '1b(e)')) + this.getValue(tr, '1b(g)');
-    }, 'Gain of short-term basis reported'),
-
-    '2(d)': new ScheduleDTotal('Proceeds of short-term basis NOT reported', '2(d)', Form8949Box.B),
-    '2(e)': new ScheduleDTotal('Cost basis of short-term NOT basis-reported', '2(e)', Form8949Box.B),
-    '2(g)': new ScheduleDTotal('Adjustments to short-term NOT basis-reported', '2(g)', Form8949Box.B),
-    '2(h)': new ComputedLine((tr: TaxReturn): number => {
-      return (this.getValue(tr, '2(d)') - this.getValue(tr, '2(e)')) + this.getValue(tr, '2(g)');
-    }, 'Gain of short-term basis NOT reported'),
-
-    '3(d)': new ScheduleDTotal('Proceeds of short-term basis unreported', '2(d)', Form8949Box.C),
-    '3(e)': new ScheduleDTotal('Cost basis of short-term unreported', '2(e)', Form8949Box.C),
-    '3(g)': new ScheduleDTotal('Adjustments to short-term unreported', '2(g)', Form8949Box.C),
-    '3(h)': new ComputedLine((tr: TaxReturn): number => {
-      return (this.getValue(tr, '3(d)') - this.getValue(tr, '3(e)')) + this.getValue(tr, '3(g)');
-    }, 'Gain of short-term unreported'),
-
     // 4 is not supported (Short-term gain from Form 6252 and short-term gain or (loss) from Forms 4684, 6781, and 8824)
     // 5 is not supported (Net short-term gain or (loss) from partnerships, S corporations, estates, and trusts from Schedule(s) K-1)
     // 6 is not supported (Short-term capital loss carryover. Enter the amount, if any, from line 8 of your Capital Loss Carryover Worksheet in the instructions)
 
     '7': new ComputedLine((tr: TaxReturn): number => {
+      // 1-3 are computed by Form8949.
       // 4-6 should be included.
-      return this.getValue(tr, '1b(h)') + this.getValue(tr, '2(h)'), this.getValue(tr, '3(h)');
+      const f8949 = tr.getForm(Form8949);
+      return f8949.getValue(tr, 'boxA').gainOrLoss +
+             f8949.getValue(tr, 'boxB').gainOrLoss +
+             f8949.getValue(tr, 'boxC').gainOrLoss;
     }, 'Net short-term capital gain or (loss)'),
 
     // 8a is not supported.
 
-    '8b(d)': new ScheduleDTotal('Proceeds of long-term basis reported', '2(d)', Form8949Box.D),
-    '8b(e)': new ScheduleDTotal('Cost basis of long-term basis-reported', '2(e)', Form8949Box.D),
-    '8b(g)': new ScheduleDTotal('Adjustments to long-term basis-reported', '2(g)', Form8949Box.D),
-    '8b(h)': new ComputedLine((tr: TaxReturn): number => {
-      return (this.getValue(tr, '8b(d)') - this.getValue(tr, '8b(e)')) + this.getValue(tr, '8b(g)');
-    }, 'Gain of long-term basis reported'),
-
-    '9(d)': new ScheduleDTotal('Proceeds of long-term basis NOT reported', '2(d)', Form8949Box.E),
-    '9(e)': new ScheduleDTotal('Cost basis of long-term NOT basis-reported', '2(e)', Form8949Box.E),
-    '9(g)': new ScheduleDTotal('Adjustments to long-term NOT basis-reported', '2(g)', Form8949Box.E),
-    '9(h)': new ComputedLine((tr: TaxReturn): number => {
-      return (this.getValue(tr, '9(d)') - this.getValue(tr, '9(e)')) + this.getValue(tr, '9(g)');
-    }, 'Gain of long-term basis NOT reported'),
-
-    '10(d)': new ScheduleDTotal('Proceeds of long-term basis unreported', '2(d)', Form8949Box.F),
-    '10(e)': new ScheduleDTotal('Cost basis of long-term unreported', '2(e)', Form8949Box.F),
-    '10(g)': new ScheduleDTotal('Adjustments to long-term unreported', '2(g)', Form8949Box.F),
-    '10(h)': new ComputedLine((tr: TaxReturn): number => {
-      return (this.getValue(tr, '10(d)') - this.getValue(tr, '10(e)')) + this.getValue(tr, '10(g)');
-    }, 'Gain of long-term unreported'),
-
     // 11 is not supported (Gain from Form 4797, Part I; long-term gain from Forms 2439 and 6252; and long-term gain or (loss) from Forms 4684, 6781, and 8824)
     // 12 is not supported (Net long-term gain or (loss) from partnerships, S corporations, estates, and trusts from Schedule(s) K-1)
 
@@ -91,8 +36,11 @@ export default class ScheduleD extends Form<ScheduleD['_lines']> {
 
     '15': new ComputedLine((tr: TaxReturn): number => {
       // 11-14 should be included.
-      return this.getValue(tr, '8b(h)') + this.getValue(tr, '9(h)') + this.getValue(tr, '10(h)') +
-          this.getValue(tr, '13');
+      const f8949 = tr.getForm(Form8949);
+      return f8949.getValue(tr, 'boxD').gainOrLoss +
+             f8949.getValue(tr, 'boxE').gainOrLoss +
+             f8949.getValue(tr, 'boxF').gainOrLoss +
+             this.getValue(tr, '13');
     }, 'Net long-term capital gain or (loss)'),
 
     '16': new ComputedLine((tr: TaxReturn): number => {