Initial work on Schedule D, Form 8949, and 1099-B.
authorRobert Sesek <rsesek@bluestatic.org>
Sun, 23 Feb 2020 05:02:26 +0000 (00:02 -0500)
committerRobert Sesek <rsesek@bluestatic.org>
Sun, 23 Feb 2020 05:02:45 +0000 (00:02 -0500)
src/Line.ts
src/fed2019/Form1040.test.ts
src/fed2019/Form1040.ts
src/fed2019/Form1099B.ts [new file with mode: 0644]
src/fed2019/Form8949.test.ts [new file with mode: 0644]
src/fed2019/Form8949.ts [new file with mode: 0644]
src/fed2019/ScheduleD.ts [new file with mode: 0644]

index e0e974a06c78c97711da86ada6a9a576c83cfabe..8fec28a95763ccaee8dafda4072ce6e0c6366c0a 100644 (file)
@@ -93,7 +93,12 @@ export class AccumulatorLine<F extends Form<any>,
 
   value(tr: TaxReturn): number {
     const forms: F[] = tr.findForms(this._form);
-    const reducer = (acc: number, curr: F) => acc + curr.getValue(tr, this._line);
-    return forms.reduce(reducer, 0);
+    return sumLineOfForms(tr, forms, this._line);
   }
 };
+
+export function sumLineOfForms<F extends Form<any>, L extends keyof F['lines']>(
+    tr: TaxReturn, forms: F[], line: L): number {
+  const reducer = (acc: number, curr: F) => acc + curr.getValue(tr, line);
+  return forms.reduce(reducer, 0);
+}
index 795ddd0170e20b550ea72624d362c3478194c673..7d8b77b64e1561685e0a1123d26868dbad53aa1e 100644 (file)
@@ -4,7 +4,10 @@ import TaxReturn from '../TaxReturn';
 import Form1040, { FilingStatus, Schedule2 } from './Form1040';
 import Form1099DIV from './Form1099DIV';
 import Form1099INT from './Form1099INT';
+import Form1099B, { GainType } from './Form1099B';
+import ScheduleD from './ScheduleD';
 import Form8959 from './Form8959';
+import Form8949 from './Form8949';
 import FormW2 from './FormW2';
 
 test('w2 wages', () => {
@@ -75,3 +78,21 @@ test('dividend income', () => {
   expect(f1040.getValue(tr, '3a')).toBe(75 * 2);
   expect(f1040.getValue(tr, '3b')).toBe(200);
 });
+
+test('capital gain/loss', () => {
+  const p = Person.self('A');
+  const tr = new TaxReturn(2019);
+  tr.addForm(new Form1040({ filingStatus: FilingStatus.Single }));
+  tr.addForm(new Form1099B({
+    payer: 'Brokerage',
+    payee: p,
+    description: '10 FNDC',
+    proceeds: 1000,
+    costBasis: 800,
+    gainType: GainType.LongTerm,
+    basisReportedToIRS: true
+  }));
+  Form8949.addForms(tr, []);
+  tr.addForm(new ScheduleD());
+  tr.getForm(ScheduleD).getValue(tr, '21');
+});
index f1106d86484e2f442fc7af360249de5411e475a6..273b3b85906d07290c4bb6c4fbfd89117dc7f6c5 100644 (file)
@@ -7,6 +7,7 @@ import Form8959 from './Form8959';
 import Form1099INT from './Form1099INT';
 import Form1099DIV from './Form1099DIV';
 import FormW2 from './FormW2';
+import ScheduleD from './ScheduleD';
 
 export enum FilingStatus {
   Single,
@@ -34,7 +35,16 @@ export default class Form1040 extends Form<Form1040['_lines'], Form1040Input> {
     '4d': new ComputedLine(() => 0),
     // 4c and 4d are not supported
     // 5a and 5b are not supported
-    '6': new ReferenceLine(/*'Schedule D'*/ undefined, '21', 'Capital gain/loss', 0),
+    '6': new ComputedLine((tr: TaxReturn): number => {
+      const schedD = tr.findForm(ScheduleD);
+      if (!schedD)
+        return 0;
+
+      const l6 = schedD.getValue(tr, '16');
+      if (l6 > 0)
+        return l6;
+      return schedD.getValue(tr, '21');
+    }, 'Capital gain/loss'),
     '7a': new ReferenceLine(/*'Schedule 1'*/ undefined, '9', 'Other income from Schedule 1', 0),
 
     '7b': new ComputedLine((tr: TaxReturn): number => {
diff --git a/src/fed2019/Form1099B.ts b/src/fed2019/Form1099B.ts
new file mode 100644 (file)
index 0000000..7eb1af8
--- /dev/null
@@ -0,0 +1,76 @@
+import Form from '../Form';
+import Person from '../Person';
+import TaxReturn from '../TaxReturn';
+import { InputLine } from '../Line';
+
+export enum GainType {
+  ShortTerm = 'ST',
+  LongTerm  = 'LT',
+  Ordinary  = 'O',
+};
+
+export interface SpecialProceeds {
+  collectibles?: boolean;
+  qof?: boolean;
+};
+
+export interface IRSReporting {
+  grossProceeds?: boolean;
+  netProceeds?: boolean;
+};
+
+export interface Form1099BInput {
+  payer: string;
+  payee: Person;
+  description: string;
+  dateAcquired?: string;
+  dateSold?: string;
+  proceeds: number;
+  costBasis: number;
+  accruedMarketDiscount?: number;
+  washSaleLossDisallowed?: number;
+  gainType: GainType;
+  specialProceeds?: SpecialProceeds;
+  fedIncomeTax?: number;
+  nonCoveredSecurity?: boolean;
+  irsReporting?: IRSReporting;
+  disallowedLoss?: boolean;
+  profitOnClosedContracts?: number;
+  unrealizedProfitOnOpenContractsCurrentTY?: number;
+  unrealizedProfitOnOpenContractsNextTY?: number;
+  aggregateProfitOnContracts?: number;
+  basisReportedToIRS?: boolean;
+  bartering?: number;
+};
+
+class Input<T extends keyof Form1099BInput> extends InputLine<Form1099BInput, T> {};
+
+export default class Form1099B extends Form<Form1099B['_lines'], Form1099BInput> {
+  readonly name = '1099-B';
+
+  readonly supportsMultipleCopies = true;
+
+  protected readonly _lines = {
+    'payer': new Input('payer'),
+    'recipient': new Input('payee'),
+    '1a': new Input('description'),
+    '1b': new Input('dateAcquired'),
+    '1c': new Input('dateSold'),
+    '1d': new Input('proceeds'),
+    '1e': new Input('costBasis'),
+    '1f': new Input('accruedMarketDiscount'),
+    '1g': new Input('washSaleLossDisallowed'),
+    '2': new Input('gainType'),
+    '3': new Input('specialProceeds'),
+    '4': new Input('fedIncomeTax'),
+    '5': new Input('nonCoveredSecurity'),
+    '6': new Input('irsReporting'),
+    '7': new Input('disallowedLoss'),
+    '8': new Input('profitOnClosedContracts'),
+    '9': new Input('unrealizedProfitOnOpenContractsCurrentTY'),
+    '10': new Input('unrealizedProfitOnOpenContractsNextTY'),
+    '11': new Input('aggregateProfitOnContracts'),
+    '12': new Input('basisReportedToIRS'),
+    '13': new Input('bartering')
+  };
+};
diff --git a/src/fed2019/Form8949.test.ts b/src/fed2019/Form8949.test.ts
new file mode 100644 (file)
index 0000000..0ec6323
--- /dev/null
@@ -0,0 +1,159 @@
+import TaxReturn from '../TaxReturn';
+import Person from '../Person';
+
+import Form1040, { FilingStatus } from './Form1040';
+import Form1099B, { GainType } from './Form1099B';
+import Form8949, { Form8949Box } from './Form8949';
+
+describe('single form', () => {
+  for (const box of [Form8949Box.A, Form8949Box.B, Form8949Box.D, Form8949Box.E]) {
+    test(`box ${Form8949Box[box]}`, () => {
+      const p = Person.self('A');
+      const tr = new TaxReturn(2019);
+      tr.addForm(new Form1040({ filingStatus: FilingStatus.Single }));
+      tr.addForm(new Form1099B({
+        payer: 'Brokerage',
+        payee: p,
+        description: '10 shares',
+        proceeds: 100,
+        costBasis: 110,
+        gainType: (box == Form8949Box.A || box == Form8949Box.B) ? GainType.ShortTerm : GainType.LongTerm,
+        basisReportedToIRS: (box == Form8949Box.A || box == Form8949Box.D),
+      }));
+      Form8949.addForms(tr, []);
+
+      const f8949s = tr.findForms(Form8949);
+      expect(f8949s.length).toBe(6);
+
+      for (let form of f8949s) {
+        if (form.getValue(tr, 'Box') == box) {
+          expect(form.getValue(tr, '2(d)')).toBe(100);
+          expect(form.getValue(tr, '2(e)')).toBe(110);
+          expect(form.getValue(tr, '2(g)')).toBe(0);
+        } else {
+          expect(form.getValue(tr, '2(d)')).toBe(0);
+          expect(form.getValue(tr, '2(e)')).toBe(0);
+          expect(form.getValue(tr, '2(g)')).toBe(0);
+        }
+      }
+    });
+  }
+});
+
+test('multiple forms', () => {
+  const p = Person.self('A');
+  const tr = new TaxReturn(2019);
+  tr.addForm(new Form1040({ filingStatus: FilingStatus.Single }));
+  tr.addForm(new Form1099B({
+    payer: 'Brokerage',
+    payee: p,
+    description: '10 SCHB',
+    proceeds: 55,
+    costBasis: 50,
+    gainType: GainType.ShortTerm,
+    basisReportedToIRS: true,
+  }));
+  tr.addForm(new Form1099B({
+    payer: 'Brokerage',
+    payee: p,
+    description: '10 SCHB',
+    proceeds: 55,
+    costBasis: 50,
+    gainType: GainType.LongTerm,
+    basisReportedToIRS: false,
+  }));
+  tr.addForm(new Form1099B({
+    payer: 'Brokerage',
+    payee: p,
+    description: '10 SCHF',
+    proceeds: 22.40,
+    costBasis: 10.10,
+    gainType: GainType.LongTerm,
+    basisReportedToIRS: false,
+  }));
+  Form8949.addForms(tr, []);
+
+  const f8949s = tr.findForms(Form8949);
+  expect(f8949s.length).toBe(6);
+
+  const boxA = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.A).pop();
+  expect(boxA.getValue(tr, '2(d)')).toBe(55);
+  expect(boxA.getValue(tr, '2(e)')).toBe(50);
+  expect(boxA.getValue(tr, '2(g)')).toBe(0);
+
+  const boxE = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.E).pop();
+  expect(boxE.getValue(tr, '2(d)')).toBe(77.40);
+  expect(boxE.getValue(tr, '2(e)')).toBe(60.10);
+  expect(boxE.getValue(tr, '2(g)')).toBe(0);
+
+  const otherBoxes = f8949s.filter(f => ![Form8949Box.A, Form8949Box.E].includes(f.getValue(tr, 'Box')));
+  for (const other of otherBoxes) {
+    expect(other.getValue(tr, '2(d)')).toBe(0);
+    expect(other.getValue(tr, '2(e)')).toBe(0);
+    expect(other.getValue(tr, '2(g)')).toBe(0);
+  }
+});
+
+test('adjustments', () => {
+  const p = Person.self('A');
+  const tr = new TaxReturn(2019);
+  tr.addForm(new Form1040({ filingStatus: FilingStatus.Single }));
+  const b1 = new Form1099B({
+    payer: 'Brokerage',
+    payee: p,
+    description: '10 SCHB',
+    proceeds: 55,
+    costBasis: 50,
+    gainType: GainType.ShortTerm,
+    basisReportedToIRS: false,
+  });
+  tr.addForm(b1);
+  const b2 = new Form1099B({
+    payer: 'Brokerage',
+    payee: p,
+    description: '10 SCHB',
+    proceeds: 18,
+    costBasis: 25,
+    gainType: GainType.LongTerm,
+    basisReportedToIRS: false,
+  });
+  tr.addForm(b2);
+  tr.addForm(new Form1099B({
+    payer: 'Brokerage',
+    payee: p,
+    description: '10 SCHF',
+    proceeds: 22.40,
+    costBasis: 10.10,
+    gainType: GainType.LongTerm,
+    basisReportedToIRS: true,
+  }));
+  Form8949.addForms(tr, [
+    { entry: b1, code: 'W', amount: -10 },
+    { entry: b2, code: 'W', amount: 90 },
+  ]);
+
+  const f8949s = tr.findForms(Form8949);
+  expect(f8949s.length).toBe(6);
+
+  const boxA = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.B).pop();
+  expect(boxA.getValue(tr, '2(d)')).toBe(55);
+  expect(boxA.getValue(tr, '2(e)')).toBe(50);
+  expect(boxA.getValue(tr, '2(g)')).toBe(-10);
+
+  const boxD = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.D).pop();
+  expect(boxD.getValue(tr, '2(d)')).toBe(22.40);
+  expect(boxD.getValue(tr, '2(e)')).toBe(10.10);
+  expect(boxD.getValue(tr, '2(g)')).toBe(0);
+
+  const boxE = f8949s.filter(f => f.getValue(tr, 'Box') == Form8949Box.E).pop();
+  expect(boxE.getValue(tr, '2(d)')).toBe(18);
+  expect(boxE.getValue(tr, '2(e)')).toBe(25);
+  expect(boxE.getValue(tr, '2(g)')).toBe(90);
+
+  const otherBoxes = f8949s.filter(f => ![Form8949Box.B, Form8949Box.D, Form8949Box.E].includes(f.getValue(tr, 'Box')));
+  for (const other of otherBoxes) {
+    expect(other.getValue(tr, '2(d)')).toBe(0);
+    expect(other.getValue(tr, '2(e)')).toBe(0);
+    expect(other.getValue(tr, '2(g)')).toBe(0);
+  }
+});
diff --git a/src/fed2019/Form8949.ts b/src/fed2019/Form8949.ts
new file mode 100644 (file)
index 0000000..9cd9ed9
--- /dev/null
@@ -0,0 +1,167 @@
+import Form from '../Form';
+import Person from '../Person';
+import TaxReturn from '../TaxReturn';
+import { Line, InputLine, ComputedLine, sumLineOfForms } from '../Line';
+
+import Form1099B, { GainType } from './Form1099B';
+
+export enum Form8949Box {
+  A = 'A', // Short-term transactions reported on Form(s) 1099-B showing basis was reported to the IRS
+  B = 'B', // Short-term transactions reported on Form(s) 1099-B showing basis wasn’t reported to the IRS
+  C = 'C', // Short-term transactions not reported to you on Form 1099-B
+  D = 'D', // Long-term transactions reported on Form(s) 1099-B showing basis was reported to the IRS
+  E = 'E', // Long-term transactions reported on Form(s) 1099-B showing basis wasn’t reported to the IRS
+  F = 'F', // Long-term transactions not reported to you on Form 1099-B
+};
+
+export interface Adjustment {
+  entry: Form1099B;
+  code: string;
+  amount: number;
+};
+
+export interface Form8949Input {
+  box: Form8949Box;
+  adjustments?: Adjustment[];
+};
+
+function matching1099Bs(tr: TaxReturn, box: Form8949Box): Form1099B[] {
+  return tr.findForms(Form1099B).filter(f => {
+    const gainType: GainType = f.getValue(tr, '2');
+    const basisReported: boolean = f.getValue(tr, '12');
+
+    switch (box) {
+      case Form8949Box.A:
+        return gainType == GainType.ShortTerm && basisReported;
+      case Form8949Box.B:
+        return gainType == GainType.ShortTerm && !basisReported;
+      case Form8949Box.D:
+        return gainType == GainType.LongTerm && basisReported;
+      case Form8949Box.E:
+        return gainType == GainType.LongTerm && !basisReported;
+    };
+
+    return false;
+  });
+}
+
+class Form8949Line extends Line<number> {
+  private _box: Form8949Box;
+  private _line: keyof Form1099B['lines'];
+
+  constructor(f8949Functor: () => Form8949, line: keyof Form1099B['lines'], description: string) { 
+    const box = f8949Functor().getInput('box');
+    super(`Form 8949 Box ${box} total of 1099-B ${line} (${description})`);
+    this._box = box;
+    this._line = line;
+  }
+
+  value(tr: TaxReturn): number {
+    const f1099bs = matching1099Bs(tr, this._box);
+    return sumLineOfForms(tr, f1099bs, this._line);
+  }
+};
+
+export default class Form8949 extends Form<Form8949['_lines'], Form8949Input> {
+  readonly name = '8949';
+
+  readonly supportsMultipleCopies = true;
+
+  protected readonly _lines = {
+    'Box': new InputLine<Form8949Input>('box'),
+    '2(d)': new Form8949Line(() => this, '1d', 'proceeds'),
+    '2(e)': new Form8949Line(() => this, '1e', 'cost'),
+    '2(g)': new ComputedLine((tr: TaxReturn): number => {
+      const f1099bs = matching1099Bs(tr, this.getInput('box'));
+      const adjustments = this.getInput('adjustments').filter((a: Adjustment): boolean => {
+        return f1099bs.includes(a.entry);
+      });
+      return adjustments.reduce((acc, curr) => acc + curr.amount, 0);
+    }, 'adjustments')
+  };
+
+  static addForms(tr: TaxReturn, adjustments: Adjustment[]) {
+    tr.addForm(new Form8949({ box: Form8949Box.A, adjustments }));
+    tr.addForm(new Form8949({ box: Form8949Box.B, adjustments }));
+    tr.addForm(new Form8949({ box: Form8949Box.C, adjustments }));
+    tr.addForm(new Form8949({ box: Form8949Box.D, adjustments }));
+    tr.addForm(new Form8949({ box: Form8949Box.E, adjustments }));
+    tr.addForm(new Form8949({ box: Form8949Box.F, adjustments }));
+  }
+};
+
+/*
+export interface Adjustment {
+  entry: Form1099B;
+  code: string;
+  amount: number;
+};
+
+export interface Form8949Input {
+  adjustments?: []Adjustment;
+};
+
+export interface Form8949Total {
+  proceeds: number;
+  costBasis: number;
+  adjustmentAmount: number;
+  gainOrLoss: number;
+};
+
+class Form8949Line extends Line<Form8949Total> {
+  private _box: Form8949Box;
+
+  constructor(description: string, box: Form8949Input) {
+    super(description);
+    this._box = box;
+  }
+
+  value(tr: TaxReturn): Form8949Total {
+    const lineShortTerm = this._box == Form8949Box.A || this._box == Form8949Box.B || this._box == Form8949Box.C;
+    const lineBasisReported = this._box == Form8949Box.A || this._box == Form8949Box.D;
+
+    const f1099bs = tr.findForms(Form1099B);
+    const relevant1099bs: Form1099B[] = [];
+    for (const form of f1099bs) {
+      const gainType = form.getValue(tr, '2');
+      const basisReported = form.getValue(tr, '12');
+
+      if (lineBasisReported != basisReported)
+        continue;
+
+      if (gainType == GainType.ShortTerm && lineShortTerm) {
+        relevant1099bs.push(form);
+      } else if (gainType == GainType.LongTerm && !lineShortTerm) {
+        relevant1099bs.push(form);
+      }
+    }
+
+    const sumValues = (line: keyof Form1099B['lines']) =>
+        relevant1099bs.map((f: Form1099B): number => f.getValue(tr, line))
+          .reduce((acc, curr) => acc + curr, 0);
+
+    const proceeds = sumValues('1d');
+    const costBasis = sumValues('1e');
+
+    return {
+      proceeds,
+      costBasis,
+      adjustmentAmount: 0,
+      gainOrLoss: costBasis - proceeds,
+    };
+  }
+};
+
+export default class Form8949 extends Form<Form8949['_lines'], Form8949Input> {
+  readonly name = '8949';
+
+  protected readonly _lines = {
+    boxATotals: new Form8949Line('Short-term basis reported', Form8949Box.A), 
+    boxBTotals: new Form8949Line('Short-term basis NOT reported', Form8949Box.B), 
+    boxCTotals: new Form8949Line('Short-term unreported', Form8949Box.C), 
+    boxDTotals: new Form8949Line('Long-term basis reported', Form8949Box.D), 
+    boxETotals: new Form8949Line('Long-term basis NOT reported', Form8949Box.E), 
+    boxFTotals: new Form8949Line('Long-term unreported', Form8949Box.F)
+  };
+};
+*/
diff --git a/src/fed2019/ScheduleD.ts b/src/fed2019/ScheduleD.ts
new file mode 100644 (file)
index 0000000..ccf1e34
--- /dev/null
@@ -0,0 +1,131 @@
+import Form from '../Form';
+import Person from '../Person';
+import TaxReturn from '../TaxReturn';
+import { Line, AccumulatorLine, ComputedLine, sumLineOfForms } from '../Line';
+
+import Form8949, { Form8949Box } from './Form8949';
+import Form1099DIV from './Form1099DIV';
+import Form1040, { FilingStatus } from './Form1040';
+
+class ScheduleDTotal extends Line<number> {
+  private _line: keyof Form8949['lines'];
+  private _box: Form8949Box;
+
+  constructor(description: string, line: keyof Form8949['lines'], box: Form8949Box) {
+    super(description);
+    this._line = line;
+    this._box = box;
+  }
+
+  value(tr: TaxReturn): number {
+    const forms: Form8949[] = tr.findForms(Form8949).filter(f => f.getValue(tr, 'Box') == this._box);
+    return sumLineOfForms(tr, forms, this._line);
+  }
+};
+
+export default class ScheduleD extends Form<ScheduleD['_lines']> {
+  readonly name = 'Schedule D';
+
+  protected readonly _lines = {
+    // 1a not supported (Totals for all short-term transactions reported on Form 1099-B for which basis was reported to the IRS and for which you have no adjustments)
+
+    '1b(d)': new ScheduleDTotal('Proceeds of short-term basis reported', '2(d)', Form8949Box.A),
+    '1b(e)': new ScheduleDTotal('Cost basis of short-term basis-reported', '2(e)', Form8949Box.A),
+    '1b(g)': new ScheduleDTotal('Adjustments to short-term basis-reported', '2(g)', Form8949Box.A),
+    '1b(h)': new ComputedLine((tr: TaxReturn): number => {
+      return (this.getValue(tr, '1b(d)') - this.getValue(tr, '1b(e)')) + this.getValue(tr, '1b(g)');
+    }, 'Gain of short-term basis reported'),
+
+    '2(d)': new ScheduleDTotal('Proceeds of short-term basis NOT reported', '2(d)', Form8949Box.B),
+    '2(e)': new ScheduleDTotal('Cost basis of short-term NOT basis-reported', '2(e)', Form8949Box.B),
+    '2(g)': new ScheduleDTotal('Adjustments to short-term NOT basis-reported', '2(g)', Form8949Box.B),
+    '2(h)': new ComputedLine((tr: TaxReturn): number => {
+      return (this.getValue(tr, '2(d)') - this.getValue(tr, '2(e)')) + this.getValue(tr, '2(g)');
+    }, 'Gain of short-term basis NOT reported'),
+
+    '3(d)': new ScheduleDTotal('Proceeds of short-term basis unreported', '2(d)', Form8949Box.C),
+    '3(e)': new ScheduleDTotal('Cost basis of short-term unreported', '2(e)', Form8949Box.C),
+    '3(g)': new ScheduleDTotal('Adjustments to short-term unreported', '2(g)', Form8949Box.C),
+    '3(h)': new ComputedLine((tr: TaxReturn): number => {
+      return (this.getValue(tr, '3(d)') - this.getValue(tr, '3(e)')) + this.getValue(tr, '3(g)');
+    }, 'Gain of short-term unreported'),
+
+    // 4 is not supported (Short-term gain from Form 6252 and short-term gain or (loss) from Forms 4684, 6781, and 8824)
+    // 5 is not supported (Net short-term gain or (loss) from partnerships, S corporations, estates, and trusts from Schedule(s) K-1)
+    // 6 is not supported (Short-term capital loss carryover. Enter the amount, if any, from line 8 of your Capital Loss Carryover Worksheet in the instructions)
+
+    '7': new ComputedLine((tr: TaxReturn): number => {
+      // 4-6 should be included.
+      return this.getValue(tr, '1b(h)') + this.getValue(tr, '2(h)'), this.getValue(tr, '3(h)');
+    }, 'Net short-term capital gain or (loss)'),
+
+    // 8a is not supported.
+
+    '8b(d)': new ScheduleDTotal('Proceeds of long-term basis reported', '2(d)', Form8949Box.D),
+    '8b(e)': new ScheduleDTotal('Cost basis of long-term basis-reported', '2(e)', Form8949Box.D),
+    '8b(g)': new ScheduleDTotal('Adjustments to long-term basis-reported', '2(g)', Form8949Box.D),
+    '8b(h)': new ComputedLine((tr: TaxReturn): number => {
+      return (this.getValue(tr, '8b(d)') - this.getValue(tr, '8b(e)')) + this.getValue(tr, '8b(g)');
+    }, 'Gain of long-term basis reported'),
+
+    '9(d)': new ScheduleDTotal('Proceeds of long-term basis NOT reported', '2(d)', Form8949Box.E),
+    '9(e)': new ScheduleDTotal('Cost basis of long-term NOT basis-reported', '2(e)', Form8949Box.E),
+    '9(g)': new ScheduleDTotal('Adjustments to long-term NOT basis-reported', '2(g)', Form8949Box.E),
+    '9(h)': new ComputedLine((tr: TaxReturn): number => {
+      return (this.getValue(tr, '9(d)') - this.getValue(tr, '9(e)')) + this.getValue(tr, '9(g)');
+    }, 'Gain of long-term basis NOT reported'),
+
+    '10(d)': new ScheduleDTotal('Proceeds of long-term basis unreported', '2(d)', Form8949Box.F),
+    '10(e)': new ScheduleDTotal('Cost basis of long-term unreported', '2(e)', Form8949Box.F),
+    '10(g)': new ScheduleDTotal('Adjustments to long-term unreported', '2(g)', Form8949Box.F),
+    '10(h)': new ComputedLine((tr: TaxReturn): number => {
+      return (this.getValue(tr, '10(d)') - this.getValue(tr, '10(e)')) + this.getValue(tr, '10(g)');
+    }, 'Gain of long-term unreported'),
+
+    // 11 is not supported (Gain from Form 4797, Part I; long-term gain from Forms 2439 and 6252; and long-term gain or (loss) from Forms 4684, 6781, and 8824)
+    // 12 is not supported (Net long-term gain or (loss) from partnerships, S corporations, estates, and trusts from Schedule(s) K-1)
+
+    '13': new AccumulatorLine(Form1099DIV, '2a', 'Capital gain distributions'),
+
+    // 14 is not supported (Long-term capital loss carryover. Enter the amount, if any, from line 13 of your Capital Loss Carryover Worksheet in the instructions)
+
+    '15': new ComputedLine((tr: TaxReturn): number => {
+      // 11-14 should be included.
+      return this.getValue(tr, '8b(h)') + this.getValue(tr, '9(h)') + this.getValue(tr, '10(h)') +
+          this.getValue(tr, '13');
+    }, 'Net long-term capital gain or (loss)'),
+
+    '16': new ComputedLine((tr: TaxReturn): number => {
+      return this.getValue(tr, '7') + this.getValue(tr, '15');
+    }),
+
+    '17': new ComputedLine((tr: TaxReturn): boolean => {
+      return this.getValue(tr, '15') > 0 && this.getValue(tr, '16') > 0;
+    }, 'Both ST and LT are gains'),
+
+    '18': new ComputedLine((tr: TaxReturn): number | undefined => {
+      if (!this.getValue(tr, '17'))
+        return undefined;
+      // TODO
+      return 0;
+    }, '28% Rate Gain Worksheet Value'),
+
+    // 19 is not supported (Unrecaptured Section 1250 Gain Worksheet)
+
+    '20': new ComputedLine((tr: TaxReturn): boolean | undefined => {
+      if (!this.getValue(tr, '17'))
+        return undefined;
+      const l18 = this.getValue(tr, '18');
+      const l19 = undefined; //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: TaxReturn): number | undefined => {
+      if (!this.getValue(tr, '17'))
+        return undefined;
+      const filingStatus = tr.getForm(Form1040).getInput('filingStatus');
+      const limit = filingStatus == FilingStatus.MarriedFilingSeparate ? -1500 : -3000;
+      return Math.min(this.getValue(tr, '16'), limit);
+    }, 'Net capital loss'),
+  };
+};