Include exempt interest dividends in 1040-2a.
[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 AccumulatorLine(Form1099INT, '1', 'Taxable interest'),
43 '3a': new AccumulatorLine(Form1099DIV, '1b', 'Qualified dividends'),
44 '3b': new AccumulatorLine(Form1099DIV, '1a', 'Ordinary dividends'),
45 '4a': new ComputedLine((tr): number => {
46 const f1099Rs = tr.findForms(Form1099R).filter(f => !f.getValue(tr, '7').includes(Box7Code.G));
47 return sumLineOfForms(tr, f1099Rs, '1');
48 }),
49 '4b': new ComputedLine((tr): number => {
50 const f8606s = tr.findForms(Form8606);
51 return sumLineOfForms(tr, f8606s, '15c') + sumLineOfForms(tr, f8606s, '18');
52 }, 'IRA distributions, taxadble amount'),
53 '4d': new ComputedLine(() => 0),
54 // 4c and 4d are not supported
55 // 5a and 5b are not supported
56 '6': new ComputedLine((tr): number => {
57 const schedD = tr.findForm(ScheduleD);
58 if (!schedD)
59 return 0;
60
61 const l6 = schedD.getValue(tr, '16');
62 if (l6 > 0)
63 return l6;
64 return schedD.getValue(tr, '21');
65 }, 'Capital gain/loss'),
66 '7a': new ReferenceLine(Schedule1, '9', 'Other income from Schedule 1', 0),
67
68 '7b': new ComputedLine((tr): number => {
69 let income = 0;
70 income += this.getValue(tr, '1');
71 income += this.getValue(tr, '2b');
72 income += this.getValue(tr, '3b');
73 income += this.getValue(tr, '4b');
74 income += this.getValue(tr, '4d');
75 //income += this.getValue(tr, '5b');
76 income += this.getValue(tr, '6');
77 income += this.getValue(tr, '7a');
78 return income;
79 }, 'Total income'),
80
81 '8a': new ReferenceLine(Schedule1, '22', 'Adjustments to income', 0),
82
83 '8b': new ComputedLine((tr): number => {
84 return this.getValue(tr, '7b') - this.getValue(tr, '8a');
85 }, 'Adjusted gross income'),
86
87 '9': new ComputedLine((): number => {
88 // TODO - Itemized deductions.
89 switch (this.filingStatus) {
90 case FilingStatus.Single:
91 case FilingStatus.MarriedFilingSeparate:
92 return 12200;
93 case FilingStatus.MarriedFilingJoint:
94 return 24400;
95 }
96 }, 'Deduction'),
97
98 '10': new ComputedLine((tr): number => {
99 const taxableIncome = this.getValue(tr, '8b');
100 let use8995a = false;
101 switch (this.filingStatus) {
102 case FilingStatus.Single: use8995a = taxableIncome <= 160700; break;
103 case FilingStatus.MarriedFilingSeparate: use8995a = taxableIncome <= 160725; break;
104 case FilingStatus.MarriedFilingJoint: use8995a = taxableIncome <= 321400; break;
105 };
106 return 0;
107 }, 'Qualified business income deduction'),
108
109 '11a': new ComputedLine((tr): number => {
110 return this.getValue(tr, '9') + this.getValue(tr, '10');
111 }),
112 '11b': new ComputedLine((tr): number => {
113 return clampToZero(this.getValue(tr, '8b') - this.getValue(tr, '11a'));
114 }, 'Taxable income'),
115
116 '12a': new ComputedLine((tr): number => {
117 // Not supported:
118 // Form 8814 (election to report child's interest or dividends)
119 // Form 4972 (relating to lump-sum distributions)
120 const taxableIncome = this.getValue(tr, '11b');
121
122 if (this.getValue(tr, '3a') > 0 && !tr.findForm(ScheduleD))
123 throw new UnsupportedFeatureError('Qualified Dividends and Captial Gains Tax Worksheet not supported, Schedule D requried');
124
125 const schedD = tr.findForm(ScheduleDTaxWorksheet);
126 if (schedD)
127 return schedD.getValue(tr, '47');
128
129 return computeTax(taxableIncome, this.filingStatus);
130 }, 'Tax'),
131
132 '12b': new ComputedLine((tr): number => {
133 return this.getValue(tr, '12a') + tr.getForm(Schedule2).getValue(tr, '3');
134 }, 'Additional tax'),
135
136 // Not supported: 13a - child tax credit
137
138 '13b': new ReferenceLine(Schedule3, '7', 'Additional credits', 0),
139
140 '14': new ComputedLine((tr): number => {
141 return clampToZero(this.getValue(tr, '12b') - this.getValue(tr, '13b'));
142 }),
143
144 '15': new ReferenceLine(Schedule2, '10', undefined, 0),
145
146 '16': new ComputedLine((tr): number => {
147 return this.getValue(tr, '14') + this.getValue(tr, '15');
148 }, 'Total tax'),
149
150 '17': new ComputedLine((tr): number => {
151 const fedTaxWithheldBoxes = [
152 new AccumulatorLine(W2, '2'),
153 new AccumulatorLine(Form1099R, '4'),
154 new AccumulatorLine(Form1099DIV, '4'),
155 new AccumulatorLine(Form1099INT, '4'),
156 ];
157 const withholding: number[] = fedTaxWithheldBoxes.map(b => b.value(tr));
158
159 let additionalMedicare = 0;
160 const f8959 = tr.findForm(Form8959)
161 if (f8959) {
162 additionalMedicare = f8959.getValue(tr, '24');
163 }
164
165 return reduceBySum(withholding) + additionalMedicare;
166 }, 'Federal income tax withheld'),
167
168 // 18a not supported - Earned income credit (EIC)
169 // 18b not supported - Additional child tax credit. Attach Schedule 8812
170 // 18c not supported - American opportunity credit from Form 8863, line 8
171 '18d': new ReferenceLine(Schedule3, '14', undefined, 0),
172 '18e': new ComputedLine((tr): number => {
173 // Should include 18a-18c.
174 return this.getValue(tr, '18d');
175 }),
176
177 '19': new ComputedLine((tr): number => {
178 return this.getValue(tr, '17') + this.getValue(tr, '18e');
179 }, 'Total payments'),
180
181 '20': new ComputedLine((tr): number => {
182 return clampToZero(this.getValue(tr, '19') - this.getValue(tr, '16'));
183 }, 'Amount overpaid'),
184
185 '23': new ComputedLine((tr): number => {
186 return clampToZero(this.getValue(tr, '16') - this.getValue(tr, '19'));
187 }, 'Amount you owe'),
188 }
189
190 get filingStatus(): FilingStatus {
191 return this.getInput('filingStatus');
192 }
193 };
194
195 export function computeTax(income: number, filingStatus: FilingStatus): number {
196 // From https://www.irs.gov/pub/irs-drop/rp-18-57.pdf, Section 3.01 and
197 // https://www.irs.gov/pub/irs-pdf/p17.pdf, 2019 Tax Rate Schedules (p254).
198 const taxBrackets = {
199 // Format is:
200 // [ limit-of-taxable-income, marginal-rate, base-tax ]
201 // If Income is over Row[0], pay Row[2] + (Row[1] * (Income - PreviousRow[0]))
202 [FilingStatus.MarriedFilingJoint]: [
203 [ 19400, 0.10, 0 ],
204 [ 78950, 0.12, 1940 ],
205 [ 168400, 0.22, 9086 ],
206 [ 321450, 0.24, 28765 ],
207 [ 408200, 0.32, 65497 ],
208 [ 612350, 0.35, 93257 ],
209 [ Infinity, 0.37, 164709.50 ]
210 ],
211 [FilingStatus.Single]: [
212 [ 9700, 0.10, 0 ],
213 [ 39475, 0.12, 970 ],
214 [ 84200, 0.22, 4543 ],
215 [ 160725, 0.24, 14382.50 ],
216 [ 204100, 0.32, 32748.50 ],
217 [ 510300, 0.35, 46628.50 ],
218 [ Infinity, 0.37, 153798.50 ]
219 ],
220 [FilingStatus.MarriedFilingSeparate]: [
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 [ 306175, 0.35, 46628.50 ],
227 [ Infinity, 0.37, 82354.75 ]
228 ]
229 }[filingStatus];
230
231 let i = 0;
232 while (taxBrackets[i][0] < income)
233 ++i;
234
235 const bracket = taxBrackets[i];
236 const bracketStart = i == 0 ? 0 : taxBrackets[i - 1][0];
237
238 return ((income - bracketStart) * bracket[1]) + bracket[2];
239 };