Shorten some worksheet names.
[ustaxlib.git] / src / fed2019 / Form1040.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 { Line, AccumulatorLine, ComputedLine, ReferenceLine, sumLineOfForms } from '../core/Line';
8 import { UnsupportedFeatureError } from '../core/Errors';
9 import { clampToZero, reduceBySum } from '../core/Math';
10
11 import Form8606 from './Form8606';
12 import Form8959 from './Form8959';
13 import Form1099INT from './Form1099INT';
14 import Form1099DIV from './Form1099DIV';
15 import Form1099R, { Box7Code } from './Form1099R';
16 import Form8995REIT from './Form8995';
17 import W2 from './W2';
18 import Schedule1 from './Schedule1';
19 import Schedule2 from './Schedule2';
20 import Schedule3 from './Schedule3';
21 import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
22
23 export enum FilingStatus {
24 Single = 'S',
25 MarriedFilingSeparate = 'MFS',
26 MarriedFilingJoint = 'MFJ',
27 };
28
29 export interface Form1040Input {
30 filingStatus: FilingStatus;
31 };
32
33 export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
34 readonly name = '1040';
35
36 protected readonly _lines = {
37 '1': new AccumulatorLine(W2, '1', 'Wages, salaries, tips, etc.'),
38 '2a': new ComputedLine((tr): number => {
39 const value = (new AccumulatorLine(Form1099INT, '8')).value(tr) +
40 (new AccumulatorLine(Form1099DIV, '11')).value(tr);
41 return value;
42 }, 'Tax-exempt interest'),
43 '2b': new ComputedLine((tr): number => {
44 const value = (new AccumulatorLine(Form1099INT, '1')).value(tr) +
45 (new AccumulatorLine(Form1099INT, '3')).value(tr);
46 return value;
47 }, 'Taxable interest'),
48 '3a': new AccumulatorLine(Form1099DIV, '1b', 'Qualified dividends'),
49 '3b': new AccumulatorLine(Form1099DIV, '1a', 'Ordinary dividends'),
50 '4a': new ComputedLine((tr): number => {
51 const f1099Rs = tr.findForms(Form1099R).filter(f => !f.getValue(tr, '7').includes(Box7Code.G));
52 return sumLineOfForms(tr, f1099Rs, '1');
53 }),
54 '4b': new ComputedLine((tr): number => {
55 const f8606s = tr.findForms(Form8606);
56 return sumLineOfForms(tr, f8606s, '15c') + sumLineOfForms(tr, f8606s, '18');
57 }, 'IRA distributions, taxadble amount'),
58 '4d': new ComputedLine(() => 0),
59 // 4c and 4d are not supported
60 // 5a and 5b are not supported
61 '6': new ComputedLine((tr): number => {
62 const schedD = tr.findForm(ScheduleD);
63 if (!schedD)
64 return 0;
65
66 const l6 = schedD.getValue(tr, '16');
67 if (l6 >= 0)
68 return l6;
69 return schedD.getValue(tr, '21');
70 }, 'Capital gain/loss'),
71 '7a': new ReferenceLine(Schedule1, '9', 'Other income from Schedule 1', 0),
72
73 '7b': new ComputedLine((tr): number => {
74 let income = 0;
75 income += this.getValue(tr, '1');
76 income += this.getValue(tr, '2b');
77 income += this.getValue(tr, '3b');
78 income += this.getValue(tr, '4b');
79 income += this.getValue(tr, '4d');
80 //income += this.getValue(tr, '5b');
81 income += this.getValue(tr, '6');
82 income += this.getValue(tr, '7a');
83 return income;
84 }, 'Total income'),
85
86 '8a': new ReferenceLine(Schedule1, '22', 'Adjustments to income', 0),
87
88 '8b': new ComputedLine((tr): number => {
89 return this.getValue(tr, '7b') - this.getValue(tr, '8a');
90 }, 'Adjusted gross income'),
91
92 '9': new ComputedLine((): number => {
93 // TODO - Itemized deductions.
94 switch (this.filingStatus) {
95 case FilingStatus.Single:
96 case FilingStatus.MarriedFilingSeparate:
97 return 12200;
98 case FilingStatus.MarriedFilingJoint:
99 return 24400;
100 }
101 }, 'Deduction'),
102
103 '10': new ComputedLine((tr): number => {
104 const f8995 = tr.findForm(Form8995REIT);
105 if (f8995)
106 return f8995.getValue(tr, '39');
107 return 0;
108 }, 'Qualified business income deduction'),
109
110 '11a': new ComputedLine((tr): number => {
111 return this.getValue(tr, '9') + this.getValue(tr, '10');
112 }),
113 '11b': new ComputedLine((tr): number => {
114 return clampToZero(this.getValue(tr, '8b') - this.getValue(tr, '11a'));
115 }, 'Taxable income'),
116
117 '12a': new ComputedLine((tr): number => {
118 // Not supported:
119 // Form 8814 (election to report child's interest or dividends)
120 // Form 4972 (relating to lump-sum distributions)
121
122 const schedD = tr.findForm(ScheduleD);
123 // Line 18 and 19 are not undefined or 0.
124 if (schedD && !schedD.getValue(tr, '20')) {
125 // Use ScheD tax worksheet;
126 const schedDtw = tr.findForm(ScheduleDTaxWorksheet);
127 if (schedDtw)
128 return schedDtw.getValue(tr, '47');
129 }
130
131 // If there are qualified dividends, use the QDCGTW.
132 if (this.getValue(tr, '3a') > 0) {
133 const qdcgtw = tr.getForm(QDCGTaxWorksheet);
134 return qdcgtw.getValue(tr, '27');
135 }
136
137 // Otherwise, compute just on taxable income.
138 return computeTax(this.getValue(tr, '11b'), this.filingStatus);
139 }, 'Tax'),
140
141 '12b': new ComputedLine((tr): number => {
142 return this.getValue(tr, '12a') + tr.getForm(Schedule2).getValue(tr, '3');
143 }, 'Additional tax'),
144
145 // Not supported: 13a - child tax credit
146
147 '13b': new ReferenceLine(Schedule3, '7', 'Additional credits', 0),
148
149 '14': new ComputedLine((tr): number => {
150 return clampToZero(this.getValue(tr, '12b') - this.getValue(tr, '13b'));
151 }),
152
153 '15': new ReferenceLine(Schedule2, '10', undefined, 0),
154
155 '16': new ComputedLine((tr): number => {
156 return this.getValue(tr, '14') + this.getValue(tr, '15');
157 }, 'Total tax'),
158
159 '17': new ComputedLine((tr): number => {
160 const fedTaxWithheldBoxes = [
161 new AccumulatorLine(W2, '2'),
162 new AccumulatorLine(Form1099R, '4'),
163 new AccumulatorLine(Form1099DIV, '4'),
164 new AccumulatorLine(Form1099INT, '4'),
165 ];
166 const withholding: number[] = fedTaxWithheldBoxes.map(b => b.value(tr));
167
168 let additionalMedicare = 0;
169 const f8959 = tr.findForm(Form8959)
170 if (f8959) {
171 additionalMedicare = f8959.getValue(tr, '24');
172 }
173
174 return reduceBySum(withholding) + additionalMedicare;
175 }, 'Federal income tax withheld'),
176
177 // 18a not supported - Earned income credit (EIC)
178 // 18b not supported - Additional child tax credit. Attach Schedule 8812
179 // 18c not supported - American opportunity credit from Form 8863, line 8
180 '18d': new ReferenceLine(Schedule3, '14', undefined, 0),
181 '18e': new ComputedLine((tr): number => {
182 // Should include 18a-18c.
183 return this.getValue(tr, '18d');
184 }),
185
186 '19': new ComputedLine((tr): number => {
187 return this.getValue(tr, '17') + this.getValue(tr, '18e');
188 }, 'Total payments'),
189
190 '20': new ComputedLine((tr): number => {
191 return clampToZero(this.getValue(tr, '19') - this.getValue(tr, '16'));
192 }, 'Amount overpaid'),
193
194 '23': new ComputedLine((tr): number => {
195 return clampToZero(this.getValue(tr, '16') - this.getValue(tr, '19'));
196 }, 'Amount you owe'),
197 }
198
199 get filingStatus(): FilingStatus {
200 return this.getInput('filingStatus');
201 }
202 };
203
204 export function computeTax(income: number, filingStatus: FilingStatus): number {
205 // From https://www.irs.gov/pub/irs-drop/rp-18-57.pdf, Section 3.01 and
206 // https://www.irs.gov/pub/irs-pdf/p17.pdf, 2019 Tax Rate Schedules (p254).
207 const taxBrackets = {
208 // Format is:
209 // [ limit-of-taxable-income, marginal-rate, base-tax ]
210 // If Income is over Row[0], pay Row[2] + (Row[1] * (Income - PreviousRow[0]))
211 [FilingStatus.MarriedFilingJoint]: [
212 [ 19400, 0.10, 0 ],
213 [ 78950, 0.12, 1940 ],
214 [ 168400, 0.22, 9086 ],
215 [ 321450, 0.24, 28765 ],
216 [ 408200, 0.32, 65497 ],
217 [ 612350, 0.35, 93257 ],
218 [ Infinity, 0.37, 164709.50 ]
219 ],
220 [FilingStatus.Single]: [
221 [ 9700, 0.10, 0 ],
222 [ 39475, 0.12, 970 ],
223 [ 84200, 0.22, 4543 ],
224 [ 160725, 0.24, 14382.50 ],
225 [ 204100, 0.32, 32748.50 ],
226 [ 510300, 0.35, 46628.50 ],
227 [ Infinity, 0.37, 153798.50 ]
228 ],
229 [FilingStatus.MarriedFilingSeparate]: [
230 [ 9700, 0.10, 0 ],
231 [ 39475, 0.12, 970 ],
232 [ 84200, 0.22, 4543 ],
233 [ 160725, 0.24, 14382.50 ],
234 [ 204100, 0.32, 32748.50 ],
235 [ 306175, 0.35, 46628.50 ],
236 [ Infinity, 0.37, 82354.75 ]
237 ]
238 }[filingStatus];
239
240 let i = 0;
241 while (taxBrackets[i][0] < income)
242 ++i;
243
244 const bracket = taxBrackets[i];
245 const bracketStart = i == 0 ? 0 : taxBrackets[i - 1][0];
246
247 return ((income - bracketStart) * bracket[1]) + bracket[2];
248 };
249
250 export class QDCGTaxWorksheet extends Form<QDCGTaxWorksheet['_lines']> {
251 readonly name = 'QDCG Tax Worksheet';
252
253 protected readonly _lines = {
254 '1': new ReferenceLine(Form1040, '11b', 'Taxable income'),
255 '2': new ReferenceLine(Form1040, '3a', 'Qualified dividends'),
256 '3': new ComputedLine((tr): number => {
257 const schedD = tr.findForm(ScheduleD);
258 if (schedD)
259 return clampToZero(Math.min(schedD.getValue(tr, '15'), schedD.getValue(tr, '16')));
260 return tr.getForm(Form1040).getValue(tr, '6');
261 }),
262 '4': new ComputedLine((tr): number => this.getValue(tr, '2') + this.getValue(tr, '3')),
263 '5': new ComputedLine(() => 0), // Not supported - Form 4952/4g (nvestment interest expense deduction)
264 '6': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '4') - this.getValue(tr, '5'))),
265 '7': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '6'))),
266 '8': new ComputedLine((tr): number => {
267 switch (tr.getForm(Form1040).filingStatus) {
268 case FilingStatus.Single:
269 case FilingStatus.MarriedFilingSeparate:
270 return 39375;
271 case FilingStatus.MarriedFilingJoint:
272 return 78750;
273 };
274 }),
275 '9': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '8'))),
276 '10': new ComputedLine((tr): number => Math.min(this.getValue(tr, '7'), this.getValue(tr, '9'))),
277 '11': new ComputedLine((tr): number => {
278 return this.getValue(tr, '9') - this.getValue(tr, '10');
279 }, 'Amount taxed at 0%'),
280 '12': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '6'))),
281 '13': new ReferenceLine(QDCGTaxWorksheet as any, '11'),
282 '14': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '13')),
283 '15': new ComputedLine((tr): number => {
284 switch (tr.getForm(Form1040).filingStatus) {
285 case FilingStatus.Single: return 434550;
286 case FilingStatus.MarriedFilingSeparate: return 244425;
287 case FilingStatus.MarriedFilingJoint: return 488850;
288 };
289 }),
290 '16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '15'))),
291 '17': new ComputedLine((tr): number => this.getValue(tr, '7') + this.getValue(tr, '11')),
292 '18': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '16') - this.getValue(tr, '17'))),
293 '19': new ComputedLine((tr): number => Math.min(this.getValue(tr, '14'), this.getValue(tr, '18'))),
294 '20': new ComputedLine((tr): number => {
295 return this.getValue(tr, '19') * 0.15;
296 }, 'Amount taxed at 15%'),
297 '21': new ComputedLine((tr): number => this.getValue(tr, '11') + this.getValue(tr, '19')),
298 '22': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '21')),
299 '23': new ComputedLine((tr): number => {
300 return this.getValue(tr, '22') * 0.20;
301 }, 'Amount taxed at 20%'),
302 '24': new ComputedLine((tr): number => {
303 return computeTax(this.getValue(tr, '7'), tr.getForm(Form1040).filingStatus);
304 }, 'Tax on line 7'),
305 '25': new ComputedLine((tr): number => {
306 return this.getValue(tr, '20') +
307 this.getValue(tr, '23') +
308 this.getValue(tr, '24');
309 }),
310 '26': new ComputedLine((tr): number => {
311 return computeTax(this.getValue(tr, '1'), tr.getForm(Form1040).filingStatus);
312 }, 'Tax on line 1'),
313 '27': new ComputedLine((tr): number => {
314 return Math.min(this.getValue(tr, '25'), this.getValue(tr, '26'));
315 }, 'Tax on all taxable income')
316 };
317 };