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