Remove id from Line constructor.
authorRobert Sesek <rsesek@bluestatic.org>
Fri, 21 Feb 2020 18:28:07 +0000 (13:28 -0500)
committerRobert Sesek <rsesek@bluestatic.org>
Fri, 21 Feb 2020 18:28:07 +0000 (13:28 -0500)
Instead, have the Form set it via its key in _lines.

src/Form.test.ts
src/Form.ts
src/Line.test.ts
src/Line.ts
src/TaxReturn.ts
src/fed2019/Form1040.ts
src/fed2019/FormW2.test.ts
src/fed2019/FormW2.ts

index 5c2d732f83a4af8ab11086dcb581c02c479b3c1c..122ca5d9b1b5d036e324f087536acc3ac1e9f730 100644 (file)
@@ -4,7 +4,7 @@ import Form from './Form';
 import { InconsistencyError, NotFoundError } from './Errors';
 
 test('add and get line', () => {
-  const l = new ComputedLine<number>('1', () => 42);
+  const l = new ComputedLine<number>(() => 42);
 
   class TestForm extends Form<TestForm['_lines']> {
     readonly name = 'Test Form';
@@ -50,7 +50,7 @@ test('get value', () => {
     readonly name = 'Form';
 
     protected readonly _lines = {
-      line: new ComputedLine<number>('line', () => 42),
+      line: new ComputedLine<number>(() => 42),
     };
   };
 
index f68d975ab17e9777255f12cbd713a20e8c1d0f28..f438337480b59bd803cdb88a6f3093b33428ea42 100644 (file)
@@ -16,14 +16,18 @@ export default abstract class Form<L extends { [key: string]: Line<any> },
     this._input = input;
   }
 
+  init() {
+    for (const id in this._lines) {
+      let l = this._lines[id];
+      l._id = id;
+      l.form = this;
+    }
+  }
+
   getLine<K extends keyof L>(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<K extends keyof L>(tr: TaxReturn, id: K): ReturnType<L[K]['value']> {
index 20e4d9648bff1c7f2b63a89a869ff54389798a4f..7a2c531ff153e648bfeb04bae762a2503f38c37d 100644 (file)
@@ -6,8 +6,8 @@ import { NotFoundError } from './Errors';
 class ConstantLine<T> extends Line<T> {
   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<T> extends Line<T> {
 
 test('computed line', () => {
   const tr = new TaxReturn(2019);
-  const l = new ComputedLine<number>('A',
+  const l = new ComputedLine<number>(
     (taxReturn: TaxReturn, line: ComputedLine<number>): 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<TestForm['_lines']> {
     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<number>('C', 'Form 1', '6b');
+  const l1 = new ReferenceLine<number>('Form 1', '6b');
   expect(l1.value(tr)).toBe(12.34);
 
-  const l2 = new ReferenceLine<number>('x', 'Form 2', '6b');
+  const l2 = new ReferenceLine<number>('Form 2', '6b');
   expect(() => l2.value(tr)).toThrow(NotFoundError);
 
-  const l3 = new ReferenceLine<number>('y', 'Form 1', '7a');
+  const l3 = new ReferenceLine<number>('Form 1', '7a');
   expect(() => l3.value(tr)).toThrow(NotFoundError);
 });
 
@@ -59,14 +58,16 @@ test('input line', () => {
   class TestForm extends Form<TestForm['_lines'], Input> {
     readonly name = 'F1';
     protected readonly _lines = {
-      '1': new InputLine<Input>('1', 'key'),
-      '2': new InputLine<Input>('2', 'key2')
+      '1': new InputLine<Input>('key'),
+      '2': new InputLine<Input>('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<FormZ['_lines'], {'input': number}> {
     readonly name = 'Z';
     protected readonly _lines = {
-      '3': new InputLine<any, any>('3', 'input')
+      '3': new InputLine<any, any>('input')
     }
   };
 
   class FormZ2 extends Form<FormZ2['_lines']> {
     readonly name = 'Z-2';
     protected readonly _lines = {
-      '2c': new ComputedLine<number>('2c', (tr: TaxReturn, l: Line<number>): any => {
+      '2c': new ComputedLine<number>((tr: TaxReturn, l: Line<number>): 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<number>('32', 'Z-2', '2c');
+  const l = new ReferenceLine<number>('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<number>('g', 100.25)
+      g: new ConstantLine<number>(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);
 });
index 1e6543ca444d88c1f88ab83ef6a8f8806a443bed..31c82fa90bcdc558e0e1dbef1cd5a6de67242e9c 100644 (file)
@@ -2,13 +2,12 @@ import TaxReturn from './TaxReturn';
 import Form from './Form';
 
 export abstract class Line<T> {
-  private _id: string;
   private _description?: string;
 
-  form: Form<any, any>;
+  _id: string;  // _id is set by Form.init().
+  form: Form<any, any>;  // Set by Form.init();
 
-  constructor(id: string, description?: string) {
-    this._id = id;
+  constructor(description?: string) {
     this._description = description;
   }
 
@@ -28,8 +27,8 @@ type ComputeFunc<T> = (tr: TaxReturn, l: ComputedLine<T>) => T;
 export class ComputedLine<T> extends Line<T> {
   private _compute: ComputeFunc<T>;
 
-  constructor(id: string, compute: ComputeFunc<T>, description?: string) {
-    super(id, description);
+  constructor(compute: ComputeFunc<T>, description?: string) {
+    super(description);
     this._compute = compute;
   }
 
@@ -42,8 +41,8 @@ export class ReferenceLine<T> extends Line<T> {
   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<U = unknown, T extends keyof U = any> extends Line<U[T]>
 
   form: Form<any, U>;
 
-  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<number> {
   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;
   }
index 375c629ce64eec1b62286f73ec857d78ba1b9167..c8f6ca6c3fa881b932f8fcfad56b34196eeba0e5 100644 (file)
@@ -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);
   }
 
index 8f9c87aaaa4695ca554ccbe8fdf8d32f6e833027..83bcc4d60014b0bcc5636a16be8ae538f16fafa3 100644 (file)
@@ -18,33 +18,33 @@ export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
   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<number>('7a', 'Schedule 1', '9', 'Other income from Schedule 1'),
+    '7a': new ReferenceLine<number>('Schedule 1', '9', 'Other income from Schedule 1'),
 
-    '7b': new ComputedLine<number>('7b', (tr: TaxReturn): number => {
+    '7b': new ComputedLine<number>((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<number>('8a', 'Schedule 1', '22', 'Adjustments to income'),
+    '8a': new ReferenceLine<number>('Schedule 1', '22', 'Adjustments to income'),
 
-    '8b': new ComputedLine<number>('8b', (tr: TaxReturn): number => {
+    '8b': new ComputedLine<number>((tr: TaxReturn): number => {
       return this.getValue(tr, '7b') - this.getValue(tr, '8a');
     }, 'Adjusted gross income'),
 
     // TODO - Deduction
-    '9': new ComputedLine<number>('9', () => 0, 'Deduction'),
+    '9': new ComputedLine<number>(() => 0, 'Deduction'),
 
-    '10': new ComputedLine<number>('10', (tr: TaxReturn): number => {
+    '10': new ComputedLine<number>((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<Form1040['_lines'], Form1040Input> {
       return 0;
     }, 'Qualified business income deduction'),
 
-    '11a': new ComputedLine<number>('11a', (tr: TaxReturn): number => {
+    '11a': new ComputedLine<number>((tr: TaxReturn): number => {
       return this.getValue(tr, '9') + this.getValue(tr, '10');
     }),
-    '11b': new ComputedLine<number>('11b', (tr: TaxReturn): number => {
+    '11b': new ComputedLine<number>((tr: TaxReturn): number => {
       const value = this.getValue(tr, '8b') - this.getValue(tr, '11a');
       return value < 0 ? 0 : value;
     }, 'Taxable income'),
 
-    '16': new ComputedLine<number>('16', (tr: TaxReturn): number => {
+    '16': new ComputedLine<number>((tr: TaxReturn): number => {
       return 0;
     }, 'Total tax'),
 
-    '17': new ComputedLine<number>('17', (tr: TaxReturn): number => {
+    '17': new ComputedLine<number>((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<Form1040['_lines'], Form1040Input> {
 
     // 18 not supported
 
-    '19': new ReferenceLine<number>('19', '1040', '17', 'Total payments'),
+    '19': new ReferenceLine<number>('1040', '17', 'Total payments'),
 
-    '20': new ComputedLine<number>('20', (tr: TaxReturn): number => {
+    '20': new ComputedLine<number>((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<Form1040['_lines'], Form1040Input> {
       return 0;
     }, 'Amount overpaid'),
 
-    '23': new ComputedLine<number>('23', (tr: TaxReturn): number => {
+    '23': new ComputedLine<number>((tr: TaxReturn): number => {
       const l16 = this.getValue(tr, '16');
       const l19 = this.getValue(tr, '19');
       if (l19 < l16)
index 8e3d719b4d1f3d8f0fd0f7b46b3593674b34eaf5..5b02b267e7c5526f6b23d2636599f2e7c79ef89e 100644 (file)
@@ -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);
index 5a1cb1551e679300156f4ce6f1b58cff486221d3..b9558e4baca4de0b42987a9d18240a5672f40fa9 100644 (file)
@@ -39,20 +39,20 @@ export default class W2 extends Form<W2['_lines'], W2Input> {
   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'),
   };
 };