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