From 1b1f797ffb71294ca76c627dd6e836c462c5cb23 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 23 Feb 2020 00:50:23 -0500 Subject: [PATCH] Use the original idea for Form8949, which is simpler. --- src/Form.ts | 4 + src/fed2019/Form1040.test.ts | 2 +- src/fed2019/Form8949.test.ts | 143 +++++++++++++++++++---------------- src/fed2019/Form8949.ts | 129 +++++++------------------------ src/fed2019/ScheduleD.ts | 72 +++--------------- 5 files changed, 122 insertions(+), 228 deletions(-) diff --git a/src/Form.ts b/src/Form.ts index 7f2356d..3d9d54b 100644 --- a/src/Form.ts +++ b/src/Form.ts @@ -45,6 +45,10 @@ export default abstract class Form }, } return this._input[name]; } + + hasInput(name: K): boolean { + return this._input !== undefined && name in this._input; + } }; export type FormClass> = new (...args: any[]) => T; diff --git a/src/fed2019/Form1040.test.ts b/src/fed2019/Form1040.test.ts index 7d8b77b..6efea22 100644 --- a/src/fed2019/Form1040.test.ts +++ b/src/fed2019/Form1040.test.ts @@ -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'); }); diff --git a/src/fed2019/Form8949.test.ts b/src/fed2019/Form8949.test.ts index 0ec6323..d966e33 100644 --- a/src/fed2019/Form8949.test.ts +++ b/src/fed2019/Form8949.test.ts @@ -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); } }); diff --git a/src/fed2019/Form8949.ts b/src/fed2019/Form8949.ts index 9cd9ed9..944061f 100644 --- a/src/fed2019/Form8949.ts +++ b/src/fed2019/Form8949.ts @@ -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 { - 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 { - readonly name = '8949'; - - readonly supportsMultipleCopies = true; - - protected readonly _lines = { - 'Box': new InputLine('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 { 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 { export default class Form8949 extends Form { 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), }; }; -*/ diff --git a/src/fed2019/ScheduleD.ts b/src/fed2019/ScheduleD.ts index ccf1e34..14ca672 100644 --- a/src/fed2019/ScheduleD.ts +++ b/src/fed2019/ScheduleD.ts @@ -7,81 +7,26 @@ import Form8949, { Form8949Box } from './Form8949'; import Form1099DIV from './Form1099DIV'; import Form1040, { FilingStatus } from './Form1040'; -class ScheduleDTotal extends Line { - 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 { 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 { '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 => { -- 2.22.5