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', () => {
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
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)
}),
};
};
+
+export interface SALTWorksheetInput {
+ prevYearSalt: number; // ScheduleA@5d.
+ limitedPrevYearSalt: number; // ScheduleA@5e.
+ prevYearItemizedDeductions?: number; // ScheduleA@17.
+ prevYearFilingStatus?: FilingStatus;
+};
+
+export class SALTWorksheet extends Form<SALTWorksheet['_lines'], SALTWorksheetInput> {
+ 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<SALTWorksheetInput>('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')
+ };
+};