Add overlay forms for fed2020.
[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, SymbolicLine, UnsupportedLine, sumFormLines, sumLineOfForms } from '../core/Line';
8 import { UnsupportedFeatureError } from '../core/Errors';
9 import { clampToZero, reduceBySum, undefinedToZero } 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 ScheduleA from './ScheduleA';
22 import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
23
24 export enum FilingStatus {
25 Single = 'S',
26 MarriedFilingSeparate = 'MFS',
27 MarriedFilingJoint = 'MFJ',
28 };
29
30 export interface Form1040Input {
31 filingStatus: FilingStatus;
32 };
33
34 export default class Form1040 extends Form<Form1040Input> {
35 readonly name = '1040';
36
37 taxableInterest(tr: TaxReturn): number {
38 const value = (new AccumulatorLine(Form1099INT, '1')).value(tr) +
39 (new AccumulatorLine(Form1099INT, '3')).value(tr);
40 return value;
41 }
42
43 qualifiedDividends(tr: TaxReturn): number {
44 return this.getValue(tr, '3a');
45 }
46
47 adjustedGrossIncome(tr: TaxReturn): number {
48 return this.getValue(tr, '7b') - this.getValue(tr, '8a');
49 }
50
51 deduction(tr: TaxReturn): number {
52 let deduction = 0;
53 const schedA = tr.findForm(ScheduleA);
54 if (schedA) {
55 deduction = schedA.getValue(tr, '17');
56 if (schedA.getValue(tr, '18')) {
57 return deduction;
58 }
59 }
60
61 return Math.max(deduction, tr.constants.standardDeduction[this.filingStatus]);
62 }
63
64 qualifiedBusinessIncomeDeduction(tr: TaxReturn): number {
65 const f8995 = tr.findForm(Form8995REIT);
66 if (f8995)
67 return f8995.getValue(tr, '39');
68 return 0;
69 }
70
71 capitalGainOrLoss(tr: TaxReturn): number {
72 const schedD = tr.findForm(ScheduleD);
73 if (!schedD)
74 return 0;
75
76 const l6 = schedD.getValue(tr, '16');
77 if (l6 >= 0)
78 return l6;
79 return schedD.getValue(tr, '21');
80 }
81
82 totalIncome(tr: TaxReturn): number {
83 return sumFormLines(tr, this, ['1', '2b', '3b', '4b', '4d', '5b', '6', '7a']);
84 }
85
86 taxableIncome(tr: TaxReturn): number {
87 return clampToZero(this.getValue(tr, '8b') - this.getValue(tr, '11a'));
88 }
89
90 tax(tr: TaxReturn): number {
91 // Not supported:
92 // Form 8814 (election to report child's interest or dividends)
93 // Form 4972 (relating to lump-sum distributions)
94
95 const schedD = tr.findForm(ScheduleD);
96 // Line 18 and 19 are not undefined or 0.
97 if (schedD && !schedD.getValue(tr, '20')) {
98 // Use ScheD tax worksheet;
99 const schedDtw = tr.findForm(ScheduleDTaxWorksheet);
100 if (schedDtw)
101 return schedDtw.getValue(tr, '47');
102 }
103
104 // If there are qualified dividends, use the QDCGTW.
105 if (this.qualifiedDividends(tr) > 0) {
106 return tr.getForm(QDCGTaxWorksheet).totalTax(tr);
107 }
108
109 // Otherwise, compute just on taxable income.
110 return computeTax(this.taxableIncome(tr), tr);
111 }
112
113 readonly lines = {
114 '1': new AccumulatorLine(W2, '1', 'Wages, salaries, tips, etc.'),
115 '2a': new ComputedLine((tr): number => {
116 const value = (new AccumulatorLine(Form1099INT, '8')).value(tr) +
117 (new AccumulatorLine(Form1099DIV, '11')).value(tr);
118 return value;
119 }, 'Tax-exempt interest'),
120 '2b': new ComputedLine((tr) => this.taxableInterest(tr), 'Taxable interest'),
121 '3a': new AccumulatorLine(Form1099DIV, '1b', 'Qualified dividends'),
122 '3b': new AccumulatorLine(Form1099DIV, '1a', 'Ordinary dividends'),
123 '4a': new ComputedLine((tr): number => {
124 const f1099Rs = tr.findForms(Form1099R).filter(f => !f.getValue(tr, '7').includes(Box7Code.G));
125 return sumLineOfForms(tr, f1099Rs, '1');
126 }),
127 '4b': new ComputedLine((tr): number => {
128 const f8606s = tr.findForms(Form8606);
129 return sumLineOfForms(tr, f8606s, '15c') + sumLineOfForms(tr, f8606s, '18');
130 }, 'IRA distributions, taxable amount'),
131 '4c': new UnsupportedLine('Pensions and annuities'),
132 '4d': new UnsupportedLine('Pensions and annuities, taxable amount'),
133 '5a': new UnsupportedLine('Social security benefits'),
134 '5b': new UnsupportedLine('Social security benefits, taxable amount'),
135 '6': new ComputedLine((tr) => this.capitalGainOrLoss(tr), 'Capital gain/loss'),
136 '7a': new ReferenceLine(Schedule1, '9', 'Other income from Schedule 1', 0),
137
138 '7b': new ComputedLine((tr) => this.totalIncome(tr), 'Total income'),
139
140 '8a': new ReferenceLine(Schedule1, '22', 'Adjustments to income', 0),
141
142 '8b': new ComputedLine((tr) => this.adjustedGrossIncome(tr), 'Adjusted gross income'),
143
144 '9': new ComputedLine((tr) => this.deduction(tr), 'Deduction'),
145
146 '10': new ComputedLine((tr) => this.qualifiedBusinessIncomeDeduction(tr), 'Qualified business income deduction'),
147
148 '11a': new ComputedLine((tr): number => {
149 return this.getValue(tr, '9') + this.getValue(tr, '10');
150 }),
151 '11b': new ComputedLine((tr) => this.taxableIncome(tr), 'Taxable income'),
152
153 '12a': new ComputedLine((tr) => this.tax(tr), 'Tax'),
154
155 '12b': new ComputedLine((tr): number => {
156 return this.getValue(tr, '12a') + tr.getForm(Schedule2).getValue(tr, '3');
157 }, 'Additional tax'),
158
159 '13a': new UnsupportedLine('Child tax credit'),
160
161 '13b': new ComputedLine((tr): number => {
162 let value: number = this.getValue(tr, '13a');
163 const sched3 = tr.findForm(Schedule3);
164 if (sched3)
165 value += undefinedToZero(sched3.getValue(tr, '7'));
166 return value;
167 }, 'Additional credits'),
168
169 '14': new ComputedLine((tr): number => {
170 return clampToZero(this.getValue(tr, '12b') - this.getValue(tr, '13b'));
171 }),
172
173 '15': new ReferenceLine(Schedule2, '10', undefined, 0),
174
175 '16': new ComputedLine((tr): number => {
176 return this.getValue(tr, '14') + this.getValue(tr, '15');
177 }, 'Total tax'),
178
179 '17': new ComputedLine((tr): number => {
180 const fedTaxWithheldBoxes = [
181 new AccumulatorLine(W2, '2'),
182 new AccumulatorLine(Form1099R, '4'),
183 new AccumulatorLine(Form1099DIV, '4'),
184 new AccumulatorLine(Form1099INT, '4'),
185 ];
186 const withholding: number[] = fedTaxWithheldBoxes.map(b => b.value(tr));
187
188 let additionalMedicare = 0;
189 const f8959 = tr.findForm(Form8959)
190 if (f8959) {
191 additionalMedicare = f8959.getValue(tr, '24');
192 }
193
194 return reduceBySum(withholding) + additionalMedicare;
195 }, 'Federal income tax withheld'),
196
197 '18a': new UnsupportedLine('Earned income credit (EIC)'),
198 '18b': new UnsupportedLine('Additional child tax credit. Attach Schedule 8812'),
199 '18c': new UnsupportedLine('American opportunity credit from Form 8863, line 8'),
200 '18d': new ReferenceLine(Schedule3, '14', undefined, 0),
201 '18e': new ComputedLine((tr): number => {
202 return sumFormLines(tr, this, ['18a', '18b', '18c', '18d']);
203 }),
204
205 '19': new ComputedLine((tr): number => {
206 return this.getValue(tr, '17') + this.getValue(tr, '18e');
207 }, 'Total payments'),
208
209 '20': new ComputedLine((tr): number => {
210 return clampToZero(this.getValue(tr, '19') - this.getValue(tr, '16'));
211 }, 'Amount overpaid'),
212
213 '23': new ComputedLine((tr): number => {
214 return clampToZero(this.getValue(tr, '16') - this.getValue(tr, '19'));
215 }, 'Amount you owe'),
216 }
217
218 get filingStatus(): FilingStatus {
219 return this.getInput('filingStatus');
220 }
221 };
222
223 export function computeTax(income: number, tr: TaxReturn): number {
224 const f1040 = tr.getForm(Form1040);
225 const taxBrackets = tr.constants.taxBrackets[f1040.filingStatus];
226
227 let i = 0;
228 while (taxBrackets[i][0] < income)
229 ++i;
230
231 const bracket = taxBrackets[i];
232 const bracketStart = i == 0 ? 0 : taxBrackets[i - 1][0];
233
234 return ((income - bracketStart) * bracket[1]) + bracket[2];
235 };
236
237 export class QDCGTaxWorksheet extends Form {
238 readonly name = 'QDCG Tax Worksheet';
239
240 dividendsAndCapitalGains(tr: TaxReturn): number {
241 return clampToZero(this.getValue(tr, '4') - this.getValue(tr, '5'));
242 }
243
244 taxableIncomeLessDividendsAndCapitalGains(tr: TaxReturn): number {
245 return clampToZero(this.getValue(tr, '1') - this.getValue(tr, '6'));
246 }
247
248 totalTax(tr: TaxReturn): number {
249 return Math.min(this.getValue(tr, '25'), this.getValue(tr, '26'));
250 }
251
252 readonly lines = {
253 '1': new SymbolicLine(Form1040, 'taxableIncome', 'Taxable income'),
254 '2': new ReferenceLine(Form1040, '3a', 'Qualified dividends'),
255 '3': new ComputedLine((tr): number => {
256 const schedD = tr.findForm(ScheduleD);
257 if (schedD)
258 return clampToZero(Math.min(schedD.getValue(tr, '15'), schedD.getValue(tr, '16')));
259 return tr.getForm(Form1040).capitalGainOrLoss(tr);
260 }),
261 '4': new ComputedLine((tr): number => this.getValue(tr, '2') + this.getValue(tr, '3')),
262 '5': new UnsupportedLine('Form 4952@4g (Investment interest expense deduction)'),
263 '6': new ComputedLine((tr) => this.dividendsAndCapitalGains(tr)),
264 '7': new ComputedLine((tr) => this.taxableIncomeLessDividendsAndCapitalGains(tr)),
265 '8': new ComputedLine((tr): number => {
266 const fs = tr.getForm(Form1040).filingStatus;
267 return tr.constants.capitalGains.rate0MaxIncome[fs];
268 }),
269 '9': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '8'))),
270 '10': new ComputedLine((tr): number => Math.min(this.getValue(tr, '7'), this.getValue(tr, '9'))),
271 '11': new ComputedLine((tr): number => {
272 return this.getValue(tr, '9') - this.getValue(tr, '10');
273 }, 'Amount taxed at 0%'),
274 '12': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '6'))),
275 '13': new ReferenceLine(QDCGTaxWorksheet as any, '11'),
276 '14': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '13')),
277 '15': new ComputedLine((tr): number => {
278 const fs = tr.getForm(Form1040).filingStatus;
279 return tr.constants.capitalGains.rate15MaxIncome[fs];
280 }),
281 '16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '15'))),
282 '17': new ComputedLine((tr): number => this.getValue(tr, '7') + this.getValue(tr, '11')),
283 '18': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '16') - this.getValue(tr, '17'))),
284 '19': new ComputedLine((tr): number => Math.min(this.getValue(tr, '14'), this.getValue(tr, '18'))),
285 '20': new ComputedLine((tr): number => {
286 return this.getValue(tr, '19') * 0.15;
287 }, 'Amount taxed at 15%'),
288 '21': new ComputedLine((tr): number => this.getValue(tr, '11') + this.getValue(tr, '19')),
289 '22': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '21')),
290 '23': new ComputedLine((tr): number => {
291 return this.getValue(tr, '22') * 0.20;
292 }, 'Amount taxed at 20%'),
293 '24': new ComputedLine((tr): number => {
294 return computeTax(this.getValue(tr, '7'), tr);
295 }, 'Tax on line 7'),
296 '25': new ComputedLine((tr): number => {
297 return this.getValue(tr, '20') +
298 this.getValue(tr, '23') +
299 this.getValue(tr, '24');
300 }),
301 '26': new ComputedLine((tr): number => {
302 return computeTax(this.getValue(tr, '1'), tr);
303 }, 'Tax on line 1'),
304 '27': new ComputedLine((tr) => this.totalTax(tr), 'Tax on all taxable income')
305 };
306 };