Restructuring in prep for being a usable package.
[ustaxlib.git] / src / fed2019 / Form1116.ts
1 import { Form } from '../core';
2 import { TaxReturn } from '../core';
3 import { Person } from '../core';
4 import { ComputedLine, InputLine, ReferenceLine } from '../core/Line';
5 import { UnsupportedFeatureError } from '../core/Errors';
6 import { reduceBySum } from '../core/Math';
7
8 import Form1040 from './Form1040';
9 import Form8949 from './Form8949';
10 import Schedule2 from './Schedule2';
11 import ScheduleD from './ScheduleD';
12
13 export enum ForeignIncomeCategory {
14 A = 'A: Section 951A category',
15 B = 'B: Foreign branch category',
16 C = 'C: Passive category',
17 D = 'D: General category',
18 E = 'E: Section 901(j)',
19 F = 'F: Certain income re-sourced by treaty',
20 G = 'G: Lump-sum distributions',
21 };
22
23 export interface Form1116Input {
24 person: Person;
25 incomeCategory: ForeignIncomeCategory;
26 posessionName: 'RIC' | string;
27 grossForeignIncome: number;
28 lossesFromForeignSources?: number;
29 totalForeignTaxesPaidOrAccrued: number;
30 };
31
32 class Input<T extends keyof Form1116Input> extends InputLine<Form1116Input, T> {};
33
34 export default class Form1116 extends Form<Form1116['_lines'], Form1116Input> {
35 readonly name = '1116';
36
37 protected readonly _lines = {
38 'category': new ComputedLine((tr: TaxReturn): ForeignIncomeCategory => {
39 const input = this.getInput('incomeCategory');
40 if (input != ForeignIncomeCategory.C)
41 throw new UnsupportedFeatureError(`Form 1116 does not support ${input}`);
42 return input;
43 }),
44 'i': new Input('posessionName'),
45 '1a': new Input('grossForeignIncome'),
46 // 1b not supported - services as an employee.
47 // 2 not supported - Expenses definitely related to the income
48 '3a': new ReferenceLine(Form1040, '9', 'Deductions'),
49 '3b': new ComputedLine(() => 0, 'Other deductions'), // Not supported
50 '3c': new ComputedLine((tr): number => {
51 return this.getValue(tr, '3a') + this.getValue(tr, '3b');
52 }),
53 '3d': new ReferenceLine(Form1116 as any, '1a'), // Should exclude income from unsupported Form 2555.
54 '3e': new ComputedLine((tr): number => {
55 const f1040 = tr.findForm(Form1040);
56 // Take total income, but do not net capital gains out with losses, so remove
57 // line 6.
58 let grossIncome = f1040.getValue(tr, '7b') - f1040.getValue(tr, '6');
59 const f8949 = tr.findForm(Form8949);
60 if (f8949) {
61 const keys: (keyof Form8949['lines'])[] = ['boxA', 'boxB', 'boxC', 'boxD', 'boxE', 'boxF'];
62 const values = keys.map(k => f8949.getValue(tr, k).gainOrLoss).filter(n => n > 0);
63 grossIncome += reduceBySum(values);
64
65 grossIncome += tr.getForm(ScheduleD).getValue(tr, '13');
66 }
67 return grossIncome;
68 }),
69 '3f': new ComputedLine((tr): number => {
70 return Number.parseFloat((this.getValue(tr, '3d') / this.getValue(tr, '3e')).toFixed(4));
71 }),
72 '3g': new ComputedLine((tr): number => {
73 return this.getValue(tr, '3c') * this.getValue(tr, '3f');
74 }),
75 // 4 not supported - Pro rata share of interest expense
76 '5': new Input('lossesFromForeignSources', undefined, 0),
77 '6': new ComputedLine((tr): number => {
78 // Should include 2, 4a, 4b.
79 return this.getValue(tr, '3g') + this.getValue(tr, '5');
80 }),
81 '7': new ComputedLine((tr): number => this.getValue(tr, '1a') - this.getValue(tr, '6')),
82 // Skip the complicated Part II matrix and just use the input value.
83 '8': new Input('totalForeignTaxesPaidOrAccrued'),
84 '9': new ReferenceLine(Form1116 as any, '8'),
85 // 10 not supported - Carryback or carryover
86 '11': new ComputedLine((tr): number => this.getValue(tr, '9') /* + this.getValue(tr, '10') */),
87 // 12 not supported - Reduction in foreign taxes
88 // 13 not supported - Taxes reclassified under high tax kickout
89 '14': new ComputedLine((tr): number => {
90 return this.getValue(tr, '11') /*+
91 this.getValue(tr, '12') +
92 this.getValue(tr, '13')*/;
93 }),
94 '15': new ReferenceLine(Form1116 as any, '7'),
95 // 16 not supported - Adjustments to line 15
96 '17': new ComputedLine((tr): number => this.getValue(tr, '15') /* + this.getValue(tr, '16') */),
97 // TODO - This does not handle necessary adjustments.
98 '18': new ReferenceLine(Form1040, '11b'),
99 '19': new ComputedLine((tr): number => this.getValue(tr, '17') / this.getValue(tr, '18')),
100 '20': new ComputedLine((tr): number => {
101 let value = tr.getForm(Form1040).getValue(tr, '12a');
102 const sched2 = tr.findForm(Schedule2);
103 if (sched2)
104 value += sched2.getValue(tr, '2');
105 return value;
106 }),
107 '21': new ComputedLine((tr): number => this.getValue(tr, '20') * this.getValue(tr, '19'), 'Maximum amount of credit'),
108 '22': new ComputedLine((tr): number => Math.min(this.getValue(tr, '14'), this.getValue(tr, '21'))),
109 // 23-30 not supported (other category F1116)
110 '31': new ReferenceLine(Form1116 as any, '22'),
111 // 32 not supported - Reduction of credit for international boycott operations
112 '33': new ComputedLine((tr): number => this.getValue(tr, '31') /* - this.getValue(tr, '32')*/),
113 };
114 };