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