Add Schedule 1.
authorRobert Sesek <rsesek@bluestatic.org>
Sun, 8 Mar 2020 14:25:50 +0000 (10:25 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Sun, 8 Mar 2020 14:25:50 +0000 (10:25 -0400)
src/Math.test.ts
src/Math.ts
src/fed2019/Form1040.ts
src/fed2019/Schedule1.test.ts [new file with mode: 0644]
src/fed2019/Schedule1.ts [new file with mode: 0644]

index e6197c5ddcb62e1508ca5c92f5b9d8476d2470ae..b339b8932e3435d4aa2e43178c4a256b75ed4ca7 100644 (file)
@@ -1,7 +1,12 @@
-import { clampToZero } from './Math';
+import { clampToZero, undefinedToZero } from './Math';
 
 test('clamp to zero', () => {
   expect(clampToZero(100)).toBe(100);
   expect(clampToZero(-100)).toBe(0);
   expect(clampToZero(0)).toBe(0);
 });
+
+test('undefiend to zero', () => {
+  expect(undefinedToZero(undefined)).toBe(0);
+  expect(undefinedToZero(100)).toBe(100);
+});
index 613e5eb9b2c117d38656b9e1fc568a5a8c483173..90f76c6bd3488b0e859fdb29db288ae725137577 100644 (file)
@@ -1,5 +1,5 @@
-export function clampToZero(value: number): number {
-  return value < 0 ? 0 : value;
-}
+export const clampToZero = (value: number): number => value < 0 ? 0 : value;
+
+export const undefinedToZero = (value?: number): number => value === undefined ? 0 : value;
 
 export const reduceBySum = (list: number[]) => list.reduce((acc, curr) => acc + curr, 0);
index 8c5d829eacae75ce11f926064fc5b3948b178e54..c9c65265d34fb0c43bc4c3864fc8baae860a96cb 100644 (file)
@@ -10,6 +10,7 @@ import Form1099INT from './Form1099INT';
 import Form1099DIV from './Form1099DIV';
 import Form1099R, { Box7Code } from './Form1099R';
 import FormW2 from './FormW2';
+import Schedule1 from './Schedule1';
 import Schedule2 from './Schedule2';
 import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
 
@@ -53,7 +54,7 @@ export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
         return l6;
       return schedD.getValue(tr, '21');
     }, 'Capital gain/loss'),
-    '7a': new ReferenceLine(/*'Schedule 1'*/ undefined, '9', 'Other income from Schedule 1', 0),
+    '7a': new ReferenceLine(Schedule1, '9', 'Other income from Schedule 1', 0),
 
     '7b': new ComputedLine((tr): number => {
       let income = 0;
@@ -68,7 +69,7 @@ export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
       return income;
     }, 'Total income'),
 
-    '8a': new ReferenceLine(undefined /*'Schedule 1'*/, '22', 'Adjustments to income', 0),
+    '8a': new ReferenceLine(Schedule1, '22', 'Adjustments to income', 0),
 
     '8b': new ComputedLine((tr): number => {
       return this.getValue(tr, '7b') - this.getValue(tr, '8a');
diff --git a/src/fed2019/Schedule1.test.ts b/src/fed2019/Schedule1.test.ts
new file mode 100644 (file)
index 0000000..117ef92
--- /dev/null
@@ -0,0 +1,44 @@
+import TaxReturn from '../TaxReturn';
+import Person from '../Person';
+import { UnsupportedFeatureError } from '../Errors';
+
+import Form1040, { FilingStatus } from './Form1040';
+import Schedule1, { Schedule1Input } from './Schedule1';
+
+test('state tax refund', () => {
+  const p = Person.self('A');
+  const tr = new TaxReturn(2019);
+  tr.addForm(new Form1040({
+    filingStatus: FilingStatus.Single
+  }));
+  const f = new Schedule1({
+    stateAndLocalTaxableRefunds: 500
+  });
+  tr.addForm(f);
+
+  expect(f.getValue(tr, '9')).toBe(500);
+  expect(tr.getForm(Form1040).getValue(tr, '7a')).toBe(500);
+});
+
+test('unsupported inputs', () => {
+  const keys: (keyof Schedule1Input)[] = [
+    'businessIncome',
+    'otherGainsOrLosses',
+    'rentalRealEstateRoyaltiesPartnershipsSCorps',
+    'farmIncome',
+    'businessExpensesForm2106',
+    'hsaDeduction',
+    'armedForcesMovingExpenses',
+    'deductibleSelfEmploymentTax',
+    'tuitionAndFees',
+  ];
+  for (const input of keys) {
+    const p = Person.self('A');
+    const tr = new TaxReturn(2019);
+    const f = new Schedule1({
+      [input]: 100
+    });
+    tr.addForm(f);
+    expect(() => f.getValue(tr, '9') + f.getValue(tr, '22')).toThrow(UnsupportedFeatureError);
+  }
+});
diff --git a/src/fed2019/Schedule1.ts b/src/fed2019/Schedule1.ts
new file mode 100644 (file)
index 0000000..4d0bb0e
--- /dev/null
@@ -0,0 +1,134 @@
+import Form from '../Form';
+import TaxReturn from '../TaxReturn';
+import { ComputedLine, InputLine } from '../Line';
+import { NotFoundError, UnsupportedFeatureError } from '../Errors';
+import { undefinedToZero } from '../Math';
+
+import Form1040 from './Form1040';
+
+export interface Schedule1Input {
+  // Additional Income
+  stateAndLocalTaxableRefunds?: number;
+  alimonyReceived?: number;
+  businessIncome?: number;
+  otherGainsOrLosses?: number
+  rentalRealEstateRoyaltiesPartnershipsSCorps?: number;
+  farmIncome?: number;
+  unemploymentCompensation?: number;
+  otherIncome?: number;
+
+  // Adjustments
+  educatorExpenses?: number;
+  businessExpensesForm2106?: number;
+  hsaDeduction?: number;
+  armedForcesMovingExpenses?: number;
+  deductibleSelfEmploymentTax?: number;
+  selfEmployedSepSimpleQualifiedPlans?: number;
+  selfEmployedHealthInsuranceDeduction?: number;
+  penaltyOnEarlyWithdrawal?: number;
+  alimonyPaid?: number;
+  iraDeduction?: number;
+  studentLoanInterestDeduction?: number;
+  tuitionAndFees?: number;
+};
+
+class Input<T extends keyof Schedule1Input> extends InputLine<Schedule1Input, T> {
+  private _predicate: (value: Schedule1Input[T]) => void;
+
+  constructor(input: T, predicate?: (value: Schedule1Input[T]) => void) {
+    super(input);
+    this._predicate = predicate;
+  }
+
+  value(tr: TaxReturn): Schedule1Input[T] {
+    let value: Schedule1Input[T] = undefined;
+    try {
+      value = super.value(tr);
+    } catch (NotFoundError) {
+    }
+    if (this._predicate)
+      this._predicate(value);
+    return value;
+  }
+};
+
+export default class Schedule1 extends Form<Schedule1['_lines'], Schedule1Input> {
+  readonly name = 'Schedule 1';
+
+  readonly _lines = {
+    // Part 1
+    '1': new Input('stateAndLocalTaxableRefunds'),
+    '2': new Input('alimonyReceived'),
+    '3': new Input('businessIncome', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Schedule C not supported');
+    }),
+    '4': new Input('otherGainsOrLosses', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Form 4797 not supported');
+    }),
+    '5': new Input('rentalRealEstateRoyaltiesPartnershipsSCorps', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Schedule E not supported');
+    }),
+    '6': new Input('farmIncome', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Schedule F not supported');
+    }),
+    '7': new Input('unemploymentCompensation'),
+    '8': new Input('otherIncome'),
+    '9': new ComputedLine((tr): number => {
+      return undefinedToZero(this.getValue(tr, '1')) +
+             undefinedToZero(this.getValue(tr, '2')) +
+             undefinedToZero(this.getValue(tr, '3')) +
+             undefinedToZero(this.getValue(tr, '4')) +
+             undefinedToZero(this.getValue(tr, '5')) +
+             undefinedToZero(this.getValue(tr, '6')) +
+             undefinedToZero(this.getValue(tr, '7')) +
+             undefinedToZero(this.getValue(tr, '8'));
+    }),
+
+    // Part 2
+    '10': new Input('educatorExpenses'),
+    '11': new Input('businessExpensesForm2106', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Form 2106 not supported');
+    }),
+    '12': new Input('hsaDeduction', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Form 8889 not supported');
+    }),
+    '13': new Input('armedForcesMovingExpenses', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Form 3903 not supported');
+    }),
+    '14': new Input('deductibleSelfEmploymentTax', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Schedule SE not supported');
+    }),
+    '15': new Input('selfEmployedSepSimpleQualifiedPlans'),
+    '16': new Input('selfEmployedHealthInsuranceDeduction'),
+    '17': new Input('penaltyOnEarlyWithdrawal'),
+    '18': new Input('alimonyPaid'),
+    '19': new Input('iraDeduction'),
+    '20': new Input('studentLoanInterestDeduction'),
+    '21': new Input('tuitionAndFees', (value: number) => {
+      if (value !== undefined)
+        throw new UnsupportedFeatureError('Form 8917 not supported');
+    }),
+    '22': new ComputedLine((tr): number => {
+      return undefinedToZero(this.getValue(tr, '10')) +
+             undefinedToZero(this.getValue(tr, '11')) +
+             undefinedToZero(this.getValue(tr, '12')) +
+             undefinedToZero(this.getValue(tr, '13')) +
+             undefinedToZero(this.getValue(tr, '14')) +
+             undefinedToZero(this.getValue(tr, '15')) +
+             undefinedToZero(this.getValue(tr, '16')) +
+             undefinedToZero(this.getValue(tr, '17')) +
+             undefinedToZero(this.getValue(tr, '18')) +
+             undefinedToZero(this.getValue(tr, '19')) +
+             undefinedToZero(this.getValue(tr, '20')) +
+             undefinedToZero(this.getValue(tr, '21'));
+    }),
+  };
+};