Strongly type Forms on TaxReturn.
[ustaxlib.git] / src / fed2019 / Form1040.ts
1 import Form, { FormClass } from '../Form';
2 import TaxReturn from '../TaxReturn';
3 import { Line, AccumulatorLine, ComputedLine, ReferenceLine } from '../Line';
4 import { UnsupportedFeatureError } from '../Errors';
5
6 import Form8959 from './Form8959';
7 import Form1099INT from './Form1099INT';
8 import Form1099DIV from './Form1099DIV';
9 import FormW2 from './FormW2';
10
11 export enum FilingStatus {
12 Single,
13 MarriedFilingSeparate,
14 MarriedFilingJoint,
15 };
16
17 export interface Form1040Input {
18 filingStatus: FilingStatus;
19 };
20
21 const reduceBySum = (list: number[]) => list.reduce((acc, curr) => acc + curr, 0);
22
23 export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
24 readonly name = '1040';
25
26 protected readonly _lines = {
27 '1': new AccumulatorLine(FormW2, '1', 'Wages, salaries, tips, etc.'),
28 '2a': new AccumulatorLine(Form1099INT, '8', 'Tax-exempt interest'),
29 '2b': new AccumulatorLine(Form1099INT, '1', 'Taxable interest'),
30 '3a': new AccumulatorLine(Form1099DIV, '1b', 'Qualified dividends'),
31 '3b': new AccumulatorLine(Form1099DIV, '1a', 'Ordinary dividends'),
32 // 4a and 4b are complex
33 '4b': new ComputedLine(() => 0),
34 '4d': new ComputedLine(() => 0),
35 // 4c and 4d are not supported
36 // 5a and 5b are not supported
37 '6': new ReferenceLine(/*'Schedule D'*/ undefined, '21', 'Capital gain/loss', 0),
38 '7a': new ReferenceLine(/*'Schedule 1'*/ undefined, '9', 'Other income from Schedule 1', 0),
39
40 '7b': new ComputedLine((tr: TaxReturn): number => {
41 let income = 0;
42 income += this.getValue(tr, '1');
43 income += this.getValue(tr, '2b');
44 income += this.getValue(tr, '3b');
45 income += this.getValue(tr, '4b');
46 income += this.getValue(tr, '4d');
47 //income += this.getValue(tr, '5b');
48 income += this.getValue(tr, '6');
49 income += this.getValue(tr, '7a');
50 return income;
51 }, 'Total income'),
52
53 '8a': new ReferenceLine(undefined /*'Schedule 1'*/, '22', 'Adjustments to income', 0),
54
55 '8b': new ComputedLine((tr: TaxReturn): number => {
56 return this.getValue(tr, '7b') - this.getValue(tr, '8a');
57 }, 'Adjusted gross income'),
58
59 // TODO - Deduction
60 '9': new ComputedLine(() => 0, 'Deduction'),
61
62 '10': new ComputedLine((tr: TaxReturn): number => {
63 const taxableIncome = this.getValue(tr, '8b');
64 let use8995a = false;
65 switch (this.getInput('filingStatus')) {
66 case FilingStatus.Single: use8995a = taxableIncome <= 160700; break;
67 case FilingStatus.MarriedFilingSeparate: use8995a = taxableIncome <= 160725; break;
68 case FilingStatus.MarriedFilingJoint: use8995a = taxableIncome <= 321400; break;
69 };
70 return 0;
71 }, 'Qualified business income deduction'),
72
73 '11a': new ComputedLine((tr: TaxReturn): number => {
74 return this.getValue(tr, '9') + this.getValue(tr, '10');
75 }),
76 '11b': new ComputedLine((tr: TaxReturn): number => {
77 const value = this.getValue(tr, '8b') - this.getValue(tr, '11a');
78 return value < 0 ? 0 : value;
79 }, 'Taxable income'),
80
81 '12a': new ComputedLine((tr: TaxReturn): number => {
82 // Not supported:
83 // Form 8814 (election to report child's interest or dividends)
84 // Form 4972 (relating to lump-sum distributions)
85 const taxableIncome = this.getValue(tr, '11b');
86 if (taxableIncome < 100000)
87 throw new UnsupportedFeatureError('Tax-table tax liability not supported');
88
89 const l11b = this.getValue(tr, '11b');
90
91 switch (this.getInput('filingStatus')) {
92 case FilingStatus.Single:
93 if (taxableIncome < 160725)
94 return (l11b * 0.24) - 5825.50;
95 else if (taxableIncome < 204100)
96 return (l11b * 0.32) - 18683.50;
97 else if (taxableIncome < 510300)
98 return (l11b * 0.35) - 24806.50;
99 else
100 return (l11b * 0.38) - 35012.50;
101 case FilingStatus.MarriedFilingJoint:
102 if (taxableIncome < 168400)
103 return (l11b * 0.22) - 8283.00;
104 else if (taxableIncome < 321450)
105 return (l11b * 0.24) - 11651.00;
106 else if (taxableIncome < 408200)
107 return (l11b * 0.32) - 37367.00;
108 else if (taxableIncome < 612350)
109 return (l11b * 0.35) - 49613.00;
110 else
111 return (l11b * 0.37) - 61860.00;
112 case FilingStatus.MarriedFilingSeparate:
113 if (taxableIncome < 160725)
114 return (l11b * 0.24) - 5825.50;
115 else if (taxableIncome < 204100)
116 return (l11b * 0.32) - 18683.50;
117 else if (taxableIncome < 306175)
118 return (l11b * 0.35) - 24806.50;
119 else
120 return (l11b * 0.37) - 30930.00;
121 }
122 throw new UnsupportedFeatureError('Unexpected return type');
123 }, 'Tax'),
124
125 '12b': new ComputedLine((tr: TaxReturn): number => {
126 return this.getValue(tr, '12a') + tr.getForm(Schedule2).getValue(tr, '3');
127 }, 'Additional tax'),
128
129 // Not supported: 13a - child tax credit
130
131 '13b': new ComputedLine((tr: TaxReturn): number => {
132 // TODO: add Sched 3.L7
133 return 0;
134 }, 'Additional credits'),
135
136 '14': new ComputedLine((tr: TaxReturn): number => {
137 const l12b = this.getValue(tr, '12b');
138 const l13b = this.getValue(tr, '13b');
139 const value = l12b - l13b;
140 return value < 0 ? 0 : value;
141 }),
142
143 '15': new ReferenceLine(undefined /*'Schedule 2'*/, '10', undefined, 0),
144
145 '16': new ComputedLine((tr: TaxReturn): number => {
146 return this.getValue(tr, '14') + this.getValue(tr, '15');
147 }, 'Total tax'),
148
149 '17': new ComputedLine((tr: TaxReturn): number => {
150 const fedTaxWithheldBoxes = [
151 new AccumulatorLine(FormW2, '2'),
152 //new AccumulatorLine(Form1099R, '4'),
153 new AccumulatorLine(Form1099DIV, '4'),
154 new AccumulatorLine(Form1099INT, '4'),
155 ];
156 const withholding: number[] = fedTaxWithheldBoxes.map(b => b.value(tr));
157
158 let additionalMedicare = 0;
159 const f8959 = tr.findForm(Form8959)
160 if (f8959) {
161 additionalMedicare = f8959.getValue(tr, '24');
162 }
163
164 return reduceBySum(withholding) + additionalMedicare;
165 }, 'Federal income tax withheld'),
166
167 // 18 not supported
168
169 '19': new ReferenceLine(Form1040 as any, '17', 'Total payments'),
170
171 '20': new ComputedLine((tr: TaxReturn): number => {
172 const l16: number = this.getValue(tr, '16');
173 const l19: number = this.getValue(tr, '19');
174 if (l19 > l16)
175 return l19 - l16;
176 return 0;
177 }, 'Amount overpaid'),
178
179 '23': new ComputedLine((tr: TaxReturn): number => {
180 const l16 = this.getValue(tr, '16');
181 const l19 = this.getValue(tr, '19');
182 if (l19 < l16)
183 return l16 - l19;
184 return 0;
185 }, 'Amount you owe'),
186 };
187 };
188
189 export class Schedule2 extends Form<Schedule2['_lines']> {
190 readonly name = 'Schedule 2';
191
192 protected readonly _lines = {
193 '1': new ComputedLine((tr: TaxReturn): number => {
194 // TODO - this is just using Taxable Income, rather than AMT-limited
195 // income
196 const f1040 = tr.getForm(Form1040);
197 const taxableIncome = f1040.getValue(tr, '11b');
198 switch (f1040.getInput('filingStatus')) {
199 case FilingStatus.Single:
200 if (taxableIncome < 510300)
201 return 0;
202 case FilingStatus.MarriedFilingJoint:
203 if (taxableIncome < 1020600)
204 return 0;
205 case FilingStatus.MarriedFilingSeparate:
206 if (taxableIncome < 510300)
207 return 0;
208 }
209 throw new UnsupportedFeatureError('The AMT is not supported');
210 }, 'AMT'),
211 // 2 is not supported (Excess advance premium tax credit repayment)
212 '3': new ComputedLine((tr: TaxReturn): number => {
213 // Should include line 2.
214 return this.getValue(tr, '1');
215 }),
216
217 // 4 is not supported (Self-employment tax.)
218 // 5 is not supported (Unreported social security and Medicare tax from)
219 // 6 is not supported (Additional tax on IRAs, other qualified retirement plans, and other tax-favored accounts)
220 // 7 is not supported (Household employment taxes.)
221 '8': new ComputedLine((tr: TaxReturn): number => {
222 const f1040 = tr.getForm(Form1040);
223 const wages = f1040.getLine('1').value(tr);
224 const agi = f1040.getLine('8b').value(tr);
225
226 let niit: boolean;
227 const filingStatus = f1040.getInput('filingStatus');
228
229 const additionalMedicare = wages > Form8959.filingStatusLimit(filingStatus);
230
231 switch (f1040.getInput('filingStatus')) {
232 case FilingStatus.Single:
233 if (wages > 200000) {
234 niit = true;
235 }
236 break;
237 case FilingStatus.MarriedFilingJoint:
238 if (wages > 250000) {
239 niit = true;
240 }
241 break;
242 case FilingStatus.MarriedFilingSeparate:
243 if (wages > 125000) {
244 niit = true;
245 }
246 break;
247 }
248
249 let value = 0;
250
251 if (additionalMedicare) {
252 const f8959 = tr.getForm(Form8959);
253 value += f8959.getValue(tr, '18');
254 }
255
256 if (niit) {
257 //const f8960 = tr.getForm('8960');
258 }
259
260 return value;
261 }),
262 // 9 is not supported (Section 965 net tax liability installment from Form 965-A)
263
264 '10': new ComputedLine((tr: TaxReturn): number => {
265 // Should be lines 4 - 8.
266 return this.getValue(tr, '8');
267 })
268 };
269 };