From 4e1e94028ed12de4f29dcf26f66914de966ba7b0 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 8 Mar 2020 13:20:18 -0400 Subject: [PATCH] Add Form 8960. --- src/fed2019/Form8960.test.ts | 128 +++++++++++++++++++++++++++++++++++ src/fed2019/Form8960.ts | 71 +++++++++++++++++++ src/fed2019/Schedule2.ts | 38 +++-------- 3 files changed, 209 insertions(+), 28 deletions(-) create mode 100644 src/fed2019/Form8960.test.ts create mode 100644 src/fed2019/Form8960.ts diff --git a/src/fed2019/Form8960.test.ts b/src/fed2019/Form8960.test.ts new file mode 100644 index 0000000..d77ceba --- /dev/null +++ b/src/fed2019/Form8960.test.ts @@ -0,0 +1,128 @@ +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); + }); + } +}); diff --git a/src/fed2019/Form8960.ts b/src/fed2019/Form8960.ts new file mode 100644 index 0000000..9d73220 --- /dev/null +++ b/src/fed2019/Form8960.ts @@ -0,0 +1,71 @@ +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 { + 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; + } + } +}; diff --git a/src/fed2019/Schedule2.ts b/src/fed2019/Schedule2.ts index aed3406..58863b4 100644 --- a/src/fed2019/Schedule2.ts +++ b/src/fed2019/Schedule2.ts @@ -4,7 +4,10 @@ import { ComputedLine } from '../Line'; 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 { readonly name = 'Schedule 2'; @@ -41,39 +44,19 @@ export default class Schedule2 extends Form { '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; @@ -86,4 +69,3 @@ export default class Schedule2 extends Form { }) }; }; - -- 2.22.5