From 8afc50477220d2061369a47799bc356cc0cc7ad5 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 8 Mar 2020 10:25:50 -0400 Subject: [PATCH] Add Schedule 1. --- src/Math.test.ts | 7 +- src/Math.ts | 6 +- src/fed2019/Form1040.ts | 5 +- src/fed2019/Schedule1.test.ts | 44 +++++++++++ src/fed2019/Schedule1.ts | 134 ++++++++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 src/fed2019/Schedule1.test.ts create mode 100644 src/fed2019/Schedule1.ts diff --git a/src/Math.test.ts b/src/Math.test.ts index e6197c5..b339b89 100644 --- a/src/Math.test.ts +++ b/src/Math.test.ts @@ -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); +}); diff --git a/src/Math.ts b/src/Math.ts index 613e5eb..90f76c6 100644 --- a/src/Math.ts +++ b/src/Math.ts @@ -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); diff --git a/src/fed2019/Form1040.ts b/src/fed2019/Form1040.ts index 8c5d829..c9c6526 100644 --- a/src/fed2019/Form1040.ts +++ b/src/fed2019/Form1040.ts @@ -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 { 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 { 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 index 0000000..117ef92 --- /dev/null +++ b/src/fed2019/Schedule1.test.ts @@ -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 index 0000000..4d0bb0e --- /dev/null +++ b/src/fed2019/Schedule1.ts @@ -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 extends InputLine { + 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 { + 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')); + }), + }; +}; -- 2.22.5