From e992ba64b270a5f51532672417886e5b9ea2b0ea Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Fri, 21 Feb 2020 13:28:07 -0500 Subject: [PATCH] Remove id from Line constructor. Instead, have the Form set it via its key in _lines. --- src/Form.test.ts | 4 ++-- src/Form.ts | 14 +++++++++----- src/Line.test.ts | 31 ++++++++++++++++--------------- src/Line.ts | 23 +++++++++++------------ src/TaxReturn.ts | 1 + src/fed2019/Form1040.ts | 38 +++++++++++++++++++------------------- src/fed2019/FormW2.test.ts | 1 + src/fed2019/FormW2.ts | 30 +++++++++++++++--------------- 8 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/Form.test.ts b/src/Form.test.ts index 5c2d732..122ca5d 100644 --- a/src/Form.test.ts +++ b/src/Form.test.ts @@ -4,7 +4,7 @@ import Form from './Form'; import { InconsistencyError, NotFoundError } from './Errors'; test('add and get line', () => { - const l = new ComputedLine('1', () => 42); + const l = new ComputedLine(() => 42); class TestForm extends Form { readonly name = 'Test Form'; @@ -50,7 +50,7 @@ test('get value', () => { readonly name = 'Form'; protected readonly _lines = { - line: new ComputedLine('line', () => 42), + line: new ComputedLine(() => 42), }; }; diff --git a/src/Form.ts b/src/Form.ts index f68d975..f438337 100644 --- a/src/Form.ts +++ b/src/Form.ts @@ -16,14 +16,18 @@ export default abstract class Form }, this._input = input; } + init() { + for (const id in this._lines) { + let l = this._lines[id]; + l._id = id; + l.form = this; + } + } + getLine(id: K): L[K] { if (!(id in this._lines)) throw new NotFoundError(`Form ${this.name} does not have line ${id}`); - const line = this._lines[id]; - // We cannot apply this to every line in the constructor because |_lines| - // is abstract, so do it on getLine(). - line.form = this; - return line; + return this._lines[id]; } getValue(tr: TaxReturn, id: K): ReturnType { diff --git a/src/Line.test.ts b/src/Line.test.ts index 20e4d96..7a2c531 100644 --- a/src/Line.test.ts +++ b/src/Line.test.ts @@ -6,8 +6,8 @@ import { NotFoundError } from './Errors'; class ConstantLine extends Line { private _k: T; - constructor(id: string, k: T) { - super(id, `Constant ${k}`); + constructor(k: T) { + super(`Constant ${k}`); this._k = k; } @@ -18,7 +18,7 @@ class ConstantLine extends Line { test('computed line', () => { const tr = new TaxReturn(2019); - const l = new ComputedLine('A', + const l = new ComputedLine( (taxReturn: TaxReturn, line: ComputedLine): number => { expect(taxReturn).toBe(tr); expect(line).toBe(l); @@ -26,7 +26,6 @@ test('computed line', () => { }, 'Computed Line A'); expect(l.value(tr)).toBe(42); - expect(l.id).toBe('A'); expect(l.description).toBe('Computed Line A'); }); @@ -34,20 +33,20 @@ test('reference line', () => { class TestForm extends Form { readonly name = 'Form 1'; protected readonly _lines = { - '6b': new ConstantLine('6b', 12.34) + '6b': new ConstantLine(12.34) }; }; const tr = new TaxReturn(2019); tr.addForm(new TestForm()); - const l1 = new ReferenceLine('C', 'Form 1', '6b'); + const l1 = new ReferenceLine('Form 1', '6b'); expect(l1.value(tr)).toBe(12.34); - const l2 = new ReferenceLine('x', 'Form 2', '6b'); + const l2 = new ReferenceLine('Form 2', '6b'); expect(() => l2.value(tr)).toThrow(NotFoundError); - const l3 = new ReferenceLine('y', 'Form 1', '7a'); + const l3 = new ReferenceLine('Form 1', '7a'); expect(() => l3.value(tr)).toThrow(NotFoundError); }); @@ -59,14 +58,16 @@ test('input line', () => { class TestForm extends Form { readonly name = 'F1'; protected readonly _lines = { - '1': new InputLine('1', 'key'), - '2': new InputLine('2', 'key2') + '1': new InputLine('key'), + '2': new InputLine('key2') }; }; const tr = new TaxReturn(2019); const f = new TestForm({ 'key': 'value' }); + tr.addForm(f); expect(f.getLine('1').value(tr)).toBe('value'); + expect(f.getLine('1').id).toBe('1'); const l2 = f.getLine('2'); expect(() => l2.value(tr)).toThrow(NotFoundError); @@ -76,14 +77,14 @@ test('line stack', () => { class FormZ extends Form { readonly name = 'Z'; protected readonly _lines = { - '3': new InputLine('3', 'input') + '3': new InputLine('input') } }; class FormZ2 extends Form { readonly name = 'Z-2'; protected readonly _lines = { - '2c': new ComputedLine('2c', (tr: TaxReturn, l: Line): any => { + '2c': new ComputedLine((tr: TaxReturn, l: Line): any => { return tr.getForm('Z').getLine('3').value(tr) * 0.2; }) }; @@ -93,7 +94,7 @@ test('line stack', () => { tr.addForm(new FormZ({ 'input': 100 })); tr.addForm(new FormZ2()); - const l = new ReferenceLine('32', 'Z-2', '2c'); + const l = new ReferenceLine('Z-2', '2c'); expect(l.value(tr)).toBe(20); }); @@ -102,7 +103,7 @@ test('accumulator line', () => { readonly name = 'Form B'; readonly supportsMultipleCopies = true; protected readonly _lines = { - g: new ConstantLine('g', 100.25) + g: new ConstantLine(100.25) }; }; @@ -111,6 +112,6 @@ test('accumulator line', () => { tr.addForm(new TestForm()); tr.addForm(new TestForm()); - const l = new AccumulatorLine('line', 'Form B', 'g'); + const l = new AccumulatorLine('Form B', 'g'); expect(l.value(tr)).toBe(300.75); }); diff --git a/src/Line.ts b/src/Line.ts index 1e6543c..31c82fa 100644 --- a/src/Line.ts +++ b/src/Line.ts @@ -2,13 +2,12 @@ import TaxReturn from './TaxReturn'; import Form from './Form'; export abstract class Line { - private _id: string; private _description?: string; - form: Form; + _id: string; // _id is set by Form.init(). + form: Form; // Set by Form.init(); - constructor(id: string, description?: string) { - this._id = id; + constructor(description?: string) { this._description = description; } @@ -28,8 +27,8 @@ type ComputeFunc = (tr: TaxReturn, l: ComputedLine) => T; export class ComputedLine extends Line { private _compute: ComputeFunc; - constructor(id: string, compute: ComputeFunc, description?: string) { - super(id, description); + constructor(compute: ComputeFunc, description?: string) { + super(description); this._compute = compute; } @@ -42,8 +41,8 @@ export class ReferenceLine extends Line { private _form: string; private _line: string; - constructor(id: string, form: string, line: string, description?: string) { - super(id, description || `Reference F${form}.L${line}`); + constructor(form: string, line: string, description?: string) { + super(description || `Reference F${form}.L${line}`); this._form = form; this._line = line; } @@ -58,8 +57,8 @@ export class InputLine extends Line form: Form; - constructor(id: string, input: T, description?: string) { - super(id, description); + constructor(input: T, description?: string) { + super(description); this._input = input; } @@ -72,8 +71,8 @@ export class AccumulatorLine extends Line { private _form: string; private _line: string; - constructor(id: string, form: string, line: string, description?: string) { - super(id, description || `Accumulator F${form}.L${line}`); + constructor(form: string, line: string, description?: string) { + super(description || `Accumulator F${form}.L${line}`); this._form = form; this._line = line; } diff --git a/src/TaxReturn.ts b/src/TaxReturn.ts index 375c629..c8f6ca6 100644 --- a/src/TaxReturn.ts +++ b/src/TaxReturn.ts @@ -44,6 +44,7 @@ export default class TaxReturn { throw new InconsistencyError(`Cannot have more than one type of form ${form.name}`); } } + form.init(); this._forms.push(form); } diff --git a/src/fed2019/Form1040.ts b/src/fed2019/Form1040.ts index 8f9c87a..83bcc4d 100644 --- a/src/fed2019/Form1040.ts +++ b/src/fed2019/Form1040.ts @@ -18,33 +18,33 @@ export default class Form1040 extends Form { readonly name = '1040'; protected readonly _lines = { - '1': new AccumulatorLine('1', 'W-2', '1', 'Wages, salaries, tips, etc.'), - '2a': new AccumulatorLine('2a', '1099-INT', '8', 'Tax-exempt interest'), - '2b': new AccumulatorLine('2b', '1009-INT', '1', 'Taxable interest'), - '3a': new AccumulatorLine('3a', '1099-DIV', '1b', 'Qualified dividends'), - '3b': new AccumulatorLine('3b', '1099-DIV', '1a', 'Ordinary dividends'), + '1': new AccumulatorLine('W-2', '1', 'Wages, salaries, tips, etc.'), + '2a': new AccumulatorLine('1099-INT', '8', 'Tax-exempt interest'), + '2b': new AccumulatorLine('1009-INT', '1', 'Taxable interest'), + '3a': new AccumulatorLine('1099-DIV', '1b', 'Qualified dividends'), + '3b': new AccumulatorLine('1099-DIV', '1a', 'Ordinary dividends'), // 4a and 4b are complex // 4c and 4d are not supported // 5a and 5b are not supported // 6 - Sched D - '7a': new ReferenceLine('7a', 'Schedule 1', '9', 'Other income from Schedule 1'), + '7a': new ReferenceLine('Schedule 1', '9', 'Other income from Schedule 1'), - '7b': new ComputedLine('7b', (tr: TaxReturn): number => { + '7b': new ComputedLine((tr: TaxReturn): number => { 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('8a', 'Schedule 1', '22', 'Adjustments to income'), + '8a': new ReferenceLine('Schedule 1', '22', 'Adjustments to income'), - '8b': new ComputedLine('8b', (tr: TaxReturn): number => { + '8b': new ComputedLine((tr: TaxReturn): number => { return this.getValue(tr, '7b') - this.getValue(tr, '8a'); }, 'Adjusted gross income'), // TODO - Deduction - '9': new ComputedLine('9', () => 0, 'Deduction'), + '9': new ComputedLine(() => 0, 'Deduction'), - '10': new ComputedLine('10', (tr: TaxReturn): number => { + '10': new ComputedLine((tr: TaxReturn): number => { const taxableIncome = this.getValue(tr, '8b'); let use8995a = false; switch (this.getInput('filingStatus')) { @@ -55,23 +55,23 @@ export default class Form1040 extends Form { return 0; }, 'Qualified business income deduction'), - '11a': new ComputedLine('11a', (tr: TaxReturn): number => { + '11a': new ComputedLine((tr: TaxReturn): number => { return this.getValue(tr, '9') + this.getValue(tr, '10'); }), - '11b': new ComputedLine('11b', (tr: TaxReturn): number => { + '11b': new ComputedLine((tr: TaxReturn): number => { const value = this.getValue(tr, '8b') - this.getValue(tr, '11a'); return value < 0 ? 0 : value; }, 'Taxable income'), - '16': new ComputedLine('16', (tr: TaxReturn): number => { + '16': new ComputedLine((tr: TaxReturn): number => { return 0; }, 'Total tax'), - '17': new ComputedLine('17', (tr: TaxReturn): number => { + '17': new ComputedLine((tr: TaxReturn): number => { const fedTaxWithheldBoxes = [ ['W-2', '2'], ['1099-R', '4'], ['1099-DIV', '4'], ['1099-INT', '4'] ]; - const withholding = fedTaxWithheldBoxes.map(b => (new AccumulatorLine('F1040.L17+', b[0], b[1])).value(tr)); + const withholding = fedTaxWithheldBoxes.map(b => (new AccumulatorLine(b[0], b[1])).value(tr)); let additionalMedicare = 0; const f8959 = tr.maybeGetForm('8595') @@ -84,9 +84,9 @@ export default class Form1040 extends Form { // 18 not supported - '19': new ReferenceLine('19', '1040', '17', 'Total payments'), + '19': new ReferenceLine('1040', '17', 'Total payments'), - '20': new ComputedLine('20', (tr: TaxReturn): number => { + '20': new ComputedLine((tr: TaxReturn): number => { const l16 = this.getValue(tr, '16'); const l19 = this.getValue(tr, '19'); if (l19 > l16) @@ -94,7 +94,7 @@ export default class Form1040 extends Form { return 0; }, 'Amount overpaid'), - '23': new ComputedLine('23', (tr: TaxReturn): number => { + '23': new ComputedLine((tr: TaxReturn): number => { const l16 = this.getValue(tr, '16'); const l19 = this.getValue(tr, '19'); if (l19 < l16) diff --git a/src/fed2019/FormW2.test.ts b/src/fed2019/FormW2.test.ts index 8e3d719..5b02b26 100644 --- a/src/fed2019/FormW2.test.ts +++ b/src/fed2019/FormW2.test.ts @@ -6,6 +6,7 @@ test('input', () => { const p = Person.self('Bob'); const w2 = new W2({ employer: 'Acme', employee: p, wages: 1000, fedIncomeTax: 100.40 }); const tr = new TaxReturn(2019); + tr.addForm(w2); expect(w2.getValue(tr, 'c')).toBe('Acme'); expect(w2.getValue(tr, 'e')).toBe(p); expect(w2.getValue(tr, '1')).toBe(1000); diff --git a/src/fed2019/FormW2.ts b/src/fed2019/FormW2.ts index 5a1cb15..b9558e4 100644 --- a/src/fed2019/FormW2.ts +++ b/src/fed2019/FormW2.ts @@ -39,20 +39,20 @@ export default class W2 extends Form { readonly supportsMultipleCopies = true; protected readonly _lines = { - 'c': new Input('c', 'employer', 'Employer name'), - 'e': new Input('e', 'employee', 'Emplyee name'), - '1': new Input('1', 'wages', 'Wages, tips, other compensation'), - '2': new Input('2', 'fedIncomeTax', 'Federal income tax withheld'), - '3': new Input('3', 'socialSecurityWages', 'Social security wages'), - '4': new Input('4', 'socialSecuirtyTax', 'Social security tax withheld'), - '5': new Input('5', 'medicareWages', 'Medicare wages and tips'), - '6': new Input('6', 'medicareTax', 'Medicare tax withheld'), - '7': new Input('7', 'socialSecurityTips', 'Social security tips'), - '8': new Input('8', 'allocatedTips', 'Allocated tips'), - '10': new Input('10', 'dependentCareBenefits', 'Dependent care benefits'), - '11': new Input('11', 'nonqualifiedPlans','Nonqualified plans'), - '12': new Input('12', 'box12', 'Box 12'), - '13': new Input('13', 'box13', 'Box 13'), - '14': new Input('14', 'box14', 'Other'), + 'c': new Input('employer', 'Employer name'), + 'e': new Input('employee', 'Emplyee name'), + '1': new Input('wages', 'Wages, tips, other compensation'), + '2': new Input('fedIncomeTax', 'Federal income tax withheld'), + '3': new Input('socialSecurityWages', 'Social security wages'), + '4': new Input('socialSecuirtyTax', 'Social security tax withheld'), + '5': new Input('medicareWages', 'Medicare wages and tips'), + '6': new Input('medicareTax', 'Medicare tax withheld'), + '7': new Input('socialSecurityTips', 'Social security tips'), + '8': new Input('allocatedTips', 'Allocated tips'), + '10': new Input('dependentCareBenefits', 'Dependent care benefits'), + '11': new Input('nonqualifiedPlans','Nonqualified plans'), + '12': new Input('box12', 'Box 12'), + '13': new Input('box13', 'Box 13'), + '14': new Input('box14', 'Other'), }; }; -- 2.22.5