--- /dev/null
+import Person from '../Person';
+import TaxReturn from '../TaxReturn';
+
+import FormW2 from './FormW2';
+import Form1040, { FilingStatus } from './Form1040';
+import Form1099B, { GainType } from './Form1099B';
+import Form1099DIV from './Form1099DIV';
+import Form1099INT from './Form1099INT';
+import Form8949 from './Form8949';
+import Form8959 from './Form8959';
+import Form8960 from './Form8960';
+import Schedule2 from './Schedule2';
+import ScheduleD from './ScheduleD';
+
+describe('net investment income tax', () => {
+ const filingStatusToResult = {
+ [FilingStatus.Single]: 105555,
+ [FilingStatus.MarriedFilingJoint]: 55555,
+ [FilingStatus.MarriedFilingSeparate]: 180555,
+ };
+
+ for (const filingStatus of Object.values(FilingStatus)) {
+ test(`filing status ${filingStatus}`, () => {
+ const p = Person.self('A');
+ const tr = new TaxReturn(2019);
+ tr.addForm(new Form1040({ filingStatus }));
+ tr.addForm(new Form1099DIV({
+ payer: 'Brokerage',
+ payee: p,
+ ordinaryDividends: 2000,
+ qualifiedDividends: 1500,
+ totalCapitalGain: 55
+ }));
+ tr.addForm(new Form1099INT({
+ payer: 'Bank',
+ payee: p,
+ interest: 3000
+ }));
+ tr.addForm(new Form1099B({
+ payer: 'Brokerage',
+ payee: p,
+ description: '100 VTI',
+ proceeds: 4000,
+ costBasis: 3500,
+ gainType: GainType.LongTerm,
+ basisReportedToIRS: true
+ }));
+ tr.addForm(new Form8949);
+ tr.addForm(new ScheduleD);
+ tr.addForm(new FormW2({
+ employer: 'Acme',
+ employee: p,
+ wages: 300000,
+ fedIncomeTax: 0,
+ medicareWages: 0,
+ medicareTax: 0,
+ }));
+ tr.addForm(new Form8959);
+ tr.addForm(new Schedule2);
+
+ const f = new Form8960();
+ tr.addForm(f);
+
+ expect(f.getValue(tr, '1')).toBe(3000);
+ expect(f.getValue(tr, '2')).toBe(2000);
+ expect(f.getValue(tr, '5a')).toBe(555);
+ expect(f.getValue(tr, '8')).toBe(5555);
+ expect(f.getValue(tr, '11')).toBe(0);
+ expect(f.getValue(tr, '12')).toBe(5555);
+ expect(f.getValue(tr, '13')).toBe(305555);
+ expect(f.getValue(tr, '14')).toBe(Form8960.filingStatusLimit(filingStatus));
+ expect(f.getValue(tr, '15')).toBe(filingStatusToResult[filingStatus]);
+ expect(f.getValue(tr, '16')).toBe(5555);
+ expect(f.getValue(tr, '17')).toBe(5555 * 0.038);
+
+ expect(tr.getForm(Schedule2).getValue(tr, '8')).toBe(5555 * 0.038);
+ });
+ }
+});
+
+describe('no net investment income tax', () => {
+ for (const filingStatus of Object.values(FilingStatus)) {
+ test(`filing status ${filingStatus}`, () => {
+ const p = Person.self('A');
+ const tr = new TaxReturn(2019);
+ tr.addForm(new Form1040({ filingStatus }));
+ tr.addForm(new Form1099DIV({
+ payer: 'Brokerage',
+ payee: p,
+ ordinaryDividends: 2000,
+ qualifiedDividends: 1500,
+ totalCapitalGain: 55
+ }));
+ tr.addForm(new Form1099INT({
+ payer: 'Bank',
+ payee: p,
+ interest: 3000
+ }));
+ tr.addForm(new Form1099B({
+ payer: 'Brokerage',
+ payee: p,
+ description: '100 VTI',
+ proceeds: 4000,
+ costBasis: 3500,
+ gainType: GainType.LongTerm,
+ basisReportedToIRS: true
+ }));
+ tr.addForm(new Form8949);
+ tr.addForm(new ScheduleD);
+ tr.addForm(new FormW2({
+ employer: 'Acme',
+ employee: p,
+ wages: 70000,
+ fedIncomeTax: 0,
+ medicareWages: 0,
+ medicareTax: 0,
+ }));
+ tr.addForm(new Form8959);
+ tr.addForm(new Schedule2);
+
+ const f = new Form8960();
+ tr.addForm(f);
+
+ expect(f.getValue(tr, '17')).toBe(0);
+ expect(tr.getForm(Schedule2).getValue(tr, '8')).toBe(0);
+ });
+ }
+});
--- /dev/null
+import Form from '../Form';
+import TaxReturn from '../TaxReturn';
+import { ComputedLine, ReferenceLine } from '../Line';
+import { clampToZero } from '../Math';
+
+import Form1040, { FilingStatus } from './Form1040';
+import Schedule1 from './Schedule1';
+
+export default class Form8960 extends Form<Form8960['_lines']> {
+ readonly name = '8960';
+
+ protected readonly _lines = {
+ // Part 1
+ // Section 6013 elections not supported.
+ '1': new ReferenceLine(Form1040, '2b', 'Taxable interest'),
+ '2': new ReferenceLine(Form1040, '3b', 'Ordinary dividends'),
+ // 3 not supported - Annuities
+ // 4a not supported - Rental real estate, royalties, partnerships, S corporations, trusts, etc
+ // 4b not supported - Adjustment for net income or loss derived in the ordinary course of a nonsection 1411 trade or business
+ // 4c not supported - 4a+4b
+ '5a': new ComputedLine((tr): number => {
+ return (new ReferenceLine(Form1040, '6')).value(tr) +
+ (new ReferenceLine(Schedule1, '4', undefined, 0)).value(tr);
+ }, 'Net gain or loss'),
+ // 5b not supported - Net gain or loss from disposition of property that is not subject to net investment income tax
+ // 5c not supported - Adjustment from disposition of partnership interest or S corporation stock
+ '5d': new ComputedLine((tr): number => {
+ // Should include 5b-5c.
+ return this.getValue(tr, '5a');
+ }),
+ // 6 not supported - Adjustments to investment income for certain CFCs and PFICs
+ // 7 not supported - Other modifications to investment income
+ '8': new ComputedLine((tr): number => {
+ return this.getValue(tr, '1') +
+ this.getValue(tr, '2') +
+ /*this.getValue(tr, '3') +
+ this.getValue(tr, '4c') +*/
+ this.getValue(tr, '5d') /*+
+ this.getValue(tr, '6') +
+ this.getValue(tr, '7')*/;
+ }),
+
+ // Part 2
+ // 9a not supported - Investment interest expenses
+ // 9b not supported - State, local, and foreign income tax
+ // 9c not supported - Miscellaneous investment expenses
+ // 9d not supported - 9a+9b+9c
+ // 10 not supported - Additional modifications
+ '11': new ComputedLine(() => 0, 'Total deductions and modifications'), // Not supported - 9d+10.
+
+ // Part 3
+ '12': new ComputedLine((tr): number => this.getValue(tr, '8') - this.getValue(tr, '11'), 'Net investment income'),
+ '13': new ReferenceLine(Form1040, '8b', 'Modified adjusted gross income'),
+ '14': new ComputedLine((tr): number => {
+ return Form8960.filingStatusLimit(tr.getForm(Form1040).getInput('filingStatus'));
+ }, 'Threshold'),
+ '15': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '13') - this.getValue(tr, '14'))),
+ '16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '12'), this.getValue(tr, '15'))),
+ '17': new ComputedLine((tr): number => this.getValue(tr, '16') * 0.038, 'Net investment income tax'),
+
+ // 18 - 21 not supported (Estates and Trusts)
+ };
+
+ static filingStatusLimit(filingStatus: FilingStatus): number {
+ switch (filingStatus) {
+ case FilingStatus.MarriedFilingJoint: return 250000;
+ case FilingStatus.MarriedFilingSeparate: return 125000;
+ case FilingStatus.Single: return 200000;
+ }
+ }
+};
import { UnsupportedFeatureError } from '../Errors';
import Form1040, { FilingStatus } from './Form1040';
+import Form1099DIV from './Form1099DIV';
+import Form1099INT from './Form1099INT';
import Form8959 from './Form8959';
+import Form8960 from './Form8960';
export default class Schedule2 extends Form<Schedule2['_lines']> {
readonly name = 'Schedule 2';
'8': new ComputedLine((tr): number => {
const f1040 = tr.getForm(Form1040);
const wages = f1040.getLine('1').value(tr);
-
- let niit: boolean;
const filingStatus = f1040.getInput('filingStatus');
- const additionalMedicare = wages > Form8959.filingStatusLimit(filingStatus);
-
- switch (f1040.getInput('filingStatus')) {
- case FilingStatus.Single:
- if (wages > 200000) {
- niit = true;
- }
- break;
- case FilingStatus.MarriedFilingJoint:
- if (wages > 250000) {
- niit = true;
- }
- break;
- case FilingStatus.MarriedFilingSeparate:
- if (wages > 125000) {
- niit = true;
- }
- break;
- }
-
let value = 0;
- if (additionalMedicare) {
- const f8959 = tr.getForm(Form8959);
- value += f8959.getValue(tr, '18');
+ // Additional medicare tax.
+ if (wages > Form8959.filingStatusLimit(filingStatus)) {
+ value += tr.getForm(Form8959).getValue(tr, '18');
}
- if (niit) {
- //const f8960 = tr.getForm('8960');
+ // Net investment income tax.
+ if (wages > Form8960.filingStatusLimit(filingStatus) &&
+ (tr.findForms(Form1099DIV).length || tr.findForms(Form1099INT).length)) {
+ value += tr.getForm(Form8960).getValue(tr, '17');
}
return value;
})
};
};
-