Add support for ISO exercise and disposition of property to Form6251.
[ustaxlib.git] / src / fed2019 / Form6251.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 { AccumulatorLine, ComputedLine, InputLine, ReferenceLine, UnsupportedLine, sumFormLines } from '../core/Line';
8 import { Literal, clampToZero } from '../core/Math';
9
10 import Form1040, { QDCGTaxWorksheet, FilingStatus } from './Form1040';
11 import Form1099INT from './Form1099INT';
12 import Schedule1 from './Schedule1';
13 import Schedule2 from './Schedule2';
14 import Schedule3 from './Schedule3';
15 import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
16
17 export interface Form6251Input {
18 exerciseOfIncentiveStockOptions?: number;
19 dispositionOfProperty?: number;
20 }
21
22 class Input<T extends keyof Form6251Input> extends InputLine<Form6251Input, T> {}
23
24 export default class Form6251 extends Form<Form6251Input> {
25 readonly name = '6251';
26
27 readonly lines = {
28 // Part I
29 '1': new ComputedLine((tr): number => {
30 const f1040 = tr.getForm(Form1040);
31 const income = f1040.taxableIncome(tr);
32 if (income > 0)
33 return income;
34 return f1040.adjustedGrossIncome(tr) - f1040.deduction(tr) - f1040.qualifiedBusinessIncomeDeduction(tr);
35 }, 'Regular taxable income'),
36 '2a': new ComputedLine((tr): number => {
37 // Not supported: Schedule A, line 7.
38 return tr.getForm(Form1040).deduction(tr);
39 }, 'Deduction'),
40 '2b': new ReferenceLine(Schedule1, '1', 'Tax refund', 0), // Not supported - line 8 SALT.
41 '2c': new UnsupportedLine('Investment interest expense'),
42 '2d': new UnsupportedLine('Depletion'),
43 '2e': new UnsupportedLine('Net operating loss deduction'),
44 '2f': new UnsupportedLine('Alternative tax net operating loss deduction'),
45 '2g': new AccumulatorLine(Form1099INT, '9', 'Interest from specified private activity bonds exempt from the regular tax'),
46 '2h': new UnsupportedLine('Qualified small business stock'),
47 '2i': new Input('exerciseOfIncentiveStockOptions', 'Exercise of incentive stock options', 0),
48 '2j': new UnsupportedLine('Estates and trusts (amount from Schedule K-1 (Form 1041), box 12, code A)'),
49 '2k': new Input('dispositionOfProperty', 'Disposition of property', 0),
50 '2l': new UnsupportedLine('Depreciation on assets placed in service after 1986'),
51 '2m': new UnsupportedLine('Passive activities'),
52 '2n': new UnsupportedLine('Loss limitations'),
53 '2o': new UnsupportedLine('Circulation costs'),
54 '2p': new UnsupportedLine('Long-term contracts'),
55 '2q': new UnsupportedLine('Mining costs'),
56 '2r': new UnsupportedLine('Research and experimental costs'),
57 '2s': new UnsupportedLine('Income from certain installment sales before January 1, 1987'),
58 '2t': new UnsupportedLine('Intangible drilling costs preference'),
59 '3': new UnsupportedLine('Other adjustments'),
60 '4': new ComputedLine((tr): number => {
61 return sumFormLines(tr, this, ['1', '2a', '2b', '2c', '2d', '2e', '2f', '2g', '2h', '2i', '2j', '2k', '2l', '2m', '2n', '2o', '2p', '2q', '2r', '2s', '2t', '3']);
62 }, 'Alternative minimum taxable income'),
63
64 // Part II
65 '5': new ComputedLine((tr): number => {
66 const fs = tr.getForm(Form1040).filingStatus;
67 const exemption = tr.constants.amt.exemption[fs];
68 const phaseout = tr.constants.amt.phaseout[fs];
69
70 const l4 = this.getValue(tr, '4');
71 if (l4 < phaseout)
72 return exemption;
73
74 // Exemption worksheet:
75 const wl1 = exemption;
76 const wl2 = l4;
77 const wl3 = phaseout;
78 const wl4 = clampToZero(wl2 - wl3);
79 const wl5 = wl4 * Literal(0.25);
80 const wl6 = clampToZero(wl1 - wl5);
81 return wl6;
82 }, 'Exemption'),
83 '6': new ComputedLine((tr): number => {
84 return clampToZero(this.getValue(tr, '4') - this.getValue(tr, '5'));
85 }, 'AMT taxable income less exemption'),
86 '7': new ComputedLine((tr): number => {
87 // Not supported - Form 2555.
88 // Not supported - Form1040 directly reporting cap gains on line 6.
89
90 const f1040 = tr.getForm(Form1040);
91
92 let part3 = f1040.qualifiedDividends(tr) > 0;
93
94 const schedD = tr.findForm(ScheduleD);
95 if (schedD) {
96 const flag = schedD.getValue(tr, '15') > 0 && schedD.getValue(tr, '16') > 0;
97 part3 = part3 || flag;
98 }
99
100 if (part3)
101 return this.getValue(tr, '40');
102
103 return computeAmtTax(tr, this.getValue(tr, '6'));
104 }),
105 '8': new ReferenceLine(Schedule3, '1', 'Alternative minimum tax foreign tax credit'), // Not supported - AMT FTC recalculation
106 '9': new ComputedLine((tr): number => {
107 return this.getValue(tr, '7') - this.getValue(tr, '8');
108 }, 'Tentative minimum tax'),
109 '10': new ComputedLine((tr): number => {
110 let value = tr.getForm(Form1040).tax(tr);
111 const sched2 = tr.findForm(Schedule2);
112 if (sched2) {
113 value += sched2.getValue(tr, '2');
114 }
115 // Not supported - subtracting Schedule3@1 for the AMT FTC.
116 return value;
117 }, 'Regular tax, adjusted for AMT'),
118 '11': new ComputedLine((tr): number => {
119 return clampToZero(this.getValue(tr, '9') - this.getValue(tr, '10'));
120 }, 'AMT'),
121
122 // Part III
123 '12': new ReferenceLine(Form6251 as any, '6'),
124 '13': new ComputedLine((tr): number => {
125 const schedDTW = tr.findForm(ScheduleDTaxWorksheet);
126 if (schedDTW)
127 return schedDTW.getValue(tr, '13');
128
129 return tr.getForm(QDCGTaxWorksheet).dividendsAndCapitalGains(tr);
130 }),
131 '14': new ReferenceLine(ScheduleD, '19', undefined, 0),
132 '15': new ComputedLine((tr): number => {
133 const value = this.getValue(tr, '13') + this.getValue(tr, '14');
134 const schedDTW = tr.findForm(ScheduleDTaxWorksheet);
135 if (schedDTW)
136 return Math.min(value, schedDTW.getValue(tr, '10'));
137
138 return value;
139 }),
140 '16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '12'), this.getValue(tr, '15'))),
141 '17': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '16')),
142 '18': new ComputedLine((tr): number => computeAmtTax(tr, this.getValue(tr, '17'))),
143 '19': new ComputedLine((tr): number => {
144 const fs = tr.getForm(Form1040).filingStatus;
145 return tr.constants.capitalGains.rate0MaxIncome[fs];
146 }),
147 '20': new ComputedLine((tr): number => {
148 const schedDTW = tr.findForm(ScheduleDTaxWorksheet);
149 if (schedDTW)
150 return clampToZero(schedDTW.getValue(tr, '14'));
151
152 return clampToZero(tr.getForm(QDCGTaxWorksheet).taxableIncomeLessDividendsAndCapitalGains(tr));
153 }),
154 '21': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '19') - this.getValue(tr, '20'))),
155 '22': new ComputedLine((tr): number => Math.min(this.getValue(tr, '12'), this.getValue(tr, '13'))),
156 '23': new ComputedLine((tr): number => Math.min(this.getValue(tr, '21'), this.getValue(tr, '22'))),
157 '24': new ComputedLine((tr): number => this.getValue(tr, '22') - this.getValue(tr, '23')),
158 '25': new ComputedLine((tr): number => {
159 const fs = tr.getForm(Form1040).filingStatus;
160 return tr.constants.capitalGains.rate15MaxIncome[fs];
161 }),
162 '26': new ReferenceLine(Form6251 as any, '21'),
163 '27': new ComputedLine((tr): number => {
164 const schedDTW = tr.findForm(ScheduleDTaxWorksheet);
165 if (schedDTW)
166 return clampToZero(schedDTW.getValue(tr, '21'));
167
168 return clampToZero(tr.getForm(QDCGTaxWorksheet).taxableIncomeLessDividendsAndCapitalGains(tr));
169 }),
170 '28': new ComputedLine((tr): number => this.getValue(tr, '26') + this.getValue(tr, '27')),
171 '29': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '25') - this.getValue(tr, '28'))),
172 '30': new ComputedLine((tr): number => Math.min(this.getValue(tr, '24'), this.getValue(tr, '29'))),
173 '31': new ComputedLine((tr): number => this.getValue(tr, '30') * 0.15),
174 '32': new ComputedLine((tr): number => this.getValue(tr, '23') + this.getValue(tr, '30')),
175 '33': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '22') - this.getValue(tr, '32'))),
176 '34': new ComputedLine((tr): number => this.getValue(tr, '33') * 0.20),
177 '35': new ComputedLine((tr): number => this.getValue(tr, '17') + this.getValue(tr, '32') + this.getValue(tr, '33')),
178 '36': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '12') - this.getValue(tr, '35'))),
179 '37': new ComputedLine((tr): number => this.getValue(tr, '36') * 0.25),
180 '38': new ComputedLine((tr): number => sumFormLines(tr, this, ['18', '31', '34', '37'])),
181 '39': new ComputedLine((tr): number => computeAmtTax(tr, this.getValue(tr, '12'))),
182 '40': new ComputedLine((tr): number => Math.min(this.getValue(tr, '38'), this.getValue(tr, '39'))),
183 };
184 };
185
186 function computeAmtTax(tr: TaxReturn, amount) {
187 const fs = tr.getForm(Form1040).filingStatus;
188 const limit = tr.constants.amt.limitForRate28Percent[fs];
189 const sub = limit * 0.02; // Difference between the two rates.
190
191 if (amount < limit)
192 return amount * Literal(0.26);
193 return (amount * Literal(0.28)) - sub;
194 }