Move some computations for Form1040 into methods.
[ustaxlib.git] / src / fed2019 / Schedule1.ts
1 // Copyright 2020 Blue Static <https://www.bluestatic.org>
2 // This program is free software licensed under the GNU General Public License,
3 // version 3.0. The full text of the license can be found in LICENSE.txt.
4 // SPDX-License-Identifier: GPL-3.0-only
5
6 import { Form, TaxReturn } from '../core';
7 import { ComputedLine, InputLine } from '../core/Line';
8 import * as Trace from '../core/Trace';
9 import { NotFoundError, UnsupportedFeatureError } from '../core/Errors';
10 import { undefinedToZero } from '../core/Math';
11
12 import { FilingStatus } from './Form1040';
13
14 export interface Schedule1Input {
15 // Additional Income
16 stateAndLocalTaxableRefunds?: number;
17 alimonyReceived?: number;
18 businessIncome?: number;
19 otherGainsOrLosses?: number
20 rentalRealEstateRoyaltiesPartnershipsSCorps?: number;
21 farmIncome?: number;
22 unemploymentCompensation?: number;
23 otherIncome?: number;
24
25 // Adjustments
26 educatorExpenses?: number;
27 businessExpensesForm2106?: number;
28 hsaDeduction?: number;
29 armedForcesMovingExpenses?: number;
30 deductibleSelfEmploymentTax?: number;
31 selfEmployedSepSimpleQualifiedPlans?: number;
32 selfEmployedHealthInsuranceDeduction?: number;
33 penaltyOnEarlyWithdrawal?: number;
34 alimonyPaid?: number;
35 iraDeduction?: number;
36 studentLoanInterestDeduction?: number;
37 tuitionAndFees?: number;
38 };
39
40 class Input<T extends keyof Schedule1Input> extends InputLine<Schedule1Input, T> {
41 private _predicate: (value: Schedule1Input[T]) => void;
42
43 constructor(input: T, predicate?: (value: Schedule1Input[T]) => void) {
44 super(input);
45 this._predicate = predicate;
46 }
47
48 value(tr: TaxReturn): Schedule1Input[T] {
49 let value: Schedule1Input[T] = undefined;
50 try {
51 value = super.value(tr);
52 } catch (NotFoundError) {
53 Trace.end();
54 }
55 if (this._predicate)
56 this._predicate(value);
57 return value;
58 }
59 };
60
61 export default class Schedule1 extends Form<Schedule1Input> {
62 readonly name = 'Schedule 1';
63
64 readonly lines = {
65 // Part 1
66 '1': new ComputedLine((tr): number => {
67 if (this.hasInput('stateAndLocalTaxableRefunds'))
68 return tr.getForm(SALTWorksheet).getValue(tr, '9');
69 return 0;
70 }, 'Taxable refunds, credits, or offsets of state and local income taxes'),
71 '2': new Input('alimonyReceived'),
72 '3': new Input('businessIncome', (value: number) => {
73 if (value !== undefined)
74 throw new UnsupportedFeatureError('Schedule C not supported');
75 }),
76 '4': new Input('otherGainsOrLosses', (value: number) => {
77 if (value !== undefined)
78 throw new UnsupportedFeatureError('Form 4797 not supported');
79 }),
80 '5': new Input('rentalRealEstateRoyaltiesPartnershipsSCorps', (value: number) => {
81 if (value !== undefined)
82 throw new UnsupportedFeatureError('Schedule E not supported');
83 }),
84 '6': new Input('farmIncome', (value: number) => {
85 if (value !== undefined)
86 throw new UnsupportedFeatureError('Schedule F not supported');
87 }),
88 '7': new Input('unemploymentCompensation'),
89 '8': new Input('otherIncome'),
90 '9': new ComputedLine((tr): number => {
91 return undefinedToZero(this.getValue(tr, '1')) +
92 undefinedToZero(this.getValue(tr, '2')) +
93 undefinedToZero(this.getValue(tr, '3')) +
94 undefinedToZero(this.getValue(tr, '4')) +
95 undefinedToZero(this.getValue(tr, '5')) +
96 undefinedToZero(this.getValue(tr, '6')) +
97 undefinedToZero(this.getValue(tr, '7')) +
98 undefinedToZero(this.getValue(tr, '8'));
99 }),
100
101 // Part 2
102 '10': new Input('educatorExpenses'),
103 '11': new Input('businessExpensesForm2106', (value: number) => {
104 if (value !== undefined)
105 throw new UnsupportedFeatureError('Form 2106 not supported');
106 }),
107 '12': new Input('hsaDeduction', (value: number) => {
108 if (value !== undefined)
109 throw new UnsupportedFeatureError('Form 8889 not supported');
110 }),
111 '13': new Input('armedForcesMovingExpenses', (value: number) => {
112 if (value !== undefined)
113 throw new UnsupportedFeatureError('Form 3903 not supported');
114 }),
115 '14': new Input('deductibleSelfEmploymentTax', (value: number) => {
116 if (value !== undefined)
117 throw new UnsupportedFeatureError('Schedule SE not supported');
118 }),
119 '15': new Input('selfEmployedSepSimpleQualifiedPlans'),
120 '16': new Input('selfEmployedHealthInsuranceDeduction'),
121 '17': new Input('penaltyOnEarlyWithdrawal'),
122 '18': new Input('alimonyPaid'),
123 '19': new Input('iraDeduction'),
124 '20': new Input('studentLoanInterestDeduction'),
125 '21': new Input('tuitionAndFees', (value: number) => {
126 if (value !== undefined)
127 throw new UnsupportedFeatureError('Form 8917 not supported');
128 }),
129 '22': new ComputedLine((tr): number => {
130 return undefinedToZero(this.getValue(tr, '10')) +
131 undefinedToZero(this.getValue(tr, '11')) +
132 undefinedToZero(this.getValue(tr, '12')) +
133 undefinedToZero(this.getValue(tr, '13')) +
134 undefinedToZero(this.getValue(tr, '14')) +
135 undefinedToZero(this.getValue(tr, '15')) +
136 undefinedToZero(this.getValue(tr, '16')) +
137 undefinedToZero(this.getValue(tr, '17')) +
138 undefinedToZero(this.getValue(tr, '18')) +
139 undefinedToZero(this.getValue(tr, '19')) +
140 undefinedToZero(this.getValue(tr, '20')) +
141 undefinedToZero(this.getValue(tr, '21'));
142 }),
143 };
144 };
145
146 export interface SALTWorksheetInput {
147 prevYearSalt: number; // ScheduleA@5d.
148 limitedPrevYearSalt: number; // ScheduleA@5e.
149 prevYearItemizedDeductions?: number; // ScheduleA@17.
150 prevYearFilingStatus?: FilingStatus;
151 };
152
153 export class SALTWorksheet extends Form<SALTWorksheetInput> {
154 readonly name = 'SALT Refund Worksheet';
155
156 readonly lines = {
157 '1': new ComputedLine((tr): number => {
158 const refunds = tr.findForm(Schedule1).getInput('stateAndLocalTaxableRefunds');
159 const prevYear = this.getInput('prevYearSalt');
160 return refunds > prevYear ? prevYear : refunds;
161 }, 'Tax refunds'),
162 '2': new ComputedLine((tr): [number, boolean] => {
163 const prevYearSalt = this.getInput('prevYearSalt');
164 const limitedPrevYearSalt = this.getInput('limitedPrevYearSalt');
165 if (prevYearSalt > limitedPrevYearSalt) {
166 return [prevYearSalt - limitedPrevYearSalt, true];
167 }
168 return [this.getValue(tr, '1'), false];
169 }, 'Remainder of SALT limitation'),
170 '3': new ComputedLine((tr): number => {
171 const l1 = this.getValue(tr, '1');
172 const l2 = this.getValue(tr, '2');
173 if (!l2[1])
174 return l1;
175 if (l1 > l2[0])
176 return l2[0] - l1;
177 // Else: none of refund is taxable.
178 return 0;
179 }),
180 '4': new InputLine<SALTWorksheetInput>('prevYearItemizedDeductions'),
181 '5': new ComputedLine((tr): number => {
182 const fs = this.getInput('prevYearFilingStatus');
183 return tr.constants.prevYearStandardDeduction[fs];
184 }, 'Previous year standard deduction'),
185 '6': new ComputedLine((tr): number => 0, 'Special situations'), // Not supported
186 '7': new ComputedLine((tr): number => this.getValue(tr, '5') + this.getValue(tr, '6')),
187 '8': new ComputedLine((tr): number => {
188 const l4 = this.getValue(tr, '4');
189 const l7 = this.getValue(tr, '7');
190 if (l7 < l4)
191 return l4 - l7;
192 // Else: none of refund is taxable.
193 return 0;
194 }),
195 '9': new ComputedLine((tr): number => Math.min(this.getValue(tr, '3'), this.getValue(tr, '8')), 'Taxable refund')
196 };
197 };