1 import Form from '../Form';
2 import TaxReturn from '../TaxReturn';
3 import { Line, AccumulatorLine, ComputedLine, ReferenceLine } from '../Line';
4 import { UnsupportedFeatureError } from '../Errors';
6 import Form8959 from './Form8959';
8 export enum FilingStatus {
10 MarriedFilingSeparate,
14 export interface Form1040Input {
15 filingStatus: FilingStatus;
18 const reduceBySum = (list: number[]) => list.reduce((acc, curr) => acc + curr, 0);
20 export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
21 readonly name = '1040';
23 protected readonly _lines = {
24 '1': new AccumulatorLine('W-2', '1', 'Wages, salaries, tips, etc.'),
25 '2a': new AccumulatorLine('1099-INT', '8', 'Tax-exempt interest'),
26 '2b': new AccumulatorLine('1099-INT', '1', 'Taxable interest'),
27 '3a': new AccumulatorLine('1099-DIV', '1b', 'Qualified dividends'),
28 '3b': new AccumulatorLine('1099-DIV', '1a', 'Ordinary dividends'),
29 // 4a and 4b are complex
30 '4b': new ComputedLine(() => 0),
31 '4d': new ComputedLine(() => 0),
32 // 4c and 4d are not supported
33 // 5a and 5b are not supported
34 '6': new ReferenceLine<number>('Schedule D', '21', 'Capital gain/loss', 0),
35 '7a': new ReferenceLine<number>('Schedule 1', '9', 'Other income from Schedule 1', 0),
37 '7b': new ComputedLine((tr: TaxReturn): number => {
38 const lineIds = ['1', '2b', '3b', '4b', '4d', /*'5b',*/ '6', '7a'];
39 const lines: number[] = lineIds.map(l => this.getValue(tr, l as keyof Form1040['_lines']));
40 return reduceBySum(lines);
43 '8a': new ReferenceLine<number>('Schedule 1', '22', 'Adjustments to income', 0),
45 '8b': new ComputedLine((tr: TaxReturn): number => {
46 return this.getValue(tr, '7b') - this.getValue(tr, '8a');
47 }, 'Adjusted gross income'),
50 '9': new ComputedLine(() => 0, 'Deduction'),
52 '10': new ComputedLine((tr: TaxReturn): number => {
53 const taxableIncome = this.getValue(tr, '8b');
55 switch (this.getInput('filingStatus')) {
56 case FilingStatus.Single: use8995a = taxableIncome <= 160700; break;
57 case FilingStatus.MarriedFilingSeparate: use8995a = taxableIncome <= 160725; break;
58 case FilingStatus.MarriedFilingJoint: use8995a = taxableIncome <= 321400; break;
61 }, 'Qualified business income deduction'),
63 '11a': new ComputedLine((tr: TaxReturn): number => {
64 return this.getValue(tr, '9') + this.getValue(tr, '10');
66 '11b': new ComputedLine((tr: TaxReturn): number => {
67 const value = this.getValue(tr, '8b') - this.getValue(tr, '11a');
68 return value < 0 ? 0 : value;
71 '12a': new ComputedLine((tr: TaxReturn): number => {
73 // Form 8814 (election to report child's interest or dividends)
74 // Form 4972 (relating to lump-sum distributions)
75 const taxableIncome = this.getValue(tr, '11b');
76 if (taxableIncome < 100000)
77 throw new UnsupportedFeatureError('Tax-table tax liability not supported');
79 const l11b = this.getValue(tr, '11b');
81 switch (this.getInput('filingStatus')) {
82 case FilingStatus.Single:
83 if (taxableIncome < 160725)
84 return (l11b * 0.24) - 5825.50;
85 else if (taxableIncome < 204100)
86 return (l11b * 0.32) - 18683.50;
87 else if (taxableIncome < 510300)
88 return (l11b * 0.35) - 24806.50;
90 return (l11b * 0.38) - 35012.50;
91 case FilingStatus.MarriedFilingJoint:
92 if (taxableIncome < 168400)
93 return (l11b * 0.22) - 8283.00;
94 else if (taxableIncome < 321450)
95 return (l11b * 0.24) - 11651.00;
96 else if (taxableIncome < 408200)
97 return (l11b * 0.32) - 37367.00;
98 else if (taxableIncome < 612350)
99 return (l11b * 0.35) - 49613.00;
101 return (l11b * 0.37) - 61860.00;
102 case FilingStatus.MarriedFilingSeparate:
103 if (taxableIncome < 160725)
104 return (l11b * 0.24) - 5825.50;
105 else if (taxableIncome < 204100)
106 return (l11b * 0.32) - 18683.50;
107 else if (taxableIncome < 306175)
108 return (l11b * 0.35) - 24806.50;
110 return (l11b * 0.37) - 30930.00;
112 throw new UnsupportedFeatureError('Unexpected return type');
115 '12b': new ComputedLine((tr: TaxReturn): number => {
116 return this.getValue(tr, '12a') + tr.getForm<Schedule2>('Schedule 2').getValue(tr, '3');
117 }, 'Additional tax'),
119 // Not supported: 13a - child tax credit
121 '13b': new ComputedLine((tr: TaxReturn): number => {
122 // TODO: add Sched 3.L7
124 }, 'Additional credits'),
126 '14': new ComputedLine((tr: TaxReturn): number => {
127 const l12b = this.getValue(tr, '12b');
128 const l13b = this.getValue(tr, '13b');
129 const value = l12b - l13b;
130 return value < 0 ? 0 : value;
133 '15': new ReferenceLine<number>('Schedule 2', '10', undefined, 0),
135 '16': new ComputedLine((tr: TaxReturn): number => {
136 return this.getValue(tr, '14') + this.getValue(tr, '15');
139 '17': new ComputedLine((tr: TaxReturn): number => {
140 const fedTaxWithheldBoxes = [
141 ['W-2', '2'], ['1099-R', '4'], ['1099-DIV', '4'], ['1099-INT', '4']
143 const withholding = fedTaxWithheldBoxes.map(b => (new AccumulatorLine(b[0], b[1])).value(tr));
145 let additionalMedicare = 0;
146 const f8959 = tr.maybeGetForm('8595')
148 additionalMedicare = f8959.getValue(tr, '24');
151 return reduceBySum(withholding) + additionalMedicare;
152 }, 'Federal income tax withheld'),
156 '19': new ReferenceLine<number>('1040', '17', 'Total payments'),
158 '20': new ComputedLine((tr: TaxReturn): number => {
159 const l16 = this.getValue(tr, '16');
160 const l19 = this.getValue(tr, '19');
164 }, 'Amount overpaid'),
166 '23': new ComputedLine((tr: TaxReturn): number => {
167 const l16 = this.getValue(tr, '16');
168 const l19 = this.getValue(tr, '19');
172 }, 'Amount you owe'),
176 export class Schedule2 extends Form<Schedule2['_lines']> {
177 readonly name = 'Schedule 2';
179 protected readonly _lines = {
180 '1': new ComputedLine((tr: TaxReturn): number => {
181 // TODO - this is just using Taxable Income, rather than AMT-limited
183 const taxableIncome = tr.getForm('1040').getValue(tr, '11b');
184 switch (tr.getForm<Form1040>('1040').getInput('filingStatus')) {
185 case FilingStatus.Single:
186 if (taxableIncome < 510300)
188 case FilingStatus.MarriedFilingJoint:
189 if (taxableIncome < 1020600)
191 case FilingStatus.MarriedFilingSeparate:
192 if (taxableIncome < 510300)
195 throw new UnsupportedFeatureError('The AMT is not supported');
197 // 2 is not supported (Excess advance premium tax credit repayment)
198 '3': new ComputedLine((tr: TaxReturn): number => {
199 // Should include line 2.
200 return this.getValue(tr, '1');
203 // 4 is not supported (Self-employment tax.)
204 // 5 is not supported (Unreported social security and Medicare tax from)
205 // 6 is not supported (Additional tax on IRAs, other qualified retirement plans, and other tax-favored accounts)
206 // 7 is not supported (Household employment taxes.)
207 '8': new ComputedLine((tr: TaxReturn): number => {
208 const f1040 = tr.getForm<Form1040>('1040');
209 const wages = f1040.getLine('1').value(tr);
210 const agi = f1040.getLine('8b').value(tr);
213 const filingStatus = f1040.getInput('filingStatus');
215 const additionalMedicare = wages > Form8959.filingStatusLimit(filingStatus);
217 switch (f1040.getInput('filingStatus')) {
218 case FilingStatus.Single:
219 if (wages > 200000) {
223 case FilingStatus.MarriedFilingJoint:
224 if (wages > 250000) {
228 case FilingStatus.MarriedFilingSeparate:
229 if (wages > 125000) {
237 if (additionalMedicare) {
238 const f8959 = tr.getForm('8959');
239 value += f8959.getValue(tr, '18');
243 const f8960 = tr.getForm('8960');
248 // 9 is not supported (Section 965 net tax liability installment from Form 965-A)
250 '10': new ComputedLine((tr: TaxReturn): number => {
251 // Should be lines 4 - 8.
252 return this.getValue(tr, '8');