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