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