Fix bugs in Schedule D and implement the QDCG Tax Worksheet.
[ustaxlib.git] / src / fed2019 / ScheduleD.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, Person, TaxReturn } from '../core';
7 import { Line, AccumulatorLine, ComputedLine, ReferenceLine, sumLineOfForms } from '../core/Line';
8 import { clampToZero } from '../core/Math';
9 import { NotFoundError, UnsupportedFeatureError } from '../core/Errors';
10
11 import Form8949, { Form8949Box } from './Form8949';
12 import Form1099DIV from './Form1099DIV';
13 import Form1040, { FilingStatus, QDCGTaxWorksheet, computeTax } from './Form1040';
14
15 export default class ScheduleD extends Form<ScheduleD['_lines']> {
16 readonly name = 'Schedule D';
17
18 protected readonly _lines = {
19 // 1a not supported (Totals for all short-term transactions reported on Form 1099-B for which basis was reported to the IRS and for which you have no adjustments)
20 // 4 is not supported (Short-term gain from Form 6252 and short-term gain or (loss) from Forms 4684, 6781, and 8824)
21 // 5 is not supported (Net short-term gain or (loss) from partnerships, S corporations, estates, and trusts from Schedule(s) K-1)
22 // 6 is not supported (Short-term capital loss carryover. Enter the amount, if any, from line 8 of your Capital Loss Carryover Worksheet in the instructions)
23
24 '7': new ComputedLine((tr): number => {
25 // 1-3 are computed by Form8949.
26 // 4-6 should be included.
27 const f8949 = tr.getForm(Form8949);
28 return f8949.getValue(tr, 'boxA').gainOrLoss +
29 f8949.getValue(tr, 'boxB').gainOrLoss +
30 f8949.getValue(tr, 'boxC').gainOrLoss;
31 }, 'Net short-term capital gain or loss'),
32
33 // 8a is not supported.
34
35 // 11 is not supported (Gain from Form 4797, Part I; long-term gain from Forms 2439 and 6252; and long-term gain or (loss) from Forms 4684, 6781, and 8824)
36 // 12 is not supported (Net long-term gain or (loss) from partnerships, S corporations, estates, and trusts from Schedule(s) K-1)
37
38 '13': new AccumulatorLine(Form1099DIV, '2a', 'Capital gain distributions'),
39
40 // 14 is not supported (Long-term capital loss carryover. Enter the amount, if any, from line 13 of your Capital Loss Carryover Worksheet in the instructions)
41
42 '15': new ComputedLine((tr): number => {
43 // 11-14 should be included.
44 const f8949 = tr.getForm(Form8949);
45 return f8949.getValue(tr, 'boxD').gainOrLoss +
46 f8949.getValue(tr, 'boxE').gainOrLoss +
47 f8949.getValue(tr, 'boxF').gainOrLoss +
48 this.getValue(tr, '13');
49 }, 'Net long-term capital gain or loss'),
50
51 '16': new ComputedLine((tr): number => {
52 return this.getValue(tr, '7') + this.getValue(tr, '15');
53 // If value is a gain, enter on 1040/6 and goto 17.
54 // If value is a loss, goto 21 and 22.
55 // If value is zero, enter 0 on 1040/6 and goto 22.
56 }, 'Total capital gain or loss'),
57
58 '17': new ComputedLine((tr): boolean => {
59 return this.getValue(tr, '15') > 0 && this.getValue(tr, '16') > 0;
60 // If yes, goto 18.
61 // If no, goto 22.
62 }, 'Both ST and LT are gains'),
63
64 '18': new ComputedLine((tr): number | undefined => {
65 // Not supported - only for gains on Qualified Small Business Stock or collectibles.
66 return 0;
67 }, '28% Rate Gain Worksheet Value'),
68
69 '19': new ComputedLine(() => undefined, 'Unrecaptured Section 1250 Gain Worksheet'), // Not supported.
70
71 '20': new ComputedLine((tr): boolean | undefined => {
72 const l18 = this.getValue(tr, '18');
73 const l19 = this.getValue(tr, '19');
74 return (l18 === 0 || l18 === undefined) || (l19 === 0 || l19 === undefined);
75 }, 'Line 18 and 19 both 0 or blank?'),
76
77 '21': new ComputedLine((tr): number | undefined => {
78 const l16 = this.getValue(tr, '16');
79 if (l16 >= 0)
80 return 0;
81 const filingStatus = tr.getForm(Form1040).filingStatus;
82 const limit = filingStatus == FilingStatus.MarriedFilingSeparate ? -1500 : -3000;
83 return Math.max(l16, limit);
84 }, 'Net capital loss'),
85
86 '22': new ComputedLine((tr): boolean => {
87 return tr.getForm(Form1040).getValue(tr, '3a') > 0;
88 }, 'Need QD/CG Tax Worksheet'),
89 };
90 };
91
92 export class ScheduleDTaxWorksheet extends Form<ScheduleDTaxWorksheet['_lines']> {
93 readonly name = 'Schedule D Tax Worksheet';
94
95 protected readonly _lines = {
96 '1': new ReferenceLine(Form1040, '11b'),
97 '2': new ReferenceLine(Form1040, '3a'),
98 // TODO 3 - form 4952
99 // TODO 4 - 4952
100 '5': new ComputedLine((tr): number => 0),
101 '6': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '2') - this.getValue(tr, '5'))),
102 '7': new ComputedLine((tr): number => {
103 const schedD = tr.getForm(ScheduleD);
104 return Math.min(schedD.getValue(tr, '15'), schedD.getValue(tr, '16'));
105 }),
106 '8': new ComputedLine((tr): number => {
107 return 0;
108 // return Math.min(this.getValue(tr, '3'), this.getValue(tr, '4'));
109 }),
110 '9': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '7') - this.getValue(tr, '8'))),
111 '10': new ComputedLine((tr): number => this.getValue(tr, '6') + this.getValue(tr, '9')),
112 '11': new ComputedLine((tr): number => {
113 const schedD = tr.getForm(ScheduleD);
114 return schedD.getValue(tr, '18') + schedD.getValue(tr, '19');
115 }),
116 '12': new ComputedLine((tr): number => Math.min(this.getValue(tr, '9'), this.getValue(tr, '11'))),
117 '13': new ComputedLine((tr): number => this.getValue(tr, '10') - this.getValue(tr, '12')),
118 '14': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '13'))),
119 '15': new ComputedLine((tr): number => {
120 switch (tr.getForm(Form1040).filingStatus) {
121 case FilingStatus.Single:
122 case FilingStatus.MarriedFilingSeparate:
123 return 39375;
124 case FilingStatus.MarriedFilingJoint:
125 return 78750;
126 }
127 }),
128 '16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '15'))),
129 '17': new ComputedLine((tr): number => Math.min(this.getValue(tr, '14'), this.getValue(tr, '16'))),
130 '18': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '10'))),
131 '19': new ComputedLine((tr): number => {
132 let threshold: number;
133 switch (tr.getForm(Form1040).filingStatus) {
134 case FilingStatus.Single:
135 case FilingStatus.MarriedFilingSeparate:
136 threshold = 160725;
137 break;
138 case FilingStatus.MarriedFilingJoint:
139 threshold = 321450;
140 break;
141 }
142 return Math.min(this.getValue(tr, '1'), threshold);
143 }),
144 '20': new ComputedLine((tr): number => Math.min(this.getValue(tr, '14'), this.getValue(tr, '19'))),
145 '21': new ComputedLine((tr): number => Math.max(this.getValue(tr, '18'), this.getValue(tr, '20'))),
146 '22': new ComputedLine((tr): number => this.getValue(tr, '16') - this.getValue(tr, '17')),
147 '23': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '13'))),
148 '24': new ReferenceLine(ScheduleDTaxWorksheet as any, '22'),
149 '25': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '23') - this.getValue(tr, '24'))),
150 '26': new ComputedLine((tr): number => {
151 switch (tr.getForm(Form1040).filingStatus) {
152 case FilingStatus.Single:
153 return 434550;
154 case FilingStatus.MarriedFilingSeparate:
155 return 244425;
156 case FilingStatus.MarriedFilingJoint:
157 return 488850;
158 }
159 }),
160 '27': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '26'))),
161 '28': new ComputedLine((tr): number => this.getValue(tr, '21') + this.getValue(tr, '22')),
162 '29': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '27') - this.getValue(tr, '28'))),
163 '30': new ComputedLine((tr): number => Math.min(this.getValue(tr, '25'), this.getValue(tr, '29'))),
164 '31': new ComputedLine((tr): number => this.getValue(tr, '30') * 0.15),
165 '32': new ComputedLine((tr): number => this.getValue(tr, '24') + this.getValue(tr, '30')),
166 '33': new ComputedLine((tr): number => this.getValue(tr, '23') - this.getValue(tr, '32')),
167 '34': new ComputedLine((tr): number => this.getValue(tr, '33') * 0.20),
168 '35': new ComputedLine((tr): number => {
169 const schedD = tr.getForm(ScheduleD);
170 // TODO - line 19 is not supported.
171 return Math.min(this.getValue(tr, '9'), Infinity); //schedD.getValue(tr, '19'));
172 }),
173 '36': new ComputedLine((tr): number => this.getValue(tr, '10') + this.getValue(tr, '21')),
174 '37': new ReferenceLine(ScheduleDTaxWorksheet as any, '1'),
175 '38': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '36') - this.getValue(tr, '37'))),
176 '39': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '35') - this.getValue(tr, '38'))),
177 '40': new ComputedLine((tr): number => this.getValue(tr, '39') * 0.25),
178 '41': new ComputedLine((tr): number => {
179 const schedD = tr.getForm(ScheduleD);
180 if (schedD.getValue(tr, '18'))
181 throw new UnsupportedFeatureError('28% Gain unsupported');
182 return 0;
183 }),
184 '42': new ComputedLine((tr): number => {
185 if (!tr.getForm(ScheduleD).getValue(tr, '18'))
186 return 0;
187 return this.getValue(tr, '1') - this.getValue(tr, '41');
188 }),
189 '43': new ComputedLine((tr): number => {
190 if (!tr.getForm(ScheduleD).getValue(tr, '18'))
191 return 0;
192 return this.getValue(tr, '42') * 0.28;
193 }),
194 '44': new ComputedLine((tr): number => {
195 const income = this.getValue(tr, '21');
196 return computeTax(income, tr.getForm(Form1040).filingStatus);
197 }),
198 '45': new ComputedLine((tr): number => {
199 return this.getValue(tr, '31') +
200 this.getValue(tr, '34') +
201 this.getValue(tr, '40') +
202 this.getValue(tr, '43') +
203 this.getValue(tr, '44');
204 }),
205 '46': new ComputedLine((tr): number => {
206 const income = this.getValue(tr, '1');
207 return computeTax(income, tr.getForm(Form1040).filingStatus);
208 }),
209 '47': new ComputedLine((tr): number => Math.min(this.getValue(tr, '45'), this.getValue(tr, '46'))),
210 };
211 };