From 4b51eb6ca47ca496937b2739a5c53bed94abb5c0 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Fri, 21 Feb 2020 22:58:50 -0500 Subject: [PATCH] Start implementing Form 1040 tax calculation. --- src/Line.ts | 6 ++- src/fed2019/Form1040.test.ts | 8 ++-- src/fed2019/Form1040.ts | 87 ++++++++++++++++++++++++++++++++---- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/src/Line.ts b/src/Line.ts index 91f6bbf..48141ae 100644 --- a/src/Line.ts +++ b/src/Line.ts @@ -40,14 +40,18 @@ export class ComputedLine extends Line { export class ReferenceLine extends Line { private _form: string; private _line: string; + private _fallback?: T; - constructor(form: string, line: string, description?: string) { + constructor(form: string, line: string, description?: string, fallback?: T) { super(description || `Reference F${form}.L${line}`); this._form = form; this._line = line; + this._fallback = fallback; } value(tr: TaxReturn): T { + if (this._fallback !== undefined && !tr.maybeGetForm(this._form)) + return this._fallback; return tr.getForm(this._form).getLine(this._line).value(tr); } }; diff --git a/src/fed2019/Form1040.test.ts b/src/fed2019/Form1040.test.ts index cfe9b64..ef3dde6 100644 --- a/src/fed2019/Form1040.test.ts +++ b/src/fed2019/Form1040.test.ts @@ -1,7 +1,7 @@ import Person from '../Person'; import TaxReturn from '../TaxReturn'; -import Form1040 from './Form1040'; +import Form1040, { FilingStatus } from './Form1040'; import Form1099INT from './Form1099INT'; import FormW2 from './FormW2'; @@ -9,11 +9,11 @@ test('w2 wages', () => { const pa = Person.self('A'); const pb = Person.spouse('B'); const tr = new TaxReturn(2019); - tr.addForm(new FormW2({ employer: 'AA', employee: pa, wages: 100.00, fedIncomeTax: 0 })); + tr.addForm(new FormW2({ employer: 'AA', employee: pa, wages: 1000000.00, fedIncomeTax: 0 })); tr.addForm(new FormW2({ employer: 'BB', employee: pb, wages: 36.32, fedIncomeTax: 0 })); - const f1040 = new Form1040(); + const f1040 = new Form1040({ filingStatus: FilingStatus.MarriedFilingJoint }); tr.addForm(f1040); - expect(f1040.getValue(tr, '1')).toBe(136.32); + expect(f1040.getValue(tr, '1')).toBe(1000036.32); f1040.getValue(tr, '23'); }); diff --git a/src/fed2019/Form1040.ts b/src/fed2019/Form1040.ts index 3d77496..b99ae60 100644 --- a/src/fed2019/Form1040.ts +++ b/src/fed2019/Form1040.ts @@ -1,10 +1,11 @@ import Form from '../Form'; import TaxReturn from '../TaxReturn'; import { Line, AccumulatorLine, ComputedLine, ReferenceLine } from '../Line'; +import { UnsupportedFeatureError } from '../Errors'; export enum FilingStatus { Single, - MarriedFilingSingle, + MarriedFilingSeparate, MarriedFilingJoint, }; @@ -24,18 +25,20 @@ export default class Form1040 extends Form { '3a': new AccumulatorLine('1099-DIV', '1b', 'Qualified dividends'), '3b': new AccumulatorLine('1099-DIV', '1a', 'Ordinary dividends'), // 4a and 4b are complex + '4b': new ComputedLine(() => 0), + '4d': new ComputedLine(() => 0), // 4c and 4d are not supported // 5a and 5b are not supported - // 6 - Sched D - '7a': new ReferenceLine('Schedule 1', '9', 'Other income from Schedule 1'), + '6': new ReferenceLine('Schedule D', '21', 'Capital gain/loss', 0), + '7a': new ReferenceLine('Schedule 1', '9', 'Other income from Schedule 1', 0), '7b': new ComputedLine((tr: TaxReturn): number => { - const lineIds = ['1', '2b', '3b', '4b', '4d', '5b', '6', '7a']; + const lineIds = ['1', '2b', '3b', '4b', '4d', /*'5b',*/ '6', '7a']; const lines: number[] = lineIds.map(l => this.getValue(tr, l as keyof Form1040['_lines'])); return reduceBySum(lines); }, 'Total income'), - '8a': new ReferenceLine('Schedule 1', '22', 'Adjustments to income'), + '8a': new ReferenceLine('Schedule 1', '22', 'Adjustments to income', 0), '8b': new ComputedLine((tr: TaxReturn): number => { return this.getValue(tr, '7b') - this.getValue(tr, '8a'); @@ -48,9 +51,9 @@ export default class Form1040 extends Form { const taxableIncome = this.getValue(tr, '8b'); let use8995a = false; switch (this.getInput('filingStatus')) { - case FilingStatus.Single: use8995a = taxableIncome <= 160700; break; - case FilingStatus.MarriedFilingSingle: use8995a = taxableIncome <= 160725; break; - case FilingStatus.MarriedFilingJoint: use8995a = taxableIncome <= 321400; break; + case FilingStatus.Single: use8995a = taxableIncome <= 160700; break; + case FilingStatus.MarriedFilingSeparate: use8995a = taxableIncome <= 160725; break; + case FilingStatus.MarriedFilingJoint: use8995a = taxableIncome <= 321400; break; }; return 0; }, 'Qualified business income deduction'), @@ -63,8 +66,73 @@ export default class Form1040 extends Form { return value < 0 ? 0 : value; }, 'Taxable income'), - '16': new ComputedLine((tr: TaxReturn): number => { + '12a': new ComputedLine((tr: TaxReturn): number => { + // Not supported: + // Form 8814 (election to report child's interest or dividends) + // Form 4972 (relating to lump-sum distributions) + const taxableIncome = this.getValue(tr, '11b'); + if (taxableIncome < 100000) + throw new UnsupportedFeatureError('Tax-table tax liability not supported'); + + const l11b = this.getValue(tr, '11b'); + + switch (this.getInput('filingStatus')) { + case FilingStatus.Single: + if (taxableIncome < 160725) + return (l11b * 0.24) - 5825.50; + else if (taxableIncome < 204100) + return (l11b * 0.32) - 18683.50; + else if (taxableIncome < 510300) + return (l11b * 0.35) - 24806.50; + else + return (l11b * 0.38) - 35012.50; + case FilingStatus.MarriedFilingJoint: + if (taxableIncome < 168400) + return (l11b * 0.22) - 8283.00; + else if (taxableIncome < 321450) + return (l11b * 0.24) - 11651.00; + else if (taxableIncome < 408200) + return (l11b * 0.32) - 37367.00; + else if (taxableIncome < 612350) + return (l11b * 0.35) - 49613.00; + else + return (l11b * 0.37) - 61860.00; + case FilingStatus.MarriedFilingSeparate: + if (taxableIncome < 160725) + return (l11b * 0.24) - 5825.50; + else if (taxableIncome < 204100) + return (l11b * 0.32) - 18683.50; + else if (taxableIncome < 306175) + return (l11b * 0.35) - 24806.50; + else + return (l11b * 0.37) - 30930.00; + } + throw new UnsupportedFeatureError('Unexpected return type'); + }, 'Tax'), + + '12b': new ComputedLine((tr: TaxReturn): number => { + // TODO: add Sched 2.L3 + return this.getValue(tr, '12a'); + }), + + // Not supported: 13a - child tax credit + + '13b': new ComputedLine((tr: TaxReturn): number => { + // TODO: add Sched 3.L7 return 0; + }), + + '14': new ComputedLine((tr: TaxReturn): number => { + const l12b = this.getValue(tr, '12b'); + const l13b = this.getValue(tr, '13b'); + const value = l12b - l13b; + return value < 0 ? 0 : value; + }), + + '15': new ReferenceLine('Schedule 2', '10', undefined, 0), + + '16': new ComputedLine((tr: TaxReturn): number => { + return this.getValue(tr, '14') + this.getValue(tr, '15'); }, 'Total tax'), '17': new ComputedLine((tr: TaxReturn): number => { @@ -102,4 +170,5 @@ export default class Form1040 extends Form { return 0; }, 'Amount you owe'), }; + }; -- 2.22.5