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