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