From 9eb10bc9365fe17f2310b8863f980a872fb735f8 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 22 Mar 2020 16:40:56 -0400 Subject: [PATCH] Add SALT worksheet for determining taxable amount of refunds. --- src/fed2019/Schedule1.test.ts | 45 +++++++++++++++++++++--- src/fed2019/Schedule1.ts | 66 +++++++++++++++++++++++++++++++++-- src/fed2019/index.ts | 1 + 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/fed2019/Schedule1.test.ts b/src/fed2019/Schedule1.test.ts index 7a982d4..9daa230 100644 --- a/src/fed2019/Schedule1.test.ts +++ b/src/fed2019/Schedule1.test.ts @@ -7,22 +7,57 @@ import { Person } from '../core'; import { UnsupportedFeatureError } from '../core/Errors'; import Form1040, { FilingStatus } from './Form1040'; -import Schedule1, { Schedule1Input } from './Schedule1'; +import Schedule1, { Schedule1Input, SALTWorksheet } from './Schedule1'; import TaxReturn from './TaxReturn'; -test('state tax refund', () => { +test('non-taxable state tax refund', () => { const p = Person.self('A'); const tr = new TaxReturn(); tr.addForm(new Form1040({ filingStatus: FilingStatus.Single })); + const w = new SALTWorksheet({ + prevYearSalt: 90000, + limitedPrevYearSalt: 10000, + prevYearItemizedDeductions: 25000, + prevYearFilingStatus: FilingStatus.Single + }); + tr.addForm(w); + const f = new Schedule1({ + stateAndLocalTaxableRefunds: 17000 + }); + tr.addForm(f); + + expect(w.getValue(tr, '1')).toBe(17000); + expect(w.getValue(tr, '2')).toStrictEqual([80000, true]); + expect(w.getValue(tr, '3')).toBe(0); + expect(w.getValue(tr, '9')).toBe(0); + expect(tr.getForm(Form1040).getValue(tr, '7a')).toBe(0); +}); + +test('taxable state tax refund', () => { + const p = Person.self('A'); + const tr = new TaxReturn(); + tr.addForm(new Form1040({ + filingStatus: FilingStatus.Single + })); + const w = new SALTWorksheet({ + prevYearSalt: 7500, + limitedPrevYearSalt: 7500, + prevYearItemizedDeductions: 14000, + prevYearFilingStatus: FilingStatus.Single + }); + tr.addForm(w); const f = new Schedule1({ - stateAndLocalTaxableRefunds: 500 + stateAndLocalTaxableRefunds: 2000 }); tr.addForm(f); - expect(f.getValue(tr, '9')).toBe(500); - expect(tr.getForm(Form1040).getValue(tr, '7a')).toBe(500); + expect(w.getValue(tr, '1')).toBe(2000); + expect(w.getValue(tr, '2')).toStrictEqual([2000, false]); + expect(w.getValue(tr, '3')).toBe(2000); + expect(w.getValue(tr, '9')).toBe(2000); + expect(tr.getForm(Form1040).getValue(tr, '7a')).toBe(2000); }); test('unsupported inputs', () => { diff --git a/src/fed2019/Schedule1.ts b/src/fed2019/Schedule1.ts index 60544cd..852633d 100644 --- a/src/fed2019/Schedule1.ts +++ b/src/fed2019/Schedule1.ts @@ -9,7 +9,7 @@ import * as Trace from '../core/Trace'; import { NotFoundError, UnsupportedFeatureError } from '../core/Errors'; import { undefinedToZero } from '../core/Math'; -import Form1040 from './Form1040'; +import Form1040, { FilingStatus } from './Form1040'; export interface Schedule1Input { // Additional Income @@ -63,7 +63,11 @@ export default class Schedule1 extends Form readonly _lines = { // Part 1 - '1': new Input('stateAndLocalTaxableRefunds'), + '1': new ComputedLine((tr): number => { + if (this.hasInput('stateAndLocalTaxableRefunds')) + return tr.getForm(SALTWorksheet).getValue(tr, '9'); + return 0; + }, 'Taxable refunds, credits, or offsets of state and local income taxes'), '2': new Input('alimonyReceived'), '3': new Input('businessIncome', (value: number) => { if (value !== undefined) @@ -138,3 +142,61 @@ export default class Schedule1 extends Form }), }; }; + +export interface SALTWorksheetInput { + prevYearSalt: number; // ScheduleA@5d. + limitedPrevYearSalt: number; // ScheduleA@5e. + prevYearItemizedDeductions?: number; // ScheduleA@17. + prevYearFilingStatus?: FilingStatus; +}; + +export class SALTWorksheet extends Form { + readonly name = 'State and Local Income Tax Refund Worksheet'; + + protected readonly _lines = { + '1': new ComputedLine((tr): number => { + const refunds = tr.findForm(Schedule1).getInput('stateAndLocalTaxableRefunds'); + const prevYear = this.getInput('prevYearSalt'); + return refunds > prevYear ? prevYear : refunds; + }, 'Tax refunds'), + '2': new ComputedLine((tr): [number, boolean] => { + const prevYearSalt = this.getInput('prevYearSalt'); + const limitedPrevYearSalt = this.getInput('limitedPrevYearSalt'); + if (prevYearSalt > limitedPrevYearSalt) { + return [prevYearSalt - limitedPrevYearSalt, true]; + } + return [this.getValue(tr, '1'), false]; + }, 'Remainder of SALT limitation'), + '3': new ComputedLine((tr): number => { + const l1 = this.getValue(tr, '1'); + const l2 = this.getValue(tr, '2'); + if (!l2[1]) + return l1; + if (l1 > l2[0]) + return l2[0] - l1; + // Else: none of refund is taxable. + return 0; + }), + '4': new InputLine('prevYearItemizedDeductions'), + '5': new ComputedLine((tr): number => { + switch (this.getInput('prevYearFilingStatus')) { + case FilingStatus.Single: + case FilingStatus.MarriedFilingSeparate: + return 12000; + case FilingStatus.MarriedFilingJoint: + return 24000; + } + }, 'Previous year standard deduction'), + '6': new ComputedLine((tr): number => 0, 'Special situations'), // Not supported + '7': new ComputedLine((tr): number => this.getValue(tr, '5') + this.getValue(tr, '6')), + '8': new ComputedLine((tr): number => { + const l4 = this.getValue(tr, '4'); + const l7 = this.getValue(tr, '7'); + if (l7 < l4) + return l4 - l7; + // Else: none of refund is taxable. + return 0; + }), + '9': new ComputedLine((tr): number => Math.min(this.getValue(tr, '3'), this.getValue(tr, '8')), 'Taxable refund') + }; +}; diff --git a/src/fed2019/index.ts b/src/fed2019/index.ts index 6c92fec..1f9f7bb 100644 --- a/src/fed2019/index.ts +++ b/src/fed2019/index.ts @@ -26,5 +26,6 @@ export * from './Form1099B'; export * from './Form1099R'; export * from './Form1116'; export * from './Form8949'; +export * from './Schedule1'; export * from './ScheduleD'; export * from './W2'; -- 2.22.5