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