import Form1099B from './Form1099B';
import Form1099R, { Box7Code } from './Form1099R';
import Schedule2 from './Schedule2';
+import ScheduleA from './ScheduleA';
import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
import Form8606 from './Form8606';
import Form8959 from './Form8959';
expect(f.getValue(tr, '4a')).toBe(6000);
expect(f.getValue(tr, '4b')).toBe(0);
});
+
+test('itemized deductions', () => {
+ const tr = new TaxReturn();
+ const f = new Form1040({
+ filingStatus: FilingStatus.MarriedFilingJoint
+ });
+ tr.addForm(f);
+ tr.addForm(new ScheduleA({
+ charitableGiftsCashOrCheck: 26000
+ }));
+
+ expect(f.getValue(tr, '9')).toBe(26000);
+});
+
+test('itemized deductions, forced', () => {
+ const tr = new TaxReturn();
+ const f = new Form1040({
+ filingStatus: FilingStatus.MarriedFilingJoint
+ });
+ tr.addForm(f);
+ tr.addForm(new ScheduleA({
+ stateAndLocalIncomeAndSalesTaxes: 10000,
+ forceItemize: true
+ }));
+
+ expect(f.getValue(tr, '9')).toBe(10000);
+});
import Schedule1 from './Schedule1';
import Schedule2 from './Schedule2';
import Schedule3 from './Schedule3';
+import ScheduleA from './ScheduleA';
import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD';
export enum FilingStatus {
return this.getValue(tr, '7b') - this.getValue(tr, '8a');
}, 'Adjusted gross income'),
- '9': new ComputedLine((): number => {
- // TODO - Itemized deductions.
+ '9': new ComputedLine((tr): number => {
+ let deduction = 0;
+ const schedA = tr.findForm(ScheduleA);
+ if (schedA) {
+ deduction = schedA.getValue(tr, '17');
+ if (schedA.getValue(tr, '18')) {
+ return deduction;
+ }
+ }
+
switch (this.filingStatus) {
case FilingStatus.Single:
case FilingStatus.MarriedFilingSeparate:
- return 12200;
+ return Math.max(deduction, 12200);
case FilingStatus.MarriedFilingJoint:
- return 24400;
+ return Math.max(deduction, 24400);
}
}, 'Deduction'),
- **Schedule 3:** Additional credits and payments
- **Schedule B:** _This form is not directly modeled, and instead the computations are done on
1040._
+- **Schedule A:** Itemized deductions
- **Schedule D:** Capital gains and losses
- **Form 1099-B:** Proceeds from broker transactions
- **Form 1099-DIV:** Dividend income
--- /dev/null
+// Copyright 2020 Blue Static <https://www.bluestatic.org>
+// This program is free software licensed under the GNU General Public License,
+// version 3.0. The full text of the license can be found in LICENSE.txt.
+// SPDX-License-Identifier: GPL-3.0-only
+
+import { Person } from '../core';
+import { UnsupportedFeatureError } from '../core/Errors';
+
+import Form1040, { FilingStatus } from './Form1040';
+import ScheduleA from './ScheduleA';
+import TaxReturn from './TaxReturn';
+import W2 from './W2';
+
+test('medical and dental expenses, limited', () => {
+ const p = Person.self('A');
+ const tr = new TaxReturn();
+ tr.addPerson(p);
+ tr.addForm(new W2({
+ employee: p,
+ employer: 'E',
+ wages: 3000,
+ }));
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.Single
+ }));
+
+ const f = new ScheduleA({
+ medicalAndDentalExpenses: 250,
+ });
+ tr.addForm(f);
+
+ expect(f.getValue(tr, '2')).toBe(3000);
+ expect(f.getValue(tr, '4')).toBe(25);
+ expect(f.getValue(tr, '17')).toBe(25);
+});
+
+test('medical and dental expenses, excluded', () => {
+ const p = Person.self('A');
+ const tr = new TaxReturn();
+ tr.addPerson(p);
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.Single
+ }));
+ tr.addForm(new W2({
+ employee: p,
+ employer: 'E',
+ wages: 300000,
+ }));
+
+ const f = new ScheduleA({
+ medicalAndDentalExpenses: 250,
+ });
+ tr.addForm(f);
+
+ expect(tr.getForm(Form1040).getValue(tr, '7b')).toBe(300000);
+ expect(f.getValue(tr, '2')).toBe(300000);
+ expect(f.getValue(tr, '4')).toBe(0);
+});
+
+test('state and local taxes, un-limited', () => {
+ const tr = new TaxReturn();
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.MarriedFilingJoint
+ }));
+
+ const f = new ScheduleA({
+ stateAndLocalIncomeAndSalesTaxes: 3000,
+ stateAndLocalRealEstateTaxes: 475,
+ stateAndLocalPropertyTaxes: 225,
+ });
+ tr.addForm(f);
+
+ expect(f.getValue(tr, '5d')).toBe(3700);
+ expect(f.getValue(tr, '5e')).toBe(3700);
+ expect(f.getValue(tr, '17')).toBe(3700);
+});
+
+test('state and local taxes, limited', () => {
+ const tr = new TaxReturn();
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.MarriedFilingJoint
+ }));
+
+ const f = new ScheduleA({
+ stateAndLocalIncomeAndSalesTaxes: 21124,
+ });
+ tr.addForm(f);
+
+ expect(f.getValue(tr, '5d')).toBe(21124);
+ expect(f.getValue(tr, '5e')).toBe(10000);
+ expect(f.getValue(tr, '17')).toBe(10000);
+});
+
+test('charitable gifts', () => {
+ const tr = new TaxReturn();
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.MarriedFilingJoint
+ }));
+
+ const f = new ScheduleA({
+ charitableGiftsCashOrCheck: 3000,
+ charitableGiftsOther: 100,
+ charitableCarryOver: 22,
+ });
+ tr.addForm(f);
+
+ expect(f.getValue(tr, '14')).toBe(3122);
+ expect(f.getValue(tr, '17')).toBe(3122);
+});
+
+test('all inputs', () => {
+ const p = Person.self('A');
+ const tr = new TaxReturn();
+ tr.addPerson(p);
+ tr.addForm(new W2({
+ employee: p,
+ employer: 'E',
+ wages: 3000,
+ }));
+ tr.addForm(new Form1040({
+ filingStatus: FilingStatus.Single
+ }));
+
+ const f = new ScheduleA({
+ medicalAndDentalExpenses: 250,
+ stateAndLocalIncomeAndSalesTaxes: 1000,
+ stateAndLocalRealEstateTaxes: 1000,
+ stateAndLocalPropertyTaxes: 1000,
+ otherTaxes: 1000,
+ unreportedMortgageInterest: 1000,
+ unreportedMortagePoints: 1000,
+ mortgageInsurancePremiums: 1000,
+ investmentInterest: 1000,
+ charitableGiftsCashOrCheck: 1000,
+ charitableGiftsOther: 1000,
+ charitableCarryOver: 1000,
+ casualtyAndTheftLosses: 1000,
+ });
+ tr.addForm(f);
+
+ expect(f.getValue(tr, '17')).toBe(12025);
+});
--- /dev/null
+// Copyright 2020 Blue Static <https://www.bluestatic.org>
+// This program is free software licensed under the GNU General Public License,
+// version 3.0. The full text of the license can be found in LICENSE.txt.
+// SPDX-License-Identifier: GPL-3.0-only
+
+import { Form } from '../core';
+import { ComputedLine, InputLine, ReferenceLine, UnsupportedLine, sumFormLines } from '../core/Line';
+import { clampToZero } from '../core/Math';
+
+import Form1040, { FilingStatus } from './Form1040';
+
+export interface ScheduleAInput {
+ medicalAndDentalExpenses?: number;
+
+ stateAndLocalIncomeAndSalesTaxes?: number;
+ stateAndLocalRealEstateTaxes?: number;
+ stateAndLocalPropertyTaxes?: number;
+ otherTaxes?: number;
+
+ // This form does not apply the mortgage interest limitations.
+ unreportedMortgageInterest?: number;
+ unreportedMortagePoints?: number;
+ mortgageInsurancePremiums?: number;
+ investmentInterest?: number;
+
+ charitableGiftsCashOrCheck?: number;
+ charitableGiftsOther?: number;
+ charitableCarryOver?: number;
+
+ casualtyAndTheftLosses?: number;
+
+ forceItemize?: boolean;
+};
+
+class Input extends InputLine<ScheduleAInput> {};
+
+export default class ScheduleA extends Form<ScheduleA['lines'], ScheduleAInput> {
+ readonly name = 'Schedule A';
+
+ readonly lines = {
+ // Medical and dental expenses
+ '1': new Input('medicalAndDentalExpenses', 'Medical and dental expenses', 0),
+ '2': new ReferenceLine(Form1040, '8b'),
+ '3': new ComputedLine((tr): number => this.getValue(tr, '2') * 0.075),
+ '4': new ComputedLine((tr): number => clampToZero(this.getValue(tr, '1') - this.getValue(tr, '3'))),
+
+ // Taxes you paid
+ '5a': new Input('stateAndLocalIncomeAndSalesTaxes', 'State and local income taxes or general sales taxes', 0),
+ '5b': new Input('stateAndLocalRealEstateTaxes', 'State and local real estate taxes', 0),
+ '5c': new Input('stateAndLocalPropertyTaxes', 'State and local personal property taxes', 0),
+ '5d': new ComputedLine((tr): number => sumFormLines(tr, this, ['5a', '5b', '5c'])),
+ '5e': new ComputedLine((tr): number => {
+ const fs = tr.getForm(Form1040).filingStatus;
+ const limit = fs == FilingStatus.MarriedFilingSeparate ? 5000 : 10000;
+ return Math.min(this.getValue(tr, '5d'), limit);
+ }),
+ '6': new Input('otherTaxes', 'Other taxes', 0),
+ '7': new ComputedLine((tr): number => sumFormLines(tr, this, ['5e', '6'])),
+
+ // Interest you paid
+ // TODO - Form 1098
+ '8a': new UnsupportedLine('Home mortgage interest and points'),
+ '8b': new Input('unreportedMortgageInterest', 'Home mortgage interest not reported on Form 1098', 0),
+ '8c': new Input('unreportedMortagePoints', 'Points not reported on Form 1098', 0),
+ '8d': new Input('mortgageInsurancePremiums', 'Mortgage insurance premiums', 0),
+ '8e': new ComputedLine((tr): number => sumFormLines(tr, this, ['8a', '8b', '8c', '8d'])),
+ '9': new Input('investmentInterest', 'Investment interest', 0),
+ '10': new ComputedLine((tr): number => sumFormLines(tr, this, ['8e', '9'])),
+
+ // Gifts to charity
+ '11': new Input('charitableGiftsCashOrCheck', 'Gifts by cash or check', 0),
+ '12': new Input('charitableGiftsOther', 'Other than by cash or check', 0),
+ '13': new Input('charitableCarryOver', 'Carryover from prior year', 0),
+ '14': new ComputedLine((tr): number => sumFormLines(tr, this, ['11', '12', '13'])),
+
+ '15': new Input('casualtyAndTheftLosses', 'Casualty and theft loss(es)', 0),
+
+ '17': new ComputedLine((tr): number => sumFormLines(tr, this, ['4', '7', '10', '14', '15'])),
+ '18': new Input('forceItemize', 'Itemize even if less than standard deduction', false),
+ };
+};
export { default as Schedule1 } from './Schedule1';
export { default as Schedule2 } from './Schedule2';
export { default as Schedule3 } from './Schedule3';
+export { default as ScheduleA } from './ScheduleA';
export { default as ScheduleD } from './ScheduleD';
export { default as TaxReturn } from './TaxReturn';
export { default as W2 } from './W2';