Make core/TaxReturn an abstract class.
authorRobert Sesek <rsesek@bluestatic.org>
Fri, 13 Mar 2020 04:32:37 +0000 (00:32 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Fri, 13 Mar 2020 04:34:03 +0000 (00:34 -0400)
A tax year model will provide the concrete implementation.

14 files changed:
src/core/Form.test.ts
src/core/Line.test.ts
src/core/TaxReturn.test.ts
src/core/TaxReturn.ts
src/fed2019/Form1040.test.ts
src/fed2019/Form1116.test.ts
src/fed2019/Form8606.test.ts
src/fed2019/Form8949.test.ts
src/fed2019/Form8959.test.ts
src/fed2019/Form8960.test.ts
src/fed2019/FormW2.test.ts
src/fed2019/Schedule1.test.ts
src/fed2019/Schedule3.test.ts
src/fed2019/TaxReturn.ts [new file with mode: 0644]

index 309c302b4fb8e6378506b9c9dffc1f31205dc8af..9aebe244f65dfa4c508b86fd2bbe5fce8cdd4ff9 100644 (file)
@@ -3,6 +3,11 @@ import TaxReturn from './TaxReturn';
 import Form, { isFormT } from './Form';
 import { InconsistencyError, NotFoundError } from './Errors';
 
+class TestTaxReturn extends TaxReturn {
+  get year() { return 2019; }
+  get includeJointPersonForms() { return true; }
+};
+
 test('add and get line', () => {
   const l = new ComputedLine<number>(() => 42);
 
@@ -55,7 +60,7 @@ test('get value', () => {
   };
 
   const f = new TestForm();
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   expect(f.getValue(tr, 'line')).toBe(42);
 
   //TYPEERROR:
index ababbb777f63f7420dcae90c8009144c8027cfb1..65221f8b559cf8e2f33ff939ac97f6b74dff4010 100644 (file)
@@ -3,6 +3,11 @@ import Form, { FormClass } from './Form';
 import TaxReturn from './TaxReturn';
 import { NotFoundError } from './Errors';
 
+class TestTaxReturn extends TaxReturn {
+  get year() { return 2019; }
+  get includeJointPersonForms() { return false; }
+};
+
 class ConstantLine<T> extends Line<T> {
   private _k: T;
 
@@ -17,7 +22,7 @@ class ConstantLine<T> extends Line<T> {
 };
 
 test('computed line', () => {
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const l = new ComputedLine<number>(
     (taxReturn: TaxReturn): number => {
       expect(taxReturn).toBe(tr);
@@ -37,7 +42,7 @@ test('reference line', () => {
     };
   };
 
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   tr.addForm(new TestForm());
 
   const l1 = new ReferenceLine(TestForm, '6b');
@@ -71,7 +76,7 @@ test('self reference line', () => {
     };
   };
 
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const f = new TestForm();
   tr.addForm(f);
   tr.addForm(new OtherForm());
@@ -95,7 +100,7 @@ test('input line', () => {
       '3': new InputLine<Input>('key2', undefined, 'FALLBACK')
     };
   };
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const f = new TestForm({ 'key': 'value' });
   tr.addForm(f);
 
@@ -125,7 +130,7 @@ test('line stack', () => {
     };
   };
 
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   tr.addForm(new FormZ({ 'input': 100 }));
   tr.addForm(new FormZ2());
 
@@ -142,7 +147,7 @@ test('accumulator line', () => {
     };
   };
 
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   tr.addForm(new TestForm());
   tr.addForm(new TestForm());
   tr.addForm(new TestForm());
index 9a5b252e6e8f52b0dda848c3b6fdcf0a9e81e854..21ca625165d89941963b40d3b711eb8f0fe80ae7 100644 (file)
@@ -3,19 +3,20 @@ import Person from './Person';
 import Form from './Form';
 import { NotFoundError, InconsistencyError } from './Errors';
 
-test('constructor', () => {
-  const tr = new TaxReturn(2019);
-  expect(tr.year).toBe(2019);
-});
+class TestTaxReturn extends TaxReturn {
+  get year() { return 2019; }
+
+  includeJointPersonForms = false;
+};
 
 test('does not support Dependents', () => {
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const p = Person.dependent('Baby');
   expect(() => tr.addPerson(p)).toThrow('Dependents are not supported');
 });
 
 test('add more than one Self', () => {
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const p1 = Person.self('A');
   tr.addPerson(p1);
   const p2 = Person.self('B');
@@ -23,7 +24,7 @@ test('add more than one Self', () => {
 });
 
 test('add more than one Spouse', () => {
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const p1 = Person.spouse('A');
   tr.addPerson(p1);
   const p2 = Person.spouse('B');
@@ -31,7 +32,7 @@ test('add more than one Spouse', () => {
 });
 
 test('add Self and Spouse', () => {
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const self = Person.self('Billy Bob');
   const spouse = Person.spouse('Jilly Bob');
   tr.addPerson(self);
@@ -44,7 +45,7 @@ test('add Self and Spouse', () => {
 });
 
 test('get non-existent person', () => {
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const self = Person.self('Billy Bob');
   tr.addPerson(self);
 
@@ -58,7 +59,7 @@ test('single-copy forms', () => {
     protected readonly _lines = null;
   };
 
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const f = new TestForm();
   tr.addForm(f);
   expect(() => tr.addForm(new TestForm)).toThrow(InconsistencyError);
@@ -73,7 +74,7 @@ test('multiple-copy forms', () => {
     protected readonly _lines = null;
   };
 
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   const f1 = new TestForm();
   const f2 = new TestForm();
   const f3 = new TestForm();
@@ -95,7 +96,7 @@ test('get non-existent form', () => {
     readonly name = 'Test Form';
     protected readonly _lines = null;
   }
-  const tr = new TaxReturn(2019);
+  const tr = new TestTaxReturn();
   expect(() => tr.getForm(TestForm)).toThrow(NotFoundError);
   expect(tr.findForm(TestForm)).toBeNull();
   expect(tr.findForms(TestForm)).toEqual([]);
index 3320756f6885045062ba148b5fdc6ac9acfa3c9a..359acab3f5921da647c62a0826f0b196ae0d3ce3 100644 (file)
@@ -2,19 +2,13 @@ import Form, { FormClass, isFormT } from './Form';
 import Person, { Relation } from './Person';
 import { NotFoundError, InconsistencyError, UnsupportedFeatureError } from './Errors';
 
-export default class TaxReturn {
-  private _year: number;
-
+export default abstract class TaxReturn {
   private _people: Person[] = [];
   private _forms: Form<any, unknown>[] = [];
 
-  constructor(year: number) {
-    this._year = year;
-  }
+  abstract get year(): number;
 
-  get year(): number {
-    return this._year;
-  }
+  abstract get includeJointPersonForms(): boolean;
 
   get forms(): Form<any, unknown>[] {
     return [...this._forms];
index d8d567ffe5d943ab722ce9abe080c814e318eaf0..12f1ba9d560a373e54c551350e5ec69e4a6c5c00 100644 (file)
@@ -1,4 +1,4 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 import { NotFoundError } from '../core/Errors';
 
 import Form1040, { FilingStatus } from './Form1040';
@@ -11,12 +11,13 @@ import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
 import Form8606 from './Form8606';
 import Form8959 from './Form8959';
 import Form8949 from './Form8949';
+import TaxReturn from './TaxReturn';
 import W2 from './W2';
 
 test('w2 wages', () => {
   const pa = Person.self('A');
   const pb = Person.spouse('B');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new W2({
     employer: 'AA',
     employee: pa,
@@ -40,7 +41,7 @@ test('w2 wages', () => {
 
 test('interest income', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new Form1099INT({
     payer: 'Bank',
     payee: p,
@@ -63,7 +64,7 @@ test('interest income', () => {
 
 test('dividend income', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   const f1099div = new Form1099DIV({
     payer: 'Brokerage',
     payee: p,
@@ -83,7 +84,7 @@ test('dividend income', () => {
 
 test('capital gain/loss', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new Form1040({ filingStatus: FilingStatus.Single }));
   tr.addForm(new W2({
     employer: 'Money',
@@ -108,7 +109,7 @@ test('capital gain/loss', () => {
 
 test('require Form8959', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new W2({
     employer: 'Company',
     employee: p,
@@ -128,7 +129,7 @@ test('require Form8959', () => {
 
 test('backdoor and megabackdoor roth', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new Form1099R({
     payer: 'Roth',
     payee: p,
index 2619503269fcf04564f07aea636dabc4d1227d45..add04cc18db61edb9bdaefe2b065cf3e77423bf9 100644 (file)
@@ -1,4 +1,4 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 import { UnsupportedFeatureError } from '../core/Errors';
 
 import Form1040, { FilingStatus } from './Form1040';
@@ -7,11 +7,12 @@ import Form1099B, { GainType } from './Form1099B';
 import Form1099DIV from './Form1099DIV';
 import Form8949 from './Form8949';
 import W2 from './W2';
+import TaxReturn from './TaxReturn';
 import ScheduleD from './ScheduleD';
 
 test('supported income category', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   const f = new Form1116({
     person: p,
     incomeCategory: ForeignIncomeCategory.C,
@@ -29,7 +30,7 @@ test('unsupported income categories', () => {
       continue;
 
     const p = Person.self('B');
-    const tr = new TaxReturn(2019);
+    const tr = new TaxReturn();
     const f = new Form1116({
       person: p,
       incomeCategory: category,
@@ -44,7 +45,7 @@ test('unsupported income categories', () => {
 
 test('foreign tax credit', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new Form1040({
     filingStatus: FilingStatus.MarriedFilingJoint
   }));
@@ -84,7 +85,7 @@ test('foreign tax credit', () => {
 
 test('no net capital losses in total income', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new Form1040({
     filingStatus: FilingStatus.MarriedFilingJoint
   }));
index b7bd8a2a00c20ffbb532cf3cec3c32dc2b2f304c..643c8e4ebaa3b8da14bac386ef1d77c36b99f8b2 100644 (file)
@@ -1,11 +1,12 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 
 import Form1040, { FilingStatus } from './Form1040';
 import Form8606 from './Form8606';
+import TaxReturn from './TaxReturn';
 
 test('skip part 1', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   const f = new Form8606({
     person: p,
     nondeductibleContributions: 6000,
@@ -19,7 +20,7 @@ test('skip part 1', () => {
 
 test('Roth conversion no basis', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   const f = new Form8606({
     person: p,
     nondeductibleContributions: 6000,
index 1fb5e556bef9cec3510212465eb59cf18e08f75d..471918185aab1c63e973a79b4f8dd24b2c8e748d 100644 (file)
@@ -1,14 +1,15 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 
 import Form1040, { FilingStatus } from './Form1040';
 import Form1099B, { GainType } from './Form1099B';
 import Form8949, { Form8949Box, Form8949Total } from './Form8949';
+import TaxReturn from './TaxReturn';
 
 describe('single form', () => {
   for (const box of [Form8949Box.A, Form8949Box.B, Form8949Box.D, Form8949Box.E]) {
     test(`box ${Form8949Box[box]}`, () => {
       const p = Person.self('A');
-      const tr = new TaxReturn(2019);
+      const tr = new TaxReturn();
       tr.addForm(new Form1040({ filingStatus: FilingStatus.Single }));
       tr.addForm(new Form1099B({
         payer: 'Brokerage',
@@ -47,7 +48,7 @@ describe('single form', () => {
 
 test('multiple forms', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new Form1040({ filingStatus: FilingStatus.Single }));
   tr.addForm(new Form1099B({
     payer: 'Brokerage',
@@ -104,7 +105,7 @@ test('multiple forms', () => {
 
 test('adjustments', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new Form1040({ filingStatus: FilingStatus.Single }));
   const b1 = new Form1099B({
     payer: 'Brokerage',
index 684e7cf2e6abafe058f3f019ead920a00cc05c73..890311b13662289022bb1d62eed66e10065f9d76 100644 (file)
@@ -1,9 +1,10 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 
 import W2 from './W2';
 import Form8959 from './Form8959';
 import Form1040, { FilingStatus } from './Form1040';
 import Schedule2 from './Schedule2';
+import TaxReturn from './TaxReturn';
 
 describe('additional medicare tax', () => {
   const filingStatusToResults = {
@@ -21,7 +22,7 @@ describe('additional medicare tax', () => {
   for (const filingStatus of Object.values(FilingStatus)) {
     test(`filing status ${filingStatus}`, () => {
       const p = Person.self('A');
-      const tr = new TaxReturn(2019);
+      const tr = new TaxReturn();
       tr.addForm(new Form1040({ filingStatus }));
       tr.addForm(new W2({
         employer: 'Acme',
@@ -56,7 +57,7 @@ describe('no additional medicare tax', () => {
   for (const filingStatus of Object.values(FilingStatus)) {
     test(`filing status ${filingStatus}`, () => {
       const p = Person.self('A');
-      const tr = new TaxReturn(2019);
+      const tr = new TaxReturn();
       tr.addForm(new Form1040({ filingStatus }));
       tr.addForm(new W2({
         employer: 'Acme',
index 753af6b038c4bf501f300134ff220d3b0dfccbf7..093ae2ab5cd5a44adfeb908e69d0608ea4eb4905 100644 (file)
@@ -1,4 +1,4 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 
 import W2 from './W2';
 import Form1040, { FilingStatus } from './Form1040';
@@ -10,6 +10,7 @@ import Form8959 from './Form8959';
 import Form8960 from './Form8960';
 import Schedule2 from './Schedule2';
 import ScheduleD from './ScheduleD';
+import TaxReturn from './TaxReturn';
 
 describe('net investment income tax', () => {
   const filingStatusToResult = {
@@ -21,7 +22,7 @@ describe('net investment income tax', () => {
   for (const filingStatus of Object.values(FilingStatus)) {
     test(`filing status ${filingStatus}`, () => {
       const p = Person.self('A');
-      const tr = new TaxReturn(2019);
+      const tr = new TaxReturn();
       tr.addForm(new Form1040({ filingStatus }));
       tr.addForm(new Form1099DIV({
         payer: 'Brokerage',
@@ -81,7 +82,7 @@ describe('no net investment income tax', () => {
   for (const filingStatus of Object.values(FilingStatus)) {
     test(`filing status ${filingStatus}`, () => {
       const p = Person.self('A');
-      const tr = new TaxReturn(2019);
+      const tr = new TaxReturn();
       tr.addForm(new Form1040({ filingStatus }));
       tr.addForm(new Form1099DIV({
         payer: 'Brokerage',
index 9505f61eedbd3b38adb99ea21ad5e2d83d54a512..603c12929630cf61bb7c4315c360f918f9a609e6 100644 (file)
@@ -1,11 +1,12 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 
+import TaxReturn from './TaxReturn';
 import W2 from './W2';
 
 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);
+  const tr = new TaxReturn();
   tr.addForm(w2);
   expect(w2.getValue(tr, 'c')).toBe('Acme');
   expect(w2.getValue(tr, 'e')).toBe(p);
index e99e72c02b7bfa85022c9d04d81bea8d649c4996..290a8e1652e8cda9a178c2e9dde34bc8b1ceb846 100644 (file)
@@ -1,12 +1,13 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 import { UnsupportedFeatureError } from '../core/Errors';
 
 import Form1040, { FilingStatus } from './Form1040';
 import Schedule1, { Schedule1Input } from './Schedule1';
+import TaxReturn from './TaxReturn';
 
 test('state tax refund', () => {
   const p = Person.self('A');
-  const tr = new TaxReturn(2019);
+  const tr = new TaxReturn();
   tr.addForm(new Form1040({
     filingStatus: FilingStatus.Single
   }));
@@ -33,7 +34,7 @@ test('unsupported inputs', () => {
   ];
   for (const input of keys) {
     const p = Person.self('A');
-    const tr = new TaxReturn(2019);
+    const tr = new TaxReturn();
     const f = new Schedule1({
       [input]: 100
     });
index e0b5efc95aeb2207bbeba1453b67579541c66087..1e7a0baaf69cc889ba73655163f47a2a96e35362 100644 (file)
@@ -1,4 +1,4 @@
-import { Person, TaxReturn } from '../core';
+import { Person } from '../core';
 import { NotFoundError } from '../core/Errors';
 
 import Form1040, { FilingStatus } from './Form1040';
@@ -7,6 +7,7 @@ import Form1116 from './Form1116';
 import Form8949 from './Form8949';
 import Schedule3 from './Schedule3';
 import ScheduleD from './ScheduleD';
+import TaxReturn from './TaxReturn';
 
 test('foreign tax credit, form 1116 not required', () => {
   const filingStatusToForeignTax = {
@@ -17,7 +18,7 @@ test('foreign tax credit, form 1116 not required', () => {
 
   for (const filingStatus of Object.values(FilingStatus)) {
     const p = Person.self('A');
-    const tr = new TaxReturn(2019);
+    const tr = new TaxReturn();
     tr.addForm(new Form1040({ filingStatus }));
     tr.addForm(new Form8949);
     tr.addForm(new ScheduleD);
@@ -45,7 +46,7 @@ test('foreign tax credit, form 1116 required', () => {
 
   for (const filingStatus of Object.values(FilingStatus)) {
     const p = Person.self('A');
-    const tr = new TaxReturn(2019);
+    const tr = new TaxReturn();
     tr.addForm(new Form1040({ filingStatus }));
     tr.addForm(new Form8949);
     tr.addForm(new ScheduleD);
diff --git a/src/fed2019/TaxReturn.ts b/src/fed2019/TaxReturn.ts
new file mode 100644 (file)
index 0000000..9a7892f
--- /dev/null
@@ -0,0 +1,11 @@
+import { TaxReturn as BaseTaxReturn } from '../core';
+
+import Form1040, { FilingStatus } from './Form1040';
+
+export default class TaxReturn extends BaseTaxReturn {
+  get year() { return 2019; }
+
+  get includeJointPersonForms() {
+    return this.getForm(Form1040).getInput('filingStatus') == FilingStatus.MarriedFilingJoint;
+  }
+};