Implement the Schedule D Tax Worksheet.
[ustaxlib.git] / src / fed2019 / Form1040.ts
1 import Form, { FormClass } from '../Form';
2 import TaxReturn from '../TaxReturn';
3 import { Line, AccumulatorLine, ComputedLine, ReferenceLine } from '../Line';
4 import { UnsupportedFeatureError } from '../Errors';
5
6 import Form8959 from './Form8959';
7 import Form1099INT from './Form1099INT';
8 import Form1099DIV from './Form1099DIV';
9 import FormW2 from './FormW2';
10 import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
11
12 export enum FilingStatus {
13 Single,
14 MarriedFilingSeparate,
15 MarriedFilingJoint,
16 };
17
18 export interface Form1040Input {
19 filingStatus: FilingStatus;
20 };
21
22 const reduceBySum = (list: number[]) => list.reduce((acc, curr) => acc + curr, 0);
23
24 export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
25 readonly name = '1040';
26
27 protected readonly _lines = {
28 '1': new AccumulatorLine(FormW2, '1', 'Wages, salaries, tips, etc.'),
29 '2a': new AccumulatorLine(Form1099INT, '8', 'Tax-exempt interest'),
30 '2b': new AccumulatorLine(Form1099INT, '1', 'Taxable interest'),
31 '3a': new AccumulatorLine(Form1099DIV, '1b', 'Qualified dividends'),
32 '3b': new AccumulatorLine(Form1099DIV, '1a', 'Ordinary dividends'),
33 // 4a and 4b are complex
34 '4b': new ComputedLine(() => 0),
35 '4d': new ComputedLine(() => 0),
36 // 4c and 4d are not supported
37 // 5a and 5b are not supported
38 '6': new ComputedLine((tr: TaxReturn): number => {
39 const schedD = tr.findForm(ScheduleD);
40 if (!schedD)
41 return 0;
42
43 const l6 = schedD.getValue(tr, '16');
44 if (l6 > 0)
45 return l6;
46 return schedD.getValue(tr, '21');
47 }, 'Capital gain/loss'),
48 '7a': new ReferenceLine(/*'Schedule 1'*/ undefined, '9', 'Other income from Schedule 1', 0),
49
50 '7b': new ComputedLine((tr: TaxReturn): number => {
51 let income = 0;
52 income += this.getValue(tr, '1');
53 income += this.getValue(tr, '2b');
54 income += this.getValue(tr, '3b');
55 income += this.getValue(tr, '4b');
56 income += this.getValue(tr, '4d');
57 //income += this.getValue(tr, '5b');
58 income += this.getValue(tr, '6');
59 income += this.getValue(tr, '7a');
60 return income;
61 }, 'Total income'),
62
63 '8a': new ReferenceLine(undefined /*'Schedule 1'*/, '22', 'Adjustments to income', 0),
64
65 '8b': new ComputedLine((tr: TaxReturn): number => {
66 return this.getValue(tr, '7b') - this.getValue(tr, '8a');
67 }, 'Adjusted gross income'),
68
69 // TODO - Deduction
70 '9': new ComputedLine(() => 0, 'Deduction'),
71
72 '10': new ComputedLine((tr: TaxReturn): number => {
73 const taxableIncome = this.getValue(tr, '8b');
74 let use8995a = false;
75 switch (this.getInput('filingStatus')) {
76 case FilingStatus.Single: use8995a = taxableIncome <= 160700; break;
77 case FilingStatus.MarriedFilingSeparate: use8995a = taxableIncome <= 160725; break;
78 case FilingStatus.MarriedFilingJoint: use8995a = taxableIncome <= 321400; break;
79 };
80 return 0;
81 }, 'Qualified business income deduction'),
82
83 '11a': new ComputedLine((tr: TaxReturn): number => {
84 return this.getValue(tr, '9') + this.getValue(tr, '10');
85 }),
86 '11b': new ComputedLine((tr: TaxReturn): number => {
87 const value = this.getValue(tr, '8b') - this.getValue(tr, '11a');
88 return value < 0 ? 0 : value;
89 }, 'Taxable income'),
90
91 '12a': new ComputedLine((tr: TaxReturn): number => {
92 // Not supported:
93 // Form 8814 (election to report child's interest or dividends)
94 // Form 4972 (relating to lump-sum distributions)
95 const taxableIncome = this.getValue(tr, '11b');
96
97 if (this.getValue(tr, '3a') > 0 && !tr.findForm(ScheduleD))
98 throw new UnsupportedFeatureError('Qualified Dividends and Captial Gains Tax Worksheet not supported, Schedule D requried');
99
100 const schedD = tr.findForm(ScheduleDTaxWorksheet);
101 if (schedD)
102 return schedD.getValue(tr, '47');
103
104 return computeTax(taxableIncome, this.getInput('filingStatus'));
105 }, 'Tax'),
106
107 '12b': new ComputedLine((tr: TaxReturn): number => {
108 return this.getValue(tr, '12a') + tr.getForm(Schedule2).getValue(tr, '3');
109 }, 'Additional tax'),
110
111 // Not supported: 13a - child tax credit
112
113 '13b': new ComputedLine((tr: TaxReturn): number => {
114 // TODO: add Sched 3.L7
115 return 0;
116 }, 'Additional credits'),
117
118 '14': new ComputedLine((tr: TaxReturn): number => {
119 const l12b = this.getValue(tr, '12b');
120 const l13b = this.getValue(tr, '13b');
121 const value = l12b - l13b;
122 return value < 0 ? 0 : value;
123 }),
124
125 '15': new ReferenceLine(undefined /*'Schedule 2'*/, '10', undefined, 0),
126
127 '16': new ComputedLine((tr: TaxReturn): number => {
128 return this.getValue(tr, '14') + this.getValue(tr, '15');
129 }, 'Total tax'),
130
131 '17': new ComputedLine((tr: TaxReturn): number => {
132 const fedTaxWithheldBoxes = [
133 new AccumulatorLine(FormW2, '2'),
134 //new AccumulatorLine(Form1099R, '4'),
135 new AccumulatorLine(Form1099DIV, '4'),
136 new AccumulatorLine(Form1099INT, '4'),
137 ];
138 const withholding: number[] = fedTaxWithheldBoxes.map(b => b.value(tr));
139
140 let additionalMedicare = 0;
141 const f8959 = tr.findForm(Form8959)
142 if (f8959) {
143 additionalMedicare = f8959.getValue(tr, '24');
144 }
145
146 return reduceBySum(withholding) + additionalMedicare;
147 }, 'Federal income tax withheld'),
148
149 // 18 not supported
150
151 '19': new ReferenceLine(Form1040 as any, '17', 'Total payments'),
152
153 '20': new ComputedLine((tr: TaxReturn): number => {
154 const l16: number = this.getValue(tr, '16');
155 const l19: number = this.getValue(tr, '19');
156 if (l19 > l16)
157 return l19 - l16;
158 return 0;
159 }, 'Amount overpaid'),
160
161 '23': new ComputedLine((tr: TaxReturn): number => {
162 const l16 = this.getValue(tr, '16');
163 const l19 = this.getValue(tr, '19');
164 if (l19 < l16)
165 return l16 - l19;
166 return 0;
167 }, 'Amount you owe'),
168 };
169 };
170
171 export class Schedule2 extends Form<Schedule2['_lines']> {
172 readonly name = 'Schedule 2';
173
174 protected readonly _lines = {
175 '1': new ComputedLine((tr: TaxReturn): number => {
176 // TODO - this is just using Taxable Income, rather than AMT-limited
177 // income
178 const f1040 = tr.getForm(Form1040);
179 const taxableIncome = f1040.getValue(tr, '11b');
180 switch (f1040.getInput('filingStatus')) {
181 case FilingStatus.Single:
182 if (taxableIncome < 510300)
183 return 0;
184 case FilingStatus.MarriedFilingJoint:
185 if (taxableIncome < 1020600)
186 return 0;
187 case FilingStatus.MarriedFilingSeparate:
188 if (taxableIncome < 510300)
189 return 0;
190 }
191 throw new UnsupportedFeatureError('The AMT is not supported');
192 }, 'AMT'),
193 // 2 is not supported (Excess advance premium tax credit repayment)
194 '3': new ComputedLine((tr: TaxReturn): number => {
195 // Should include line 2.
196 return this.getValue(tr, '1');
197 }),
198
199 // 4 is not supported (Self-employment tax.)
200 // 5 is not supported (Unreported social security and Medicare tax from)
201 // 6 is not supported (Additional tax on IRAs, other qualified retirement plans, and other tax-favored accounts)
202 // 7 is not supported (Household employment taxes.)
203 '8': new ComputedLine((tr: TaxReturn): number => {
204 const f1040 = tr.getForm(Form1040);
205 const wages = f1040.getLine('1').value(tr);
206 const agi = f1040.getLine('8b').value(tr);
207
208 let niit: boolean;
209 const filingStatus = f1040.getInput('filingStatus');
210
211 const additionalMedicare = wages > Form8959.filingStatusLimit(filingStatus);
212
213 switch (f1040.getInput('filingStatus')) {
214 case FilingStatus.Single:
215 if (wages > 200000) {
216 niit = true;
217 }
218 break;
219 case FilingStatus.MarriedFilingJoint:
220 if (wages > 250000) {
221 niit = true;
222 }
223 break;
224 case FilingStatus.MarriedFilingSeparate:
225 if (wages > 125000) {
226 niit = true;
227 }
228 break;
229 }
230
231 let value = 0;
232
233 if (additionalMedicare) {
234 const f8959 = tr.getForm(Form8959);
235 value += f8959.getValue(tr, '18');
236 }
237
238 if (niit) {
239 //const f8960 = tr.getForm('8960');
240 }
241
242 return value;
243 }),
244 // 9 is not supported (Section 965 net tax liability installment from Form 965-A)
245
246 '10': new ComputedLine((tr: TaxReturn): number => {
247 // Should be lines 4 - 8.
248 return this.getValue(tr, '8');
249 })
250 };
251 };
252
253 export function computeTax(income: number, filingStatus: FilingStatus): number {
254 if (income < 100000)
255 throw new UnsupportedFeatureError('Tax-table tax liability not supported');
256
257 switch (filingStatus) {
258 case FilingStatus.Single:
259 if (income < 160725)
260 return (income * 0.24) - 5825.50;
261 else if (income < 204100)
262 return (income * 0.32) - 18683.50;
263 else if (income < 510300)
264 return (income * 0.35) - 24806.50;
265 else
266 return (income * 0.38) - 35012.50;
267 case FilingStatus.MarriedFilingJoint:
268 if (income < 168400)
269 return (income * 0.22) - 8283.00;
270 else if (income < 321450)
271 return (income * 0.24) - 11651.00;
272 else if (income < 408200)
273 return (income * 0.32) - 37367.00;
274 else if (income < 612350)
275 return (income * 0.35) - 49613.00;
276 else
277 return (income * 0.37) - 61860.00;
278 case FilingStatus.MarriedFilingSeparate:
279 if (income < 160725)
280 return (income * 0.24) - 5825.50;
281 else if (income < 204100)
282 return (income * 0.32) - 18683.50;
283 else if (income < 306175)
284 return (income * 0.35) - 24806.50;
285 else
286 return (income * 0.37) - 30930.00;
287 }
288 throw new UnsupportedFeatureError('Unexpected return type');
289 };