Initial work on Schedule D, Form 8949, and 1099-B.
[ustaxlib.git] / src / fed2019 / Form8949.ts
1 import Form from '../Form';
2 import Person from '../Person';
3 import TaxReturn from '../TaxReturn';
4 import { Line, InputLine, ComputedLine, sumLineOfForms } from '../Line';
5
6 import Form1099B, { GainType } from './Form1099B';
7
8 export enum Form8949Box {
9 A = 'A', // Short-term transactions reported on Form(s) 1099-B showing basis was reported to the IRS
10 B = 'B', // Short-term transactions reported on Form(s) 1099-B showing basis wasn’t reported to the IRS
11 C = 'C', // Short-term transactions not reported to you on Form 1099-B
12 D = 'D', // Long-term transactions reported on Form(s) 1099-B showing basis was reported to the IRS
13 E = 'E', // Long-term transactions reported on Form(s) 1099-B showing basis wasn’t reported to the IRS
14 F = 'F', // Long-term transactions not reported to you on Form 1099-B
15 };
16
17 export interface Adjustment {
18 entry: Form1099B;
19 code: string;
20 amount: number;
21 };
22
23 export interface Form8949Input {
24 box: Form8949Box;
25 adjustments?: Adjustment[];
26 };
27
28 function matching1099Bs(tr: TaxReturn, box: Form8949Box): Form1099B[] {
29 return tr.findForms(Form1099B).filter(f => {
30 const gainType: GainType = f.getValue(tr, '2');
31 const basisReported: boolean = f.getValue(tr, '12');
32
33 switch (box) {
34 case Form8949Box.A:
35 return gainType == GainType.ShortTerm && basisReported;
36 case Form8949Box.B:
37 return gainType == GainType.ShortTerm && !basisReported;
38 case Form8949Box.D:
39 return gainType == GainType.LongTerm && basisReported;
40 case Form8949Box.E:
41 return gainType == GainType.LongTerm && !basisReported;
42 };
43
44 return false;
45 });
46 }
47
48 class Form8949Line extends Line<number> {
49 private _box: Form8949Box;
50 private _line: keyof Form1099B['lines'];
51
52 constructor(f8949Functor: () => Form8949, line: keyof Form1099B['lines'], description: string) {
53 const box = f8949Functor().getInput('box');
54 super(`Form 8949 Box ${box} total of 1099-B ${line} (${description})`);
55 this._box = box;
56 this._line = line;
57 }
58
59 value(tr: TaxReturn): number {
60 const f1099bs = matching1099Bs(tr, this._box);
61 return sumLineOfForms(tr, f1099bs, this._line);
62 }
63 };
64
65 export default class Form8949 extends Form<Form8949['_lines'], Form8949Input> {
66 readonly name = '8949';
67
68 readonly supportsMultipleCopies = true;
69
70 protected readonly _lines = {
71 'Box': new InputLine<Form8949Input>('box'),
72 '2(d)': new Form8949Line(() => this, '1d', 'proceeds'),
73 '2(e)': new Form8949Line(() => this, '1e', 'cost'),
74 '2(g)': new ComputedLine((tr: TaxReturn): number => {
75 const f1099bs = matching1099Bs(tr, this.getInput('box'));
76 const adjustments = this.getInput('adjustments').filter((a: Adjustment): boolean => {
77 return f1099bs.includes(a.entry);
78 });
79 return adjustments.reduce((acc, curr) => acc + curr.amount, 0);
80 }, 'adjustments')
81 };
82
83 static addForms(tr: TaxReturn, adjustments: Adjustment[]) {
84 tr.addForm(new Form8949({ box: Form8949Box.A, adjustments }));
85 tr.addForm(new Form8949({ box: Form8949Box.B, adjustments }));
86 tr.addForm(new Form8949({ box: Form8949Box.C, adjustments }));
87 tr.addForm(new Form8949({ box: Form8949Box.D, adjustments }));
88 tr.addForm(new Form8949({ box: Form8949Box.E, adjustments }));
89 tr.addForm(new Form8949({ box: Form8949Box.F, adjustments }));
90 }
91 };
92
93 /*
94 export interface Adjustment {
95 entry: Form1099B;
96 code: string;
97 amount: number;
98 };
99
100 export interface Form8949Input {
101 adjustments?: []Adjustment;
102 };
103
104 export interface Form8949Total {
105 proceeds: number;
106 costBasis: number;
107 adjustmentAmount: number;
108 gainOrLoss: number;
109 };
110
111 class Form8949Line extends Line<Form8949Total> {
112 private _box: Form8949Box;
113
114 constructor(description: string, box: Form8949Input) {
115 super(description);
116 this._box = box;
117 }
118
119 value(tr: TaxReturn): Form8949Total {
120 const lineShortTerm = this._box == Form8949Box.A || this._box == Form8949Box.B || this._box == Form8949Box.C;
121 const lineBasisReported = this._box == Form8949Box.A || this._box == Form8949Box.D;
122
123 const f1099bs = tr.findForms(Form1099B);
124 const relevant1099bs: Form1099B[] = [];
125 for (const form of f1099bs) {
126 const gainType = form.getValue(tr, '2');
127 const basisReported = form.getValue(tr, '12');
128
129 if (lineBasisReported != basisReported)
130 continue;
131
132 if (gainType == GainType.ShortTerm && lineShortTerm) {
133 relevant1099bs.push(form);
134 } else if (gainType == GainType.LongTerm && !lineShortTerm) {
135 relevant1099bs.push(form);
136 }
137 }
138
139 const sumValues = (line: keyof Form1099B['lines']) =>
140 relevant1099bs.map((f: Form1099B): number => f.getValue(tr, line))
141 .reduce((acc, curr) => acc + curr, 0);
142
143 const proceeds = sumValues('1d');
144 const costBasis = sumValues('1e');
145
146 return {
147 proceeds,
148 costBasis,
149 adjustmentAmount: 0,
150 gainOrLoss: costBasis - proceeds,
151 };
152 }
153 };
154
155 export default class Form8949 extends Form<Form8949['_lines'], Form8949Input> {
156 readonly name = '8949';
157
158 protected readonly _lines = {
159 boxATotals: new Form8949Line('Short-term basis reported', Form8949Box.A),
160 boxBTotals: new Form8949Line('Short-term basis NOT reported', Form8949Box.B),
161 boxCTotals: new Form8949Line('Short-term unreported', Form8949Box.C),
162 boxDTotals: new Form8949Line('Long-term basis reported', Form8949Box.D),
163 boxETotals: new Form8949Line('Long-term basis NOT reported', Form8949Box.E),
164 boxFTotals: new Form8949Line('Long-term unreported', Form8949Box.F)
165 };
166 };
167 */