Restructuring in prep for being a usable package.
[ustaxlib.git] / src / fed2019 / Form1040.ts
1 import Form, { FormClass } from '../core/Form';
2 import { TaxReturn } from '../core';
3 import { Line, AccumulatorLine, ComputedLine, ReferenceLine, sumLineOfForms } from '../core/Line';
4 import { UnsupportedFeatureError } from '../core/Errors';
5 import { reduceBySum } from '../core/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 Schedule1 from './Schedule1';
14 import Schedule2 from './Schedule2';
15 import Schedule3 from './Schedule3';
16 import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
17
18 export enum FilingStatus {
19 Single = 'S',
20 MarriedFilingSeparate = 'MFS',
21 MarriedFilingJoint = 'MFJ',
22 };
23
24 export interface Form1040Input {
25 filingStatus: FilingStatus;
26 };
27
28 export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
29 readonly name = '1040';
30
31 protected readonly _lines = {
32 '1': new AccumulatorLine(FormW2, '1', 'Wages, salaries, tips, etc.'),
33 '2a': new AccumulatorLine(Form1099INT, '8', 'Tax-exempt interest'),
34 '2b': new AccumulatorLine(Form1099INT, '1', 'Taxable interest'),
35 '3a': new AccumulatorLine(Form1099DIV, '1b', 'Qualified dividends'),
36 '3b': new AccumulatorLine(Form1099DIV, '1a', 'Ordinary dividends'),
37 '4a': new ComputedLine((tr): number => {
38 const f1099Rs = tr.findForms(Form1099R).filter(f => !f.getValue(tr, '7').includes(Box7Code.G));
39 return sumLineOfForms(tr, f1099Rs, '1');
40 }),
41 '4b': new ComputedLine((tr): number => {
42 const f8606s = tr.findForms(Form8606);
43 return sumLineOfForms(tr, f8606s, '15c') + sumLineOfForms(tr, f8606s, '18');
44 }, 'IRA distributions, taxadble amount'),
45 '4d': new ComputedLine(() => 0),
46 // 4c and 4d are not supported
47 // 5a and 5b are not supported
48 '6': new ComputedLine((tr): number => {
49 const schedD = tr.findForm(ScheduleD);
50 if (!schedD)
51 return 0;
52
53 const l6 = schedD.getValue(tr, '16');
54 if (l6 > 0)
55 return l6;
56 return schedD.getValue(tr, '21');
57 }, 'Capital gain/loss'),
58 '7a': new ReferenceLine(Schedule1, '9', 'Other income from Schedule 1', 0),
59
60 '7b': new ComputedLine((tr): number => {
61 let income = 0;
62 income += this.getValue(tr, '1');
63 income += this.getValue(tr, '2b');
64 income += this.getValue(tr, '3b');
65 income += this.getValue(tr, '4b');
66 income += this.getValue(tr, '4d');
67 //income += this.getValue(tr, '5b');
68 income += this.getValue(tr, '6');
69 income += this.getValue(tr, '7a');
70 return income;
71 }, 'Total income'),
72
73 '8a': new ReferenceLine(Schedule1, '22', 'Adjustments to income', 0),
74
75 '8b': new ComputedLine((tr): number => {
76 return this.getValue(tr, '7b') - this.getValue(tr, '8a');
77 }, 'Adjusted gross income'),
78
79 '9': new ComputedLine((): number => {
80 // TODO - Itemized deductions.
81 switch (this.getInput('filingStatus')) {
82 case FilingStatus.Single:
83 case FilingStatus.MarriedFilingSeparate:
84 return 12200;
85 case FilingStatus.MarriedFilingJoint:
86 return 24400;
87 }
88 }, 'Deduction'),
89
90 '10': new ComputedLine((tr): number => {
91 const taxableIncome = this.getValue(tr, '8b');
92 let use8995a = false;
93 switch (this.getInput('filingStatus')) {
94 case FilingStatus.Single: use8995a = taxableIncome <= 160700; break;
95 case FilingStatus.MarriedFilingSeparate: use8995a = taxableIncome <= 160725; break;
96 case FilingStatus.MarriedFilingJoint: use8995a = taxableIncome <= 321400; break;
97 };
98 return 0;
99 }, 'Qualified business income deduction'),
100
101 '11a': new ComputedLine((tr): number => {
102 return this.getValue(tr, '9') + this.getValue(tr, '10');
103 }),
104 '11b': new ComputedLine((tr): number => {
105 const value = this.getValue(tr, '8b') - this.getValue(tr, '11a');
106 return value < 0 ? 0 : value;
107 }, 'Taxable income'),
108
109 '12a': new ComputedLine((tr): number => {
110 // Not supported:
111 // Form 8814 (election to report child's interest or dividends)
112 // Form 4972 (relating to lump-sum distributions)
113 const taxableIncome = this.getValue(tr, '11b');
114
115 if (this.getValue(tr, '3a') > 0 && !tr.findForm(ScheduleD))
116 throw new UnsupportedFeatureError('Qualified Dividends and Captial Gains Tax Worksheet not supported, Schedule D requried');
117
118 const schedD = tr.findForm(ScheduleDTaxWorksheet);
119 if (schedD)
120 return schedD.getValue(tr, '47');
121
122 return computeTax(taxableIncome, this.getInput('filingStatus'));
123 }, 'Tax'),
124
125 '12b': new ComputedLine((tr): number => {
126 return this.getValue(tr, '12a') + tr.getForm(Schedule2).getValue(tr, '3');
127 }, 'Additional tax'),
128
129 // Not supported: 13a - child tax credit
130
131 '13b': new ReferenceLine(Schedule3, '7', 'Additional credits', 0),
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 // 18a not supported - Earned income credit (EIC)
165 // 18b not supported - Additional child tax credit. Attach Schedule 8812
166 // 18c not supported - American opportunity credit from Form 8863, line 8
167 '18d': new ReferenceLine(Schedule3, '14', undefined, 0),
168 '18e': new ComputedLine((tr): number => {
169 // Should include 18a-18c.
170 return this.getValue(tr, '18d');
171 }),
172
173 '19': new ComputedLine((tr): number => {
174 return this.getValue(tr, '17') + this.getValue(tr, '18e');
175 }, 'Total payments'),
176
177 '20': new ComputedLine((tr): number => {
178 const l16: number = this.getValue(tr, '16');
179 const l19: number = this.getValue(tr, '19');
180 if (l19 > l16)
181 return l19 - l16;
182 return 0;
183 }, 'Amount overpaid'),
184
185 '23': new ComputedLine((tr): number => {
186 const l16 = this.getValue(tr, '16');
187 const l19 = this.getValue(tr, '19');
188 if (l19 < l16)
189 return l16 - l19;
190 return 0;
191 }, 'Amount you owe'),
192 };
193 };
194
195 export function computeTax(income: number, filingStatus: FilingStatus): number {
196 if (income < 100000)
197 throw new UnsupportedFeatureError('Tax-table tax liability not supported');
198
199 switch (filingStatus) {
200 case FilingStatus.Single:
201 if (income < 160725)
202 return (income * 0.24) - 5825.50;
203 else if (income < 204100)
204 return (income * 0.32) - 18683.50;
205 else if (income < 510300)
206 return (income * 0.35) - 24806.50;
207 else
208 return (income * 0.38) - 35012.50;
209 case FilingStatus.MarriedFilingJoint:
210 if (income < 168400)
211 return (income * 0.22) - 8283.00;
212 else if (income < 321450)
213 return (income * 0.24) - 11651.00;
214 else if (income < 408200)
215 return (income * 0.32) - 37367.00;
216 else if (income < 612350)
217 return (income * 0.35) - 49613.00;
218 else
219 return (income * 0.37) - 61860.00;
220 case FilingStatus.MarriedFilingSeparate:
221 if (income < 160725)
222 return (income * 0.24) - 5825.50;
223 else if (income < 204100)
224 return (income * 0.32) - 18683.50;
225 else if (income < 306175)
226 return (income * 0.35) - 24806.50;
227 else
228 return (income * 0.37) - 30930.00;
229 }
230 throw new UnsupportedFeatureError('Unexpected return type');
231 };