Instead, have the Form set it via its key in _lines.
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';
readonly name = 'Form';
protected readonly _lines = {
- line: new ComputedLine<number>('line', () => 42),
+ line: new ComputedLine<number>(() => 42),
};
};
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']> {
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;
}
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);
},
'Computed Line A');
expect(l.value(tr)).toBe(42);
- expect(l.id).toBe('A');
expect(l.description).toBe('Computed Line A');
});
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);
});
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);
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;
})
};
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);
});
readonly name = 'Form B';
readonly supportsMultipleCopies = true;
protected readonly _lines = {
- g: new ConstantLine<number>('g', 100.25)
+ g: new ConstantLine<number>(100.25)
};
};
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);
});
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;
}
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;
}
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;
}
form: Form<any, U>;
- constructor(id: string, input: T, description?: string) {
- super(id, description);
+ constructor(input: T, description?: string) {
+ super(description);
this._input = input;
}
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;
}
throw new InconsistencyError(`Cannot have more than one type of form ${form.name}`);
}
}
+ form.init();
this._forms.push(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<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')) {
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')
// 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)
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)
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);
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'),
};
};