Fix bugs in Schedule D and implement the QDCG Tax Worksheet.
authorRobert Sesek <rsesek@bluestatic.org>
Tue, 17 Mar 2020 05:47:52 +0000 (01:47 -0400)
committerRobert Sesek <rsesek@bluestatic.org>
Tue, 17 Mar 2020 05:47:52 +0000 (01:47 -0400)
src/fed2019/Form1040.ts
src/fed2019/Schedule3.test.ts
src/fed2019/ScheduleD.ts

index eacae09fc5703c67d27846f994e15026d8c85c7a..a4ace8723484a41b8797cff89838acfbad50944b 100644 (file)
@@ -59,7 +59,7 @@ export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
         return 0;
 
       const l6 = schedD.getValue(tr, '16');
-      if (l6 > 0)
+      if (l6 >= 0)
         return l6;
       return schedD.getValue(tr, '21');
     }, 'Capital gain/loss'),
@@ -117,16 +117,24 @@ export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
       // Not supported:
       // Form 8814 (election to report child's interest or dividends)
       // Form 4972 (relating to lump-sum distributions)
-      const taxableIncome = this.getValue(tr, '11b');
 
-      if (this.getValue(tr, '3a') > 0 && !tr.findForm(ScheduleD))
-        throw new UnsupportedFeatureError('Qualified Dividends and Captial Gains Tax Worksheet not supported, Schedule D requried');
+      const schedD = tr.findForm(ScheduleD);
+      // Line 18 and 19 are not undefined or 0.
+      if (schedD && !schedD.getValue(tr, '20')) {
+        // Use ScheD tax worksheet;
+        const schedDtw = tr.findForm(ScheduleDTaxWorksheet);
+        if (schedDtw)
+          return schedDtw.getValue(tr, '47');
+      }
 
-      const schedD = tr.findForm(ScheduleDTaxWorksheet);
-      if (schedD)
-        return schedD.getValue(tr, '47');
+      // If there are qualified dividends, use the QDCGTW.
+      if (this.getValue(tr, '3a') > 0) {
+        const qdcgtw = tr.getForm(QDCGTaxWorksheet);
+        return qdcgtw.getValue(tr, '27');
+      }
 
-      return computeTax(taxableIncome, this.filingStatus);
+      // Otherwise, compute just on taxable income.
+      return computeTax(this.getValue(tr, '11b'), this.filingStatus);
     }, 'Tax'),
 
     '12b': new ComputedLine((tr): number => {
@@ -237,3 +245,72 @@ export function computeTax(income: number, filingStatus: FilingStatus): number {
 
   return ((income - bracketStart) * bracket[1]) + bracket[2];
 };
+
+export class QDCGTaxWorksheet extends Form<QDCGTaxWorksheet['_lines']> {
+  readonly name = 'Qual Div Cap Gain Tax Worksheet';
+
+  protected readonly _lines = {
+    '1': new ReferenceLine(Form1040, '11b', 'Taxable income'),
+    '2': new ReferenceLine(Form1040, '3a', 'Qualified dividends'),
+    '3': new ComputedLine((tr): number => {
+      const schedD = tr.findForm(ScheduleD);
+      if (schedD)
+        return Math.min(schedD.getValue(tr, '15'), schedD.getValue(tr, '16'));
+      return tr.getForm(Form1040).getValue(tr, '6');
+    }),
+    '4': new ComputedLine((tr): number => this.getValue(tr, '2') + this.getValue(tr, '3')),
+    '5': new ComputedLine(() => 0), // Not supported - Form 4952/4g (nvestment interest expense deduction)
+    '6': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '4') - this.getValue(tr, '5'))),
+    '7': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '6'))),
+    '8': new ComputedLine((tr): number => {
+      switch (tr.getForm(Form1040).filingStatus) {
+        case FilingStatus.Single:
+        case FilingStatus.MarriedFilingSeparate:
+          return 39375;
+        case FilingStatus.MarriedFilingJoint:
+          return 78750;
+      };
+    }),
+    '9': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '8'))),
+    '10': new ComputedLine((tr): number => Math.min(this.getValue(tr, '7'), this.getValue(tr, '9'))),
+    '11': new ComputedLine((tr): number => {
+      return this.getValue(tr, '9') - this.getValue(tr, '10');
+    }, 'Amount taxed at 0%'),
+    '12': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '6'))),
+    '13': new ReferenceLine(QDCGTaxWorksheet as any, '11'),
+    '14': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '13')),
+    '15': new ComputedLine((tr): number => {
+      switch (tr.getForm(Form1040).filingStatus) {
+        case FilingStatus.Single:                return 434550;
+        case FilingStatus.MarriedFilingSeparate: return 244425;
+        case FilingStatus.MarriedFilingJoint:    return 488850;
+      };
+    }),
+    '16': new ComputedLine((tr): number => Math.min(this.getValue(tr, '1'), this.getValue(tr, '15'))),
+    '17': new ComputedLine((tr): number => this.getValue(tr, '7') + this.getValue(tr, '11')),
+    '18': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '16') - this.getValue(tr, '17'))),
+    '19': new ComputedLine((tr): number => Math.min(this.getValue(tr, '14'), this.getValue(tr, '18'))),
+    '20': new ComputedLine((tr): number => {
+      return this.getValue(tr, '19') * 0.15;
+    }, 'Amount taxed at 15%'),
+    '21': new ComputedLine((tr): number => this.getValue(tr, '11') + this.getValue(tr, '19')),
+    '22': new ComputedLine((tr): number => this.getValue(tr, '12') - this.getValue(tr, '21')),
+    '23': new ComputedLine((tr): number => {
+      return this.getValue(tr, '22') * 0.20;
+    }, 'Amount taxed at 20%'),
+    '24': new ComputedLine((tr): number => {
+      return computeTax(this.getValue(tr, '7'), tr.getForm(Form1040).filingStatus);
+    }, 'Tax on line 7'),
+    '25': new ComputedLine((tr): number => {
+      return this.getValue(tr, '20') +
+             this.getValue(tr, '23') +
+             this.getValue(tr, '24');
+    }),
+    '26': new ComputedLine((tr): number => {
+      return computeTax(this.getValue(tr, '1'), tr.getForm(Form1040).filingStatus);
+    }, 'Tax on line 1'),
+    '27': new ComputedLine((tr): number => {
+      return Math.min(this.getValue(tr, '25'), this.getValue(tr, '26'));
+    }, 'Tax on all taxable income')
+  };
+};
index 668ced6605b49282c1a4ec3366007e025e6059d4..c67915d65040d93c7f5faaade1dd7c981c9915a5 100644 (file)
@@ -6,7 +6,7 @@
 import { Person } from '../core';
 import { NotFoundError } from '../core/Errors';
 
-import Form1040, { FilingStatus } from './Form1040';
+import Form1040, { QDCGTaxWorksheet, FilingStatus } from './Form1040';
 import Form1099DIV from './Form1099DIV';
 import Form1116 from './Form1116';
 import Form8949 from './Form8949';
@@ -28,6 +28,7 @@ test('foreign tax credit, form 1116 not required', () => {
     tr.addForm(new Form1040({ filingStatus }));
     tr.addForm(new Form8949);
     tr.addForm(new ScheduleD);
+    tr.addForm(new QDCGTaxWorksheet);
     tr.addForm(new Form1099DIV({
       payer: 'Brokerage',
       payee: p,
index 94754e8a931bbad945560222bf58ccd63b0850cb..17fd11af73f11ac523d76ae0d1be4e7c5a8f9f16 100644 (file)
@@ -6,11 +6,11 @@
 import { Form, Person, TaxReturn } from '../core';
 import { Line, AccumulatorLine, ComputedLine, ReferenceLine, sumLineOfForms } from '../core/Line';
 import { clampToZero } from '../core/Math';
-import { UnsupportedFeatureError } from '../core/Errors';
+import { NotFoundError, UnsupportedFeatureError } from '../core/Errors';
 
 import Form8949, { Form8949Box } from './Form8949';
 import Form1099DIV from './Form1099DIV';
-import Form1040, { FilingStatus, computeTax } from './Form1040';
+import Form1040, { FilingStatus, QDCGTaxWorksheet, computeTax } from './Form1040';
 
 export default class ScheduleD extends Form<ScheduleD['_lines']> {
   readonly name = 'Schedule D';
@@ -28,7 +28,7 @@ export default class ScheduleD extends Form<ScheduleD['_lines']> {
       return f8949.getValue(tr, 'boxA').gainOrLoss +
              f8949.getValue(tr, 'boxB').gainOrLoss +
              f8949.getValue(tr, 'boxC').gainOrLoss;
-    }, 'Net short-term capital gain or (loss)'),
+    }, 'Net short-term capital gain or loss'),
 
     // 8a is not supported.
 
@@ -46,40 +46,46 @@ export default class ScheduleD extends Form<ScheduleD['_lines']> {
              f8949.getValue(tr, 'boxE').gainOrLoss +
              f8949.getValue(tr, 'boxF').gainOrLoss +
              this.getValue(tr, '13');
-    }, 'Net long-term capital gain or (loss)'),
+    }, 'Net long-term capital gain or loss'),
 
     '16': new ComputedLine((tr): number => {
       return this.getValue(tr, '7') + this.getValue(tr, '15');
-    }),
+      // If value is a gain, enter on 1040/6 and goto 17.
+      // If value is a loss, goto 21 and 22.
+      // If value is zero, enter 0 on 1040/6 and goto 22.
+    }, 'Total capital gain or loss'),
 
     '17': new ComputedLine((tr): boolean => {
       return this.getValue(tr, '15') > 0 && this.getValue(tr, '16') > 0;
+      // If yes, goto 18.
+      // If no, goto 22.
     }, 'Both ST and LT are gains'),
 
     '18': new ComputedLine((tr): number | undefined => {
-      if (!this.getValue(tr, '17') || this.getValue(tr, '16') <= 0)
-        return undefined;
       // Not supported - only for gains on Qualified Small Business Stock or collectibles.
       return 0;
     }, '28% Rate Gain Worksheet Value'),
 
-    // 19 is not supported (Unrecaptured Section 1250 Gain Worksheet)
+    '19': new ComputedLine(() => undefined, 'Unrecaptured Section 1250 Gain Worksheet'), // Not supported.
 
     '20': new ComputedLine((tr): boolean | undefined => {
-      if (!this.getValue(tr, '17') || this.getValue(tr, '16') <= 0)
-        return undefined;
       const l18 = this.getValue(tr, '18');
-      const l19 = undefined; //this.getValue(tr, '19');
+      const l19 = this.getValue(tr, '19');
       return (l18 === 0 || l18 === undefined) || (l19 === 0 || l19 === undefined);
     }, 'Line 18 and 19 both 0 or blank?'),
 
     '21': new ComputedLine((tr): number | undefined => {
-      if (!this.getValue(tr, '17') || !this.getValue(tr, '20'))
-        return undefined;
+      const l16 = this.getValue(tr, '16');
+      if (l16 >= 0)
+        return 0;
       const filingStatus = tr.getForm(Form1040).filingStatus;
       const limit = filingStatus == FilingStatus.MarriedFilingSeparate ? -1500 : -3000;
-      return Math.min(this.getValue(tr, '16'), limit);
+      return Math.max(l16, limit);
     }, 'Net capital loss'),
+
+    '22': new ComputedLine((tr): boolean => {
+      return tr.getForm(Form1040).getValue(tr, '3a') > 0;
+    }, 'Need QD/CG Tax Worksheet'),
   };
 };
 
@@ -105,8 +111,7 @@ export class ScheduleDTaxWorksheet extends Form<ScheduleDTaxWorksheet['_lines']>
     '10': new ComputedLine((tr): number => this.getValue(tr, '6') + this.getValue(tr, '9')),
     '11': new ComputedLine((tr): number => {
       const schedD = tr.getForm(ScheduleD);
-      // TODO - line 19 is not supported.
-      return Math.min(schedD.getValue(tr, '18'), Infinity); //schedD.getValue(tr, '19'));
+      return schedD.getValue(tr, '18') + schedD.getValue(tr, '19');
     }),
     '12': new ComputedLine((tr): number => Math.min(this.getValue(tr, '9'), this.getValue(tr, '11'))),
     '13': new ComputedLine((tr): number => this.getValue(tr, '10') - this.getValue(tr, '12')),