Repository: thegrims/UsTaxes Branch: master Commit: 3bba83198751 Files: 522 Total size: 1.2 MB Directory structure: gitextract_fz9tnrwo/ ├── .codecov.yml ├── .dockerignore ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── ci.yml │ └── tauri-release.yml ├── .gitignore ├── .husky/ │ ├── husky.sh │ └── pre-commit ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── Dockerfile ├── LICENSE ├── README.md ├── craco.config.js ├── docker-compose.yml ├── docs/ │ ├── ARCHITECTURE.md │ ├── CODE_OF_CONDUCT.md │ └── CONTRIBUTING.md ├── notice.js ├── package.json ├── public/ │ ├── _redirects │ ├── forms/ │ │ ├── Y2020/ │ │ │ └── states/ │ │ │ ├── AK/ │ │ │ │ └── README.md │ │ │ ├── AL/ │ │ │ │ └── README.md │ │ │ ├── AR/ │ │ │ │ └── README.md │ │ │ ├── AZ/ │ │ │ │ └── README.md │ │ │ ├── CA/ │ │ │ │ └── README.md │ │ │ ├── CO/ │ │ │ │ └── README.md │ │ │ ├── CT/ │ │ │ │ └── README.md │ │ │ ├── DC/ │ │ │ │ └── README.md │ │ │ ├── DE/ │ │ │ │ └── README.md │ │ │ ├── FL/ │ │ │ │ └── README.md │ │ │ ├── GA/ │ │ │ │ └── README.md │ │ │ ├── HI/ │ │ │ │ └── README.md │ │ │ ├── IA/ │ │ │ │ └── README.md │ │ │ ├── ID/ │ │ │ │ └── README.md │ │ │ ├── IL/ │ │ │ │ └── README.md │ │ │ ├── IN/ │ │ │ │ └── README.md │ │ │ ├── KS/ │ │ │ │ └── README.md │ │ │ ├── KY/ │ │ │ │ └── README.md │ │ │ ├── LA/ │ │ │ │ └── README.md │ │ │ ├── MA/ │ │ │ │ └── README.md │ │ │ ├── MD/ │ │ │ │ └── README.md │ │ │ ├── ME/ │ │ │ │ └── README.md │ │ │ ├── MI/ │ │ │ │ └── README.md │ │ │ ├── MN/ │ │ │ │ └── README.md │ │ │ ├── MO/ │ │ │ │ └── README.md │ │ │ ├── MS/ │ │ │ │ └── README.md │ │ │ ├── MT/ │ │ │ │ └── README.md │ │ │ ├── NC/ │ │ │ │ └── README.md │ │ │ ├── ND/ │ │ │ │ └── README.md │ │ │ ├── NE/ │ │ │ │ └── README.md │ │ │ ├── NH/ │ │ │ │ └── README.md │ │ │ ├── NJ/ │ │ │ │ └── README.md │ │ │ ├── NM/ │ │ │ │ └── README.md │ │ │ ├── NV/ │ │ │ │ └── README.md │ │ │ ├── NY/ │ │ │ │ └── README.md │ │ │ ├── OH/ │ │ │ │ └── README.md │ │ │ ├── OK/ │ │ │ │ └── README.md │ │ │ ├── OR/ │ │ │ │ └── README.md │ │ │ ├── PA/ │ │ │ │ └── README.md │ │ │ ├── RI/ │ │ │ │ └── README.md │ │ │ ├── SC/ │ │ │ │ └── README.md │ │ │ ├── SD/ │ │ │ │ └── README.md │ │ │ ├── TN/ │ │ │ │ └── README.md │ │ │ ├── TX/ │ │ │ │ └── README.md │ │ │ ├── UT/ │ │ │ │ └── README.md │ │ │ ├── VA/ │ │ │ │ └── README.md │ │ │ ├── VT/ │ │ │ │ └── README.md │ │ │ ├── WA/ │ │ │ │ └── README.md │ │ │ ├── WI/ │ │ │ │ └── README.md │ │ │ ├── WV/ │ │ │ │ └── README.md │ │ │ └── WY/ │ │ │ └── README.md │ │ └── Y2021/ │ │ └── states/ │ │ ├── AK/ │ │ │ └── README.md │ │ ├── AL/ │ │ │ └── README.md │ │ ├── AR/ │ │ │ └── README.md │ │ ├── AZ/ │ │ │ └── README.md │ │ ├── CA/ │ │ │ └── README.md │ │ ├── CO/ │ │ │ └── README.md │ │ ├── CT/ │ │ │ └── README.md │ │ ├── DC/ │ │ │ └── README.md │ │ ├── DE/ │ │ │ └── README.md │ │ ├── FL/ │ │ │ └── README.md │ │ ├── GA/ │ │ │ └── README.md │ │ ├── HI/ │ │ │ └── README.md │ │ ├── IA/ │ │ │ └── README.md │ │ ├── ID/ │ │ │ └── README.md │ │ ├── IL/ │ │ │ └── README.md │ │ ├── IN/ │ │ │ └── README.md │ │ ├── KS/ │ │ │ └── README.md │ │ ├── KY/ │ │ │ └── README.md │ │ ├── LA/ │ │ │ └── README.md │ │ ├── MA/ │ │ │ └── README.md │ │ ├── MD/ │ │ │ └── README.md │ │ ├── ME/ │ │ │ └── README.md │ │ ├── MI/ │ │ │ └── README.md │ │ ├── MN/ │ │ │ └── README.md │ │ ├── MO/ │ │ │ └── README.md │ │ ├── MS/ │ │ │ └── README.md │ │ ├── MT/ │ │ │ └── README.md │ │ ├── NC/ │ │ │ └── README.md │ │ ├── ND/ │ │ │ └── README.md │ │ ├── NE/ │ │ │ └── README.md │ │ ├── NH/ │ │ │ └── README.md │ │ ├── NJ/ │ │ │ └── README.md │ │ ├── NM/ │ │ │ └── README.md │ │ ├── NV/ │ │ │ └── README.md │ │ ├── NY/ │ │ │ └── README.md │ │ ├── OH/ │ │ │ └── README.md │ │ ├── OK/ │ │ │ └── README.md │ │ ├── OR/ │ │ │ └── README.md │ │ ├── PA/ │ │ │ └── README.md │ │ ├── RI/ │ │ │ └── README.md │ │ ├── SC/ │ │ │ └── README.md │ │ ├── SD/ │ │ │ └── README.md │ │ ├── TN/ │ │ │ └── README.md │ │ ├── TX/ │ │ │ └── README.md │ │ ├── UT/ │ │ │ └── README.md │ │ ├── VA/ │ │ │ └── README.md │ │ ├── VT/ │ │ │ └── README.md │ │ ├── WA/ │ │ │ └── README.md │ │ ├── WI/ │ │ │ └── README.md │ │ ├── WV/ │ │ │ └── README.md │ │ └── WY/ │ │ └── README.md │ ├── index.html │ ├── manifest.json │ └── robots.txt ├── scripts/ │ ├── build-netlify.sh │ ├── env.ts │ ├── formgen.ts │ └── setup.ts ├── src/ │ ├── App.css │ ├── App.tsx │ ├── components/ │ │ ├── ConditionallyWrap.tsx │ │ ├── CreatePDF.tsx │ │ ├── DataPropagator.tsx │ │ ├── FormContainer/ │ │ │ └── Context.tsx │ │ ├── FormContainer.tsx │ │ ├── GettingStarted.tsx │ │ ├── HelpAndFeedback.tsx │ │ ├── Main.tsx │ │ ├── Menu.tsx │ │ ├── NoMatchPage.tsx │ │ ├── Patterns.ts │ │ ├── Questions.tsx │ │ ├── RefundBankAccount.tsx │ │ ├── ResponsiveDrawer.tsx │ │ ├── SaveToFile.tsx │ │ ├── ScrollTop.tsx │ │ ├── SkipToLinks.tsx │ │ ├── Summary.tsx │ │ ├── SummaryData.ts │ │ ├── TaxPayer/ │ │ │ ├── Address.tsx │ │ │ ├── PersonFields.tsx │ │ │ ├── SpouseAndDependent.tsx │ │ │ ├── TaxPayer.tsx │ │ │ └── index.tsx │ │ ├── UserSettings.tsx │ │ ├── Y2021/ │ │ │ └── AdvanceChildTaxCredit.tsx │ │ ├── YearDropDown.tsx │ │ ├── YearStatusBar.tsx │ │ ├── debug.tsx │ │ ├── deductions/ │ │ │ ├── F1098eInfo.tsx │ │ │ └── ItemizedDeductions.tsx │ │ ├── income/ │ │ │ ├── F1099Info.tsx │ │ │ ├── OtherInvestments.tsx │ │ │ ├── PartnershipIncome.tsx │ │ │ ├── RealEstate.tsx │ │ │ ├── StockOptions.tsx │ │ │ ├── W2JobInfo.tsx │ │ │ └── assets/ │ │ │ ├── AssetSummary.tsx │ │ │ ├── ConfigurableDataTable.tsx │ │ │ ├── DataTableStyle.ts │ │ │ ├── FilteredAssetsTable.tsx │ │ │ └── TransactionImporter.tsx │ │ ├── input/ │ │ │ ├── Currency.tsx │ │ │ ├── DatePicker.tsx │ │ │ ├── LabeledCheckbox.tsx │ │ │ ├── LabeledDropdown.tsx │ │ │ ├── LabeledInput.tsx │ │ │ ├── LabeledRadio.tsx │ │ │ ├── boxLabel.tsx │ │ │ ├── index.ts │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── pager.tsx │ │ ├── payments/ │ │ │ └── EstimatedTaxes.tsx │ │ └── savingsAccounts/ │ │ ├── IRA.tsx │ │ └── healthSavingsAccounts.tsx │ ├── core/ │ │ ├── data/ │ │ │ ├── anonymize.ts │ │ │ ├── countries.ts │ │ │ ├── index.ts │ │ │ ├── locationPostalCodes.ts │ │ │ ├── questions.ts │ │ │ └── validate.ts │ │ ├── irsForms/ │ │ │ ├── Form.ts │ │ │ ├── index.ts │ │ │ └── util.ts │ │ ├── log.ts │ │ ├── pdfFiller/ │ │ │ ├── Fill.ts │ │ │ ├── fillPdf.ts │ │ │ ├── index.ts │ │ │ └── pdfHandler.ts │ │ ├── stateForms/ │ │ │ ├── Form.ts │ │ │ └── index.ts │ │ ├── tests/ │ │ │ ├── LocalForms.ts │ │ │ ├── arbitraries.ts │ │ │ └── validation.test.ts │ │ └── util.ts │ ├── customTypes/ │ │ └── persistStore.d.ts │ ├── data/ │ │ ├── csvImport.ts │ │ ├── transactions.ts │ │ └── urls.ts │ ├── forms/ │ │ ├── F1040Base.ts │ │ ├── StateForms.ts │ │ ├── Y2020/ │ │ │ ├── data/ │ │ │ │ └── federal.ts │ │ │ ├── irsForms/ │ │ │ │ ├── F1040.ts │ │ │ │ ├── F1040Attachment.ts │ │ │ │ ├── F1040v.ts │ │ │ │ ├── F2441.ts │ │ │ │ ├── F2555.ts │ │ │ │ ├── F4136.ts │ │ │ │ ├── F4137.ts │ │ │ │ ├── F4563.ts │ │ │ │ ├── F4797.ts │ │ │ │ ├── F4952.ts │ │ │ │ ├── F4972.ts │ │ │ │ ├── F5695.ts │ │ │ │ ├── F6168.ts │ │ │ │ ├── F8582.ts │ │ │ │ ├── F8814.ts │ │ │ │ ├── F8853.ts │ │ │ │ ├── F8863.ts │ │ │ │ ├── F8888.ts │ │ │ │ ├── F8889.ts │ │ │ │ ├── F8910.ts │ │ │ │ ├── F8919.ts │ │ │ │ ├── F8936.ts │ │ │ │ ├── F8949.ts │ │ │ │ ├── F8959.ts │ │ │ │ ├── F8960.ts │ │ │ │ ├── F8962.ts │ │ │ │ ├── F8995.ts │ │ │ │ ├── F8995A.ts │ │ │ │ ├── Main.ts │ │ │ │ ├── Schedule1.ts │ │ │ │ ├── Schedule2.ts │ │ │ │ ├── Schedule3.ts │ │ │ │ ├── Schedule8812.ts │ │ │ │ ├── ScheduleA.ts │ │ │ │ ├── ScheduleB.ts │ │ │ │ ├── ScheduleC.ts │ │ │ │ ├── ScheduleD.ts │ │ │ │ ├── ScheduleE.ts │ │ │ │ ├── ScheduleEIC.ts │ │ │ │ ├── ScheduleR.ts │ │ │ │ ├── ScheduleSE.ts │ │ │ │ ├── TaxTable.ts │ │ │ │ ├── index.ts │ │ │ │ └── worksheets/ │ │ │ │ ├── ChildTaxCreditWorksheet.ts │ │ │ │ ├── Pub596Worksheet1.ts │ │ │ │ ├── SDQualifiedAndCapGains.ts │ │ │ │ ├── SDRateGainWorksheet.ts │ │ │ │ ├── SDUnrecaptured1250.ts │ │ │ │ ├── ScheduleDTaxWorksheet.ts │ │ │ │ ├── SocialSecurityBenefits.ts │ │ │ │ └── StudentLoanInterestWorksheet.ts │ │ │ ├── stateForms/ │ │ │ │ ├── AK/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── AL/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── AR/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── AZ/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── CA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── CO/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── CT/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── DC/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── DE/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── FL/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── GA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── HI/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── IA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── ID/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── IL/ │ │ │ │ │ ├── IL1040.ts │ │ │ │ │ ├── IL1040ScheduleILEIC.ts │ │ │ │ │ ├── IL1040V.ts │ │ │ │ │ ├── ILWit.ts │ │ │ │ │ └── Parameters.ts │ │ │ │ ├── IN/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── KS/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── KY/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── LA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MD/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── ME/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MI/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MN/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MO/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MS/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MT/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NC/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── ND/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NE/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NH/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NJ/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NM/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NV/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NY/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── OH/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── OK/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── OR/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── PA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── RI/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── SC/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── SD/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── TN/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── TX/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── UT/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── VA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── VT/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── WA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── WI/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── WV/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── WY/ │ │ │ │ │ └── Form.ts │ │ │ │ └── index.ts │ │ │ └── tests/ │ │ │ ├── ScheduleD.test.ts │ │ │ ├── ScheduleEIC.test.ts │ │ │ ├── f1040.test.ts │ │ │ ├── f8889.test.ts │ │ │ ├── f8949.test.ts │ │ │ ├── fica.test.ts │ │ │ ├── index.ts │ │ │ └── states/ │ │ │ └── il.test.ts │ │ ├── Y2021/ │ │ │ ├── data/ │ │ │ │ └── federal.ts │ │ │ ├── irsForms/ │ │ │ │ ├── F1040.ts │ │ │ │ ├── F1040Attachment.ts │ │ │ │ ├── F1040v.ts │ │ │ │ ├── F2439.ts │ │ │ │ ├── F2441.ts │ │ │ │ ├── F2555.ts │ │ │ │ ├── F4136.ts │ │ │ │ ├── F4137.ts │ │ │ │ ├── F4563.ts │ │ │ │ ├── F4797.ts │ │ │ │ ├── F4952.ts │ │ │ │ ├── F4972.ts │ │ │ │ ├── F5695.ts │ │ │ │ ├── F6168.ts │ │ │ │ ├── F6251.ts │ │ │ │ ├── F8582.ts │ │ │ │ ├── F8814.ts │ │ │ │ ├── F8853.ts │ │ │ │ ├── F8863.ts │ │ │ │ ├── F8888.ts │ │ │ │ ├── F8889.ts │ │ │ │ ├── F8910.ts │ │ │ │ ├── F8919.ts │ │ │ │ ├── F8936.ts │ │ │ │ ├── F8949.ts │ │ │ │ ├── F8959.ts │ │ │ │ ├── F8960.ts │ │ │ │ ├── F8962.ts │ │ │ │ ├── F8995.ts │ │ │ │ ├── F8995A.ts │ │ │ │ ├── Main.ts │ │ │ │ ├── Schedule1.ts │ │ │ │ ├── Schedule2.ts │ │ │ │ ├── Schedule3.ts │ │ │ │ ├── Schedule8812.ts │ │ │ │ ├── ScheduleA.ts │ │ │ │ ├── ScheduleB.ts │ │ │ │ ├── ScheduleC.ts │ │ │ │ ├── ScheduleD.ts │ │ │ │ ├── ScheduleE.ts │ │ │ │ ├── ScheduleEIC.ts │ │ │ │ ├── ScheduleR.ts │ │ │ │ ├── ScheduleSE.ts │ │ │ │ ├── TaxTable.ts │ │ │ │ ├── index.ts │ │ │ │ └── worksheets/ │ │ │ │ ├── Pub596Worksheet1.ts │ │ │ │ ├── QualifyingDependents.ts │ │ │ │ ├── SDQualifiedAndCapGains.ts │ │ │ │ ├── SDRateGainWorksheet.ts │ │ │ │ ├── SDTaxWorksheet.ts │ │ │ │ ├── SDUnrecaptured1250.ts │ │ │ │ ├── ScheduleDTaxWorksheet.ts │ │ │ │ ├── SocialSecurityBenefits.ts │ │ │ │ └── StudentLoanInterestWorksheet.ts │ │ │ ├── stateForms/ │ │ │ │ ├── AK/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── AL/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── AR/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── AZ/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── CA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── CO/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── CT/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── DC/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── DE/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── FL/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── GA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── HI/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── IA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── ID/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── IL/ │ │ │ │ │ ├── IL1040.ts │ │ │ │ │ ├── IL1040ScheduleILEIC.ts │ │ │ │ │ ├── IL1040V.ts │ │ │ │ │ ├── ILWit.ts │ │ │ │ │ └── Parameters.ts │ │ │ │ ├── IN/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── KS/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── KY/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── LA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MD/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── ME/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MI/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MN/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MO/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MS/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── MT/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NC/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── ND/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NE/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NH/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NJ/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NM/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NV/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── NY/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── OH/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── OK/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── OR/ │ │ │ │ │ ├── Form.ts │ │ │ │ │ ├── OR40.ts │ │ │ │ │ ├── OR40N.ts │ │ │ │ │ ├── OR40P.ts │ │ │ │ │ ├── OR40V.ts │ │ │ │ │ ├── ORASC.ts │ │ │ │ │ ├── ORASCNP.ts │ │ │ │ │ ├── ORWFHDC.ts │ │ │ │ │ └── Parameters.ts │ │ │ │ ├── PA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── RI/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── SC/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── SD/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── TN/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── TX/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── UT/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── VA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── VT/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── WA/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── WI/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── WV/ │ │ │ │ │ └── Form.ts │ │ │ │ ├── WY/ │ │ │ │ │ └── Form.ts │ │ │ │ └── index.ts │ │ │ └── tests/ │ │ │ ├── Schedule8812.test.ts │ │ │ ├── ScheduleA.test.ts │ │ │ ├── ScheduleD.test.ts │ │ │ ├── ScheduleEIC.test.ts │ │ │ ├── f1040.test.ts │ │ │ ├── f6251.test.ts │ │ │ ├── fica.test.ts │ │ │ ├── index.ts │ │ │ ├── states/ │ │ │ │ └── il.test.ts │ │ │ ├── taxRates.test.ts │ │ │ └── taxTable.csv │ │ ├── YearForms.ts │ │ ├── errors.ts │ │ └── tests/ │ │ ├── CommonTests.ts │ │ └── TestKit.ts │ ├── index.css │ ├── index.js │ ├── log.ts │ ├── pdfHandler.ts │ ├── react-app-env.d.ts │ ├── redux/ │ │ ├── TaxesState.ts │ │ ├── actions.ts │ │ ├── data.ts │ │ ├── fs/ │ │ │ ├── Actions.ts │ │ │ ├── FSReducer.ts │ │ │ ├── Load.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── migration.ts │ │ ├── reducer.ts │ │ ├── store.ts │ │ └── yearDispatch.ts │ ├── serviceWorker.js │ ├── setupTests.js │ ├── testUtil.tsx │ └── tests/ │ ├── MultipleYears.test.tsx │ ├── Recover.test.ts │ ├── arbitraries.ts │ ├── common/ │ │ ├── DomMethods.ts │ │ ├── FakePager.tsx │ │ ├── Page.tsx │ │ ├── PersonMethods.tsx │ │ ├── TaxPayerMethods.tsx │ │ └── YearsStatusBarMethods.tsx │ ├── components/ │ │ ├── CreatePdf.test.tsx │ │ ├── Menu.test.tsx │ │ ├── Questions.test.tsx │ │ ├── SpouseAndDependent/ │ │ │ ├── Dependent.test.tsx │ │ │ ├── FilingStatus.test.tsx │ │ │ ├── Methods.ts │ │ │ ├── Pages.tsx │ │ │ └── Spouse.test.tsx │ │ ├── Taxpayer.test.tsx │ │ └── income/ │ │ ├── F1099Info.test.tsx │ │ └── W2JobInfo.test.tsx │ ├── testdata/ │ │ ├── transactions_test1.csv │ │ └── transactions_test2_error.csv │ └── transactions/ │ ├── Transactions.test.tsx │ └── arbitraries.ts ├── src-tauri/ │ ├── .gitignore │ ├── Cargo.toml │ ├── rustfmt.toml │ ├── src/ │ │ ├── build.rs │ │ ├── cmd.rs │ │ └── main.rs │ └── tauri.conf.json ├── tsconfig.json └── tsconfig.path.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codecov.yml ================================================ coverage: status: project: default: # basic target: 70% threshold: 0% base: auto flags: - unit paths: - 'src' # advanced settings branches: - master if_ci_failed: ignore #error #success, failure, ignore informational: true only_pulls: false paths: - 'src' patch: default: # basic target: 70% threshold: 0% base: auto if_ci_failed: ignore #error #success, failure, ignore informational: true only_pulls: false paths: - 'src' ================================================ FILE: .dockerignore ================================================ # dependencies /node_modules /.pnp .pnp.js # misc .DS_Store .netlify npm-debug.log* yarn-debug.log* yarn-error.log* /.eslintcache # vscode settings /.vscode /.husky/ /src/redux/validation.json ================================================ FILE: .eslintignore ================================================ serviceWorker.js # don't worry about generated files /src/core/data/validate-fns.js ================================================ FILE: .eslintrc.js ================================================ module.exports = { settings: { react: { version: 'detect' } }, env: { browser: true, es2021: true, node: true }, extends: [ 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'prettier' ], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 12, sourceType: 'module' }, plugins: ['react', '@typescript-eslint', 'prettier'], rules: { 'react/jsx-uses-react': 'off', 'react/react-in-jsx-scope': 'off', '@typescript-eslint/no-unnecessary-condition': 'warn' }, parserOptions: { project: ['./tsconfig.json', './.eslintrc.js'] } } ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: # regular updates for our established packages - package-ecosystem: "npm" directory: "/" # Location of package manifests schedule: interval: "daily" target-branch: "master" open-pull-requests-limit: 15 - package-ecosystem: "cargo" directory: "/src-tauri/" # Location of package manifests schedule: interval: "daily" target-branch: "master" ================================================ FILE: .github/pull_request_template.md ================================================ **What kind of change does this PR introduce?** (delete not applicable) - Bugfix - Feature - Docs - Code style update - Refactor - Build-related changes - Other, please describe: ================================================ FILE: .github/workflows/ci.yml ================================================ # Contains unit test, lint, dependabot automerge logic name: CI on: push: branches: [ master ] pull_request: branches: [ master ] permissions: pull-requests: write contents: write jobs: test: runs-on: ubuntu-latest strategy: matrix: node-version: ['16'] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} # installs dependencies - run: npm ci # build - name: Run ESLint run: npm run lint - name: Ensure project builds run: npm run build # run tests - name: Run jest unit tests run: npm run test:report - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} directory: ./coverage verbose: true dependabot-auto-merge: needs: [test] runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v1.1.1 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs if: ${{(steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor')}} run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} ================================================ FILE: .github/workflows/tauri-release.yml ================================================ name: "publish" on: push: tags: - app-v* jobs: publish-tauri: strategy: fail-fast: false matrix: platform: [macos-latest, ubuntu-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - name: setup node uses: actions/setup-node@v2 with: node-version: 16 - name: Get the version id: get_version run: echo ::set-output name=VERSION::__VERSION__ shell: bash ## https://github.com/tauri-apps/tauri/blob/76187298c1d52dc764c5192c6a770a65f44b82ab/.github/workflows/smoke-test-prod.yml ## Following that example, we cache directories for both cargo and node. ## The cache action will check caches before these directories are required, and update the caches if necessary ## at the end of the job (after cargo install and node install if those are necessary). - name: cache rust bin id: cache_rust_bin uses: actions/cache@v1 with: path: ~/.cargo/bin/ key: ${{ runner.OS }}-xbuild-bin-${{ hashFiles('**/Cargo.toml') }} - name: cache rust registry/index id: cache_rust_reg_index uses: actions/cache@v1 with: path: ~/.cargo/registry/index key: ${{ runner.OS }}-xbuild-reg-index-${{ hashFiles('**/Cargo.toml') }}- - name: cache rust registry/cache id: cache_rust_reg_cache uses: actions/cache@v1 with: path: ~/.cargo/registry/cache/ key: ${{ runner.OS }}-xbuild-reg-cache-${{ hashFiles('**/Cargo.toml') }}- - name: install Rust stable uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal - name: install dependencies (ubuntu only) if: matrix.platform == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf - name: install app dependencies run: npm ci - name: build it run: npm run desktop-release - uses: tauri-apps/tauri-action@dev env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: releaseName: App v${{ steps.get_version.outputs.VERSION }} tagName: app-v${{ steps.get_version.outputs.VERSION }} releaseBody: "See the assets to download this version and install." releaseDraft: false prerelease: false ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # Local Netlify folder .netlify /.eslintcache # vscode settings /.vscode /.devcontainer/ /logs/ /src/core/data/validate-fns.js ================================================ FILE: .husky/husky.sh ================================================ #!/bin/sh if [ -z "$husky_skip_init" ]; then debug () { if [ "$HUSKY_DEBUG" = "1" ]; then echo "husky (debug) - $1" fi } readonly hook_name="$(basename "$0")" debug "starting $hook_name..." if [ "$HUSKY" = "0" ]; then debug "HUSKY env variable is set to 0, skipping hook" exit 0 fi if [ -f ~/.huskyrc ]; then debug "sourcing ~/.huskyrc" . ~/.huskyrc fi export readonly husky_skip_init=1 sh -e "$0" "$@" exitCode="$?" if [ $exitCode != 0 ]; then echo "husky - $hook_name hook exited with code $exitCode (error)" fi exit $exitCode fi ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/husky.sh" # npm test npx lint-staged ================================================ FILE: .npmrc ================================================ //.npmrc file engine-strict = true ================================================ FILE: .nvmrc ================================================ lts/* ================================================ FILE: .prettierignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local .github npm-debug.log* yarn-debug.log* yarn-error.log* # Local Netlify folder .netlify /.eslintcache # vscode settings /.vscode /.devcontainer/ /.husky/ tsconfig.json .codecov.yml # don't worry about src-tauri (rust code) /src-tauri # don't worry about generated files /src/core/data/validate-fns.js ================================================ FILE: .prettierrc.json ================================================ { "trailingComma": "none", "tabWidth": 2, "semi": false, "singleQuote": true, "jsxSingleQuote": false, "printWidth": 80 } ================================================ FILE: Dockerfile ================================================ FROM rust:1.51-slim-buster RUN mkdir -p /home/node/app RUN useradd -rm -d /home/node -s /bin/bash -g root -G sudo -u 1001 node RUN chown -R node /home/node RUN apt-get update && apt-get -y upgrade && apt-get install -y curl USER node # Install nvm, node, npm ARG NVM_DIR="/home/node/.nvm" ARG NODE_VERSION=16.7.0 RUN curl https://raw.githubusercontent.com/creationix/nvm/v0.38.0/install.sh | bash RUN . $NVM_DIR/nvm.sh && nvm install $NODE_VERSION --latest-npm ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH WORKDIR /home/node/app ADD ./package.json ./package.json ADD ./package-lock.json ./package-lock.json ADD ./scripts ./scripts RUN npm ci ADD . . ================================================ FILE: LICENSE ================================================ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . ================================================ FILE: README.md ================================================

USTaxes

[![Netlify Status][netlify-badge]][netlify-url] [![Github Latest Release][release-badge]][github-release] [![discord-badge]][discord-url]
## What is UsTaxes? UsTaxes is a free, open-source tax filing application that can be used to file the Federal 1040 form. It is available in both [web](https://ustaxes.org/) and [desktop][desktop-releases] versions. It is provided free of charge and requires no sharing of personal data. **Interested in contributing? [Get Started](#user-content-get-started)** ## Supported Income data Most income and deduction information from the following forms are supported for tax years 2020 and 2021. - W2 - 1099-INT - 1099-DIV - 1099-B - 1098-E - 1099-R: support for normal distributions from IRA and pension accounts. - SSA-1099 So far, this project can attach the following schedules to form 1040: - Schedule 1 (as to Schedule E and 1098-E data only) - Schedule 2 - Schedule 3 (as to excess FICA tax only) - Schedule 8812 - Schedule B - Schedule D - Schedule E - F1040-V - F8949 (Uncovered Investment Transactions) - F8889 (Health Savings Accounts) - F8959 (Additional Medicare Tax) - F8960 (Net Investment Income Tax) ## Supported Credits - Credit for children and other dependents - Earned income credit ## Supported states ### Implemented State returns The states below have been implemented partially. See the `/src/stateForms//` file for details on unimplemented portions. - Illinois ### Non-filing states Users who only have wage income and live in the states below should be able to file taxes using this site, since they do not have state level income tax. - Alaska - Florida - Nevada - New Hampshire - South Dakota - Tennessee - Texas - Washington - Wyoming ## Note on using this project This project is built by a growing community. If you notice an error in the outputted PDF or any other error, please submit an issue on the Github issues tab. We appreciate your feedback! ## User Data The project is available strictly via client side. Data is persisted to the site's localstorage so _no personal information ever leaves the user's computer._ For those who want extra security, the codebase can also be built as a [desktop application](#desktop-application). ## Contributing Thank you for taking the time to contribute; let's make tax filing free for everyone! 🎉 To ensure the project is fun for every contributor, please review: - [Code of conduct](docs/CODE_OF_CONDUCT.md) - [Contributing guide](docs/CONTRIBUTING.md) - [Project Architecture](docs/ARCHITECTURE.md) ## Get Started This application can be run as either a web application or a [standalone desktop application](#user-content-desktop-application) ### Web application This project runs on Node 16. To ensure you're on the proper version, we recommend [nvm](https://github.com/nvm-sh/nvm#installing-and-updating). With `nvm` installed, you may select a version 16 node using: ```sh nvm install 16 nvm use 16 ``` To run, ```sh npm ci # install package dependencies npm run start # run app ``` Note: To avoid having to set your node versions, we suggest using a tool like [direnv](https://direnv.net). With the following configuration file as `.envrc` in project root: ```sh export NVM_DIR="$HOME/.nvm" . "$NVM_DIR/nvm.sh" # This loads nvm #. "$NVM_DIR/bash_completion" # Optional, nvm bash completion nvm install 16 nvm use 16 ``` your environment will be set up every time you enter the project directory. #### Docker If preferred, a Docker alternative is available: ```sh docker-compose build docker-compose up ``` Open a browser to `http://localhost:3000`. To stop and remove running containers, run `docker-compose down`. ### Desktop application The desktop application is built with [Tauri][tauri-root]. In addition to the above steps, please [follow this reference for setting up your environment for Tauri][tauri-setup]. Once your environment is set up for Tauri, run, `npm run desktop`. To avoid a browser window being spawned in addition to the desktop window, just set the BROWSER environment variable as in: `BROWSER=none npm run desktop`. To build executables, run `npm run desktop-release`. ## Getting help Please reach out to us on our [discord][discord-url] if you run into any problems, or [file an issue][github-issues]. Thank you for your support! [netlify-badge]: https://api.netlify.com/api/v1/badges/41efe456-a85d-4fed-9fcf-55fe4d5aa7fa/deploy-status [netlify-url]: https://app.netlify.com/sites/peaceful-joliot-d51349/deploys [cargo-docs]: https://doc.rust-lang.org/cargo/getting-started/installation.html [discord-badge]: https://img.shields.io/discord/812156892343828500?logo=Discord [discord-url]: https://discord.gg/dAaz472mPz [github-release]: https://github.com/ustaxes/UsTaxes/releases/latest [release-badge]: https://badgen.net/github/release/ustaxes/ustaxes [desktop-releases]: https://github.com/ustaxes/UsTaxes/releases/ [github-issues]: https://github.com/ustaxes/ustaxes/issues [tauri-setup]: https://tauri.studio/en/docs/getting-started/intro/#setting-up-your-environment [tauri-root]: https://tauri.studio ================================================ FILE: craco.config.js ================================================ const path = require('path') module.exports = { webpack: { alias: { ustaxes: path.resolve(__dirname, './src') } } } ================================================ FILE: docker-compose.yml ================================================ version: '3.7' services: app: build: . command: npm run start ports: - '3000:3000' volumes: - ./:/app ================================================ FILE: docs/ARCHITECTURE.md ================================================ # Project Architecture Thank you for your interest in this project. The below should summarize the general design framework used in this project, and hopefully guide you in understanding the code and getting ready to contribute to the project. If anything at all is unclear please join the discord linked on the readme and feel free to ask any questions you might have. ## Project design There are four main concerns separated in this project: 1. Data must be collected from users (react forms) 2. Collected data must be stored in a data model (via redux dispatched actions only) 3. Tax forms must access data model to calculate data required by each form 4. That calculated data must be must be rendered into a PDF file when the user exports their 1040 and attachments. Data flows in only this one direction, from 1 to 4. ![Data flow](dataflow.svg) Note the information in the datamodel is automatically synced to browser's LocalStorage so the data is available when the user closes and reopens the page. ### Data model The root schema of data stored from form submissions is defined in [src/redux/data.ts](../src/redux/data.ts) as: ```ts export interface Information { f1099s: Supported1099[] w2s: IncomeW2[] refund?: Refund taxPayer: TaxPayer } ``` - **f1099s**: An array of all 1099s that have been added. Note this includes 1099-B which goes to Schedule D, 1099-INT which goes to Schedule B, and 1099-DIV which provides data that goes to both Schedule B and Schedule D. This confusion is not needed at this level of the data model. Later when PDFs are created, the correct data can be accessed by the code managing those schedules. - **w2s**: All W-2s that have been added for both primary taxpayer and spouse - **refund**: Direct deposit information - **taxPayer**: Basic information about user's name, SSN, dependents, spouse ### PDF Export Supported federal and state forms are included in the source control of this repository. - [src/irsForms/](../src/irsForms/) includes ts models for all federal forms that can be filled by this project. The PDFs are also included in this project at [pubilc/forms](../public/forms). Each of these form definitions implements this interface: ```ts type Field = string | number | boolean | undefined export default interface Fill { fields: () => Field[] } ``` This array of `fields` must line up exactly with the fields expected by the PDF that the data will be filled into. Getting this data to line up exactly is error prone and tedious. The only type checking we can do between the PDF and our data is to verify if a field expects a boolean value (checkbox), or a text + numeric value. In order to help this error prone process, there's also a script that should be used to add a new form. See the guide for contributing a new form below. - [src/stateForms](../src/stateForms) includes ts models for all state forms that can be filled. They also implement the same interface. ## Guide for contributing a new form implementation - Add new data schema if needed - Interfaces in [src/redux/data](../src/redux/data.ts) may need to be expanded if you're collecting additional data from the user - For a new UI form that needs its own page, add to routes in [Main.tsx](../src/components/Main.tsx) - A UI form can push new data into the state using Redux actions. Define your new action in [src/redux/actions.ts](../src/redux/actions.ts), and add your state updates to [src/redux/reducer.ts](../src/redux/reducer.ts) - If there is a new attachment to the 1040: - The blank form goes in `public/forms/`. The locations of all supported attachments and logic about what attachments are required, is in [fillPdf.ts](../src/pdfFiller/fillPdf.ts). - The data model for the PDF goes in [irsForms](../src/irsForms), and implements the `Form` interface as above. There is a script we use to generate a base implementation of the form. To generate this base implementation, run ``` npm run formgen ./public/forms/.pdf > ./src/irsForms/.ts ``` This will provide a function for each field in the PDF. At this point you should have a compilable file that needs the implementations for all those functions filled in. [npm-install]: https://www.npmjs.com/get-npm [tauri-root]: https://tauri.studio/ [rust-root]: https://www.rust-lang.org/ [webview2]: https://developer.microsoft.com/en-us/microsoft-edge/webview2/ ================================================ FILE: docs/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at grimshawaidan@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: docs/CONTRIBUTING.md ================================================ # UsTaxes Contributing Guide Before contributing please make sure to take a moment to read through the [Code of Conduct](CODE_OF_CONDUCT.md), as well as relevant documentation for the contribution you intend to make: - [Issue Reporting Guidelines](#issue-reporting-guidelines) - [Pull Request Guidelines](#pull-request-guidelines) - [Development Guide](#development-guide) ## Maintaining team - [Aidan Grimshaw](http://github.com/thegrims) - [Zak Patterson](http://github.com/zakpatterson) ## Issue Reporting Guidelines - The issue list of this repo is for bug reports and feature requests. Please come to the [Discord chat](https://discord.gg/dAaz472mPz) with general questions you may have. - If your issue is resolved but still open, please close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it. - Generally, there are many features still remaining to produce a valid 1040 for most people. We hope you will view this project in light of your individual circumstances and expertise and contribute accordingly! For example, if you have a certain type of income or receive a certain credit or deduction, and those are not yet implemented, we would happily assist you in implementing those features. ## Pull Request Guidelines - For code security, we require signed commits. If you haven't set up signed commits yet, please do the following: - Follow [this Github tutorial](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification) - To ensure all your commits are signed going forward, `git config --global commit.gpgsign true`, - If you have unsigned commits that you would like to sign, you can do so by rebasing. `git rebase master`, followed by `git commit --amend` to redo each commit on the branch. - The only requirement is that your PR is well described and your intentions are clearly communicated. - If adding new feature, provide some clear reason to add this feature. - If fixing a bug: - If you are resolving a special issue, add `Fixes: #xxx` (#xxx is the issue id) in your PR body. When your PR is merged, the maintainer will move this information into the final commit message for our release log. - Provide detailed description of the bug in the PR, or link to an issue that does. - These are just some formatting suggestions, we won't call you out for not following them: - Start commits with a capital letter and imperative mood, no period at the end. - Each commit first line should be max 50 characters, followed by a blank line. - Wrap following lines at 80 characters. - Include as much detail as you need in the following lines. - Github PR title style is the same as commit first line style. - It's OK to have multiple small commits as you work on the PR - we apply squash merges to all PRs at the end. In particular, you are free to rebase your commits so long as your pull request is in draft status. As soon as you take it out of draft status, request a review, or receive a review on your pull request, please do not rebase or force push. ## Development Guide ### General Setup First, [join our Discord server](https://discord.gg/dAaz472mPz) and let us know that you want to contribute. This way we can point you in the right direction and help ensure your contribution will be as helpful as possible. 1. To set up your machine for development, review the [Architecture doc](ARCHITECTURE.md), for required links to set up NPM 8 and Rust. 1. Next, fork and clone this repo. 1. Run `npm ci` to install the package versions referenced in `package-lock.json`. If your feature requires a new dependency, add it using `npm install @` to avoid affecting other dependencies in `package-lock.json`. Try - `npm run desktop` to try out the desktop application. Setting the environment variable `BROWSER=none` will stop the web browser from also loading for you. - `npm run start` will serve the site locally on port 3000, so you can view it at `localhost:3000` in your browser. #### Stack This app uses Typescript React and Redux. The structure of this app should look familiar to you if you have used redux before, and it may seem opaque and confusing to you if you have not used Redux before. [Here is a great guide you can use to learn to about redux](https://redux.js.org/tutorials/fundamentals/part-1-overview) #### State Management In order to manage state between many different components and concerns in a project, we dispatch actions that update a piece of state in a global state object. Each of these actions is received by a reducer that applies that change to the state variable. All of this logic is contained in [src/redux](src/redux). - `src/redux/actions.ts`: All the actions that can be sent to update our state - `src/redux/data.ts`: No functionality, just type definitions for our global state variable. - `src/redux/reducer.ts`: All logic for updating our global state. ### Directories Overview - [`src/components`](../src/components) Contains React forms and **all UI**. - [`src/data`](../src/data) Contains static data such as a list of states and a list of tax brackets - [`src/irsForms`](../src/irsForms) - These are typescript model implementations of the actual IRS pdfs. Each form provides the data to be filled into the final PDF via an array where each index must match the expected index in the PDF. We use the convention that methods are named after the actual line referred to in the PDF. So `const l1 = (): number = ...` will be the function to call to get the numeric value needed on line 1 of that form. - Also, because the forms closely follow IRS published instructions and worksheets, the tax calculations are also coded in this directory. - [`pdfFiller`](../src/pdfFiller): All the logic to actually fill form data into a PDF. - [`redux`](../src/redux) All the types and logic to manage global state in the app. - [`customTypes`](../src/customTypes) Special purpose definitions needed to give the typescript compiler more type information about some features of our dependencies we use. Ideally these needs would be provided by our dependencies and this folder can be deleted in the future. ## License UsTaxes is a GPL-licensed open source project. We think this choice is important for a few reasons - If anyone wants to use the software for any reason, they are welcome to. - If anyone wants to sell the software, they can, but they have to provide all the source so users can build the project themselves. - If someone wants to improve on the software and sell that, they can, but they also have to provide the source for all their improvements for free. We believe this choice will help you know your contributions are valued and will be used responsibly. ## Financial Contribution If you do not have time to contribute to UsTaxes directly but would like to financially offer support, we have not yet set up a framework to allow that. We promise however that any future financial contributions will be public and without preconditions. So "I would like to provide support to develop support for itemized deductions" is fine, and contributions will be allocated towards those working on that feature. But "I would like to support the project provided you take steps x and y" or conditioned on us providing advertising or selling other services will not be ok. If you have any questions about that feel free to private message one of the maintainers on our discord. This Contributor guide was adapted from the Tauri project's [Contributing document](https://github.com/tauri-apps/tauri/blob/1d66d00506ea79cf803b0e0d025ece1730ffa242/.github/CONTRIBUTING.md) ================================================ FILE: notice.js ================================================ const notice = ` ustaxes helps you build your tax return forms for free Copyright (C) 2021 Aidan Grimshaw and contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ` console.log(notice) ================================================ FILE: package.json ================================================ { "name": "ustaxes", "version": "0.1.21", "private": true, "dependencies": { "@date-io/date-fns": "^1.3.13", "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/pickers": "^3.3.10", "@tauri-apps/api": "^1.1.0", "@tauri-apps/cli": "^1.1.1", "ajv": "^8.11.0", "date-fns": "^2.29.3", "lodash": "^4.17.21", "loglevel": "^1.8.0", "papaparse": "^5.3.2", "pdf-lib": "^1.17.1", "react": "^17.0.2", "react-data-table-component": "^7.5.3", "react-device-detect": "^2.2.2", "react-dom": "^17.0.2", "react-helmet": "^6.1.0", "react-hook-form": "^7.22.5", "react-number-format": "^4.9.4", "react-redux": "^8.0.4", "react-router": "^6.3.0", "react-router-dom": "^6.3.0", "react-scripts": "^4.0.3", "redux": "^4.2.0", "redux-logger": "^3.0.6", "redux-persist": "^6.0.0", "rooks": "^7.4.0", "ts-json-schema-generator": "^1.0.0", "typescript": "^4.7.4" }, "scripts": { "start": "node ./notice.js && ts-node ./scripts/env.ts && ts-node ./scripts/setup.ts && concurrently npm:dev", "dev": "craco start", "build": "ts-node ./scripts/env.ts && ts-node ./scripts/setup.ts && craco build", "test": "ts-node ./scripts/env.ts && ts-node ./scripts/setup.ts && craco test", "eject": "craco eject", "desktop-release": "tauri build", "desktop": "tauri dev", "test:report": "ts-node ./scripts/env.ts && npm run test -- --coverage .", "tauri": "ts-node ./scripts/env.ts && tauri", "schema-generate": "ts-node ./scripts/env.ts && node ./setup.js", "lint": "eslint ./src --ext ts,js,tsx,jsx --max-warnings=0 && prettier -c .", "lint:fix": "eslint ./src --fix --ext ts,js,tsx,jsx && prettier --write .", "prettier": "prettier --write .", "formgen": "ts-node ./scripts/formgen.ts", "prepare": "ts-node ./scripts/env.ts && husky install" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@craco/craco": "^6.4.5", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^13.5.0", "@types/jest": "^29.1.1", "@types/lodash": "^4.14.186", "@types/node": "^18.7.23", "@types/papaparse": "^5.3.5", "@types/react": "^17.0.47", "@types/react-dom": "^18.0.5", "@types/react-helmet": "^6.1.5", "@types/react-redux": "^7.1.24", "@types/react-router-dom": "^5.3.3", "@types/redux-logger": "^3.0.9", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", "concurrently": "^7.4.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^6.0.1", "eslint-plugin-react": "^7.31.8", "fast-check": "^2.25.0", "husky": "^8.0.1", "lint-staged": "^13.0.3", "prettier": "2.7.1", "ts-node": "^10.9.1", "typescript-eslint": "0.0.1-alpha.0" }, "lint-staged": { "**/*": "prettier --write --ignore-unknown .", "*.{ts,js,tsx,jsx}": "eslint ./src --cache --fix" }, "jest": { "moduleNameMapper": { "^ustaxes/(.*)": "/src/$1" }, "transformIgnorePatterns": [ "/node_modules/(?!@tauri-apps)" ] }, "engines": { "yarn": "please-use-npm" } } ================================================ FILE: public/_redirects ================================================ /* /index.html 200 ================================================ FILE: public/forms/Y2020/states/AK/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/AL/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/AR/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/AZ/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/CA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/CO/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/CT/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/DC/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/DE/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/FL/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/GA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/HI/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/IA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/ID/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/IL/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/IN/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/KS/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/KY/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/LA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/MA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/MD/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/ME/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/MI/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/MN/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/MO/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/MS/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/MT/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/NC/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/ND/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/NE/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/NH/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/NJ/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/NM/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/NV/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/NY/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/OH/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/OK/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/OR/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/PA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/RI/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/SC/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/SD/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/TN/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/TX/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/UT/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/VA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/VT/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/WA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/WI/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/WV/README.md ================================================ TODO ================================================ FILE: public/forms/Y2020/states/WY/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/AK/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/AL/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/AR/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/AZ/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/CA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/CO/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/CT/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/DC/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/DE/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/FL/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/GA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/HI/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/IA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/ID/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/IL/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/IN/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/KS/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/KY/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/LA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/MA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/MD/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/ME/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/MI/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/MN/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/MO/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/MS/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/MT/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/NC/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/ND/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/NE/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/NH/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/NJ/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/NM/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/NV/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/NY/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/OH/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/OK/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/OR/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/PA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/RI/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/SC/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/SD/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/TN/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/TX/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/UT/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/VA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/VT/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/WA/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/WI/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/WV/README.md ================================================ TODO ================================================ FILE: public/forms/Y2021/states/WY/README.md ================================================ TODO ================================================ FILE: public/index.html ================================================ UsTaxes.org
================================================ FILE: public/manifest.json ================================================ { "short_name": "US Taxes", "name": "UsTaxes is an open source webapp for filing US federal income tax. All tax calculations are performed in the browser, so no personal information is stored on external servers!", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: scripts/build-netlify.sh ================================================ #!/usr/bin/env bash set -e export CI=false npm run lint npm run build ================================================ FILE: scripts/env.ts ================================================ import { exec } from 'child_process' // fs.cp since 16.7 export const requiredNodeVersion = [16, 7] export const requiredNpmVersion = [7, 0] const showError = (...errLines: string[]): void => { const maxLineLen = 78 const displayError = errLines.map((err) => { if (err.length > maxLineLen) { const words = err.split(' ') return words .reduce( (lines, word) => { if (lines.length + 1 + word.length > maxLineLen) { return [...lines, word] } else { return [...lines, lines[lines.length - 1] + ' ' + word] } }, [''] ) .join('\n') } return err }) const lineLen = Math.min( maxLineLen, Math.max(...displayError.map((x) => x.length)) ) const line = (msg: string): string => `| ${msg}`.padEnd(lineLen + 2) + ' |' const border = `|${Array.from(Array(lineLen + 2)) .map(() => '-') .join('')}|` console.error([border, ...displayError.map(line), border].join('\n')) } export const checkVersion = (): void => { const [M, m] = process.version .slice(1) // drop 'v' .split('.') // split into major and minor .map((x) => parseInt(x, 10)) const [R, r] = requiredNodeVersion if (M < R || (M === R && m < r)) { const requiredStr = requiredNodeVersion.join('.') showError( 'Required node version not found.', `Required: >= ${requiredStr}`, `Found: ${process.version}`, 'Consider using nvm: https://nvm.sh' ) process.exit(1) } } const getNpmVersion = (): Promise<[number, number]> => { return new Promise((resolve, reject) => { exec('npm --version', (error, stdout) => { if (error) { reject(error) } else { const [major, minor] = stdout .trim() .split('.') .map((x) => parseInt(x, 10)) resolve([major, minor]) } }) }) } export const checkNpmVersion = async (): Promise => { const [major, minor] = await getNpmVersion() const [R, r] = requiredNpmVersion if (major < R || (major === R && minor < r)) { const requiredStr = requiredNpmVersion.join('.') showError( 'Required npm version not found.', `Required: >= ${requiredStr}`, `Found: ${major}.${minor}`, 'Consider using nvm: https://nvm.sh' ) process.exit(1) } } const main = async (): Promise => { await checkNpmVersion() checkVersion() } export default main if (require.main === module) { main() } ================================================ FILE: scripts/formgen.ts ================================================ import { PDFDocument, PDFField, PDFCheckBox } from 'pdf-lib' import { readFile } from 'fs/promises' import * as path from 'path' import _ from 'lodash' const loadFile = async (path: string): Promise => { const file = await readFile(path) const bytearray = file.slice(0, file.byteLength) return await PDFDocument.load(bytearray) } const normalizeName = (name: string): string => name.replace(/[^a-zA-Z0-9]/g, '') const fieldFunction = (field: PDFField, index: number): [string, string] => { const name = normalizeName(field.getName()) const isNumeric = name.match(/^[0-9]+[a-z]*$/) const functionName = isNumeric ? `l${name}` : `f${index}` const returnType = (() => { if (isNumeric) return 'number' if (field instanceof PDFCheckBox) return 'boolean' return 'string' })() const fullReturnType = `${returnType}${ field.isRequired() ? '' : ' | undefined' }` const defaultValue: string = (() => { if (field.isRequired()) { if (field instanceof PDFCheckBox) { return 'false' } return "''" } return 'undefined' })() // If the pdfField has a plain numeric name, we'll assume that // it corresponds to a numbered line on the return, and it will be given // a simple implementation like const l5 = (): number | undefined => ... // If a pdffield has a string name, then we'll provide a named implementation like // const spouseSocialNumber = () => ... // and an alias with the pdf index field number. const namedImplementation = ` /** * Index ${index}: ${field.getName()} */ const ${isNumeric ? functionName : name} = (): ${fullReturnType} => { return ${defaultValue} } ` const alias = isNumeric ? undefined : ` const ${functionName} = (): ${fullReturnType} => this.${name}() ` const code: string = [namedImplementation, alias] .filter((x) => x !== undefined) .join('\n') return [code, functionName] } const buildSource = (doc: PDFDocument, formName: string): string => { const functions = doc.getForm().getFields().map(fieldFunction) const [impls, functionNames] = _.unzip(functions) const className = normalizeName(formName) return `import Form from '../Form' import F1040 from '../../irsForms/F1040' import { Field } from '../../pdfFiller' import { displayNumber, sumFields } from '../../irsForms/util' import { AccountType, FilingStatus, State } from '../../redux/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class ${className} extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State constructor(f1040: F1040) { this.info = f1040.info this.f1040 = f1040 this.formName = '${formName}' this.state = 'AK' // <-- Fill here } ${impls.join('\n')} const fields = (): Field[] => ([ ${functionNames .map((name) => name.startsWith('l') ? ` displayNumber(this.${name}())` : ` this.${name}()` ) .join(',\n')} ]) } const make${className} = (f1040: F1040): ${className} => new ${className}(f1040) export default make${className} ` } const generate = async ( inFile: string, outFile: string | undefined = undefined ): Promise => { const pdf = await loadFile(inFile) const name = path.parse(inFile).name const code = buildSource(pdf, name) if (outFile === undefined) { console.log(code) } } const help = () => { console.log(` Usage: npm run formgen .pdf `) } const main = () => { const args = process.argv.slice(2) process.argv.forEach((a) => console.log(a)) if (args.length < 1) { help() process.exit() } generate(args[0]) } if (require.main === module) { main() } ================================================ FILE: scripts/setup.ts ================================================ import { createGenerator } from 'ts-json-schema-generator' import fs from 'fs' import Ajv from 'ajv' import standaloneCode from 'ajv/dist/standalone' const config = { path: 'src/core/data/index.ts', tsconfig: 'tsconfig.json', type: '*' // Or if you want to generate schema for that one type only } const indexSchema = { $id: '#/definitions/index', type: 'number', minimum: 0 } const outputPath = 'src/core/data/validate-fns.js' const schema = createGenerator(config).createSchema(config.type) const ajv = new Ajv({ schemas: [schema, indexSchema], allowUnionTypes: true, code: { source: true, esm: true } }) const moduleCode = standaloneCode(ajv, { Index: '#/definitions/index', PersonRole: '#/definitions/PersonRole', ContactInfo: '#/definitions/ContactInfo', Address: '#/definitions/Address', AccountType: '#/definitions/AccountType', Employer: '#/definitions/Employer', FilingStatus: '#/definitions/FilingStatus', PrimaryPerson: '#/definitions/PrimaryPersonDateString', Spouse: '#/definitions/SpouseDateString', Person: '#/definitions/PersonDateString', Dependent: '#/definitions/DependentDateString', F1099IntData: '#/definitions/F1099IntData', F1099BData: '#/definitions/F1099BData', Income1099Int: '#/definitions/Income1099Int', Income1099B: '#/definitions/Income1099B', Supported1099: '#/definitions/Supported1099', IncomeW2: '#/definitions/IncomeW2', EstimatedTaxPayments: '#/definitions/EstimatedTaxPayments', Refund: '#/definitions/Refund', TaxPayer: '#/definitions/TaxPayerDateString', Information: '#/definitions/InformationDateString', Property: '#/definitions/Property', PropertyType: '#/definitions/PropertyType', F1098e: '#/definitions/F1098e', ItemizedDeductions: '#/definitions/ItemizedDeductions', Responses: '#/definitions/Responses', StateResidency: '#/definitions/StateResidency', HealthSavingsAccount: '#/definitions/HealthSavingsAccountDateString', Ira: '#/definitions/Ira', AssetType: '#/definitions/AssetType', AssetString: '#/definitions/AssetString', TaxYear: '#/definitions/TaxYear', EditHSAAction: '#/definitions/EditHSAAction', EditIRAAction: '#/definitions/EditIraAction', Credit: '#/definitions/Credit', EditCreditAction: '#/definitions/EditCreditAction' }) fs.writeFileSync(outputPath, moduleCode) ================================================ FILE: src/App.css ================================================ body { height: 100vh; } .MuiGrid-container ~ .MuiGrid-container { margin-top: 8px; } ================================================ FILE: src/App.tsx ================================================ import { ReactElement, useMemo } from 'react' import Main from './components/Main' import './App.css' import { createTheme, ThemeProvider, useMediaQuery } from '@material-ui/core' const App = (): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const theme = useMemo( () => createTheme({ palette: { secondary: prefersDarkMode ? { light: '#4f5b62', main: '#d5d5d5', dark: '#000a12', contrastText: '#ffffff' } : { light: '#4f5b62', main: '#263238', dark: '#000a12', contrastText: '#ffffff' }, primary: { light: '#66ffa6', main: '#00e676', dark: '#00b248', contrastText: '#000000' } } }), [prefersDarkMode] ) return (
) } export default App ================================================ FILE: src/components/ConditionallyWrap.tsx ================================================ import { PropsWithChildren, ReactNode, ReactElement } from 'react' type ConditionallyWrapProps = PropsWithChildren<{ condition: boolean wrapper: (children: ReactNode) => ReactElement }> const ConditionallyWrap = ({ condition, wrapper, children }: ConditionallyWrapProps): ReactElement => condition ? wrapper(children) : <>{children} export default ConditionallyWrap ================================================ FILE: src/components/CreatePDF.tsx ================================================ import { FormEvent, ReactElement, ReactNode, useEffect, useState } from 'react' import { Helmet } from 'react-helmet' import { usePager } from './pager' import Alert from '@material-ui/lab/Alert' import { useSelector } from 'react-redux' import { Information, Asset, State, TaxYear } from 'ustaxes/core/data' import { YearsTaxesState } from 'ustaxes/redux' import yearFormBuilder from 'ustaxes/forms/YearForms' import { intentionallyFloat, run, runAsync } from 'ustaxes/core/util' import { Box, Button } from '@material-ui/core' import Summary from './Summary' import { savePDF } from 'ustaxes/pdfHandler' import StateForm from 'ustaxes/core/stateForms/Form' import Form from 'ustaxes/core/irsForms/Form' export default function CreatePDF(): ReactElement { const [irsErrors, updateIrsErrors] = useState([]) const [stateErrors, updateStateErrors] = useState([]) const [irsForms, updateIrsForms] = useState([]) const [stateForms, updateStateForms] = useState([]) const year: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) const info: Information = useSelector( (state: YearsTaxesState) => state[state.activeYear] ) const assets: Asset[] = useSelector( (state: YearsTaxesState) => state.assets ) useEffect(() => { const builder = yearFormBuilder(year, info, assets) const f1040Errors = builder.errors() updateIrsErrors(f1040Errors) if (f1040Errors.length > 0) { updateStateErrors(['Cannot build state return with IRS errors']) updateStateForms([]) updateIrsForms([]) } else { const irsRes = builder.f1040() const stateRes = builder.makeStateReturn() run(stateRes).fold(updateStateErrors, (stateForms) => { updateStateErrors([]) updateStateForms(stateForms) }) run(irsRes).fold(updateIrsErrors, (f1040Forms) => { updateIrsErrors([]) updateIrsForms(f1040Forms) }) } }, [info]) const lastName = info.taxPayer.primaryPerson?.lastName const residency: State | undefined = info.stateResidencies[0]?.state const federalFileName = `${lastName ?? 'Tax'}-1040.pdf` const stateFileName = `${lastName ?? 'StateTax'}-${residency}.pdf` const { navButtons } = usePager() const federalReturn = async (e: FormEvent): Promise => { e.preventDefault() const builder = yearFormBuilder(year, info, assets) const r1 = await runAsync(builder.f1040Bytes()) const r2 = await r1.mapAsync((bytes) => savePDF(bytes, federalFileName)) return r2.orThrow() } const stateReturn = async (): Promise => { const builder = yearFormBuilder(year, info, assets) const r1 = await runAsync(builder.stateReturnBytes()) const r2 = await r1.mapAsync((bytes) => savePDF(bytes, stateFileName)) return r2.orThrow() } const printActions: ReactNode = (() => { if (irsErrors.length === 0) { return ( <>

Print Copy to File

Federal

{(() => { if (info.stateResidencies.length > 0) { return

State: {info.stateResidencies[0].state}

} })()} {(() => { if (stateErrors.length === 0) { return ( ) } return stateErrors.map((e) => ( {e} )) })()} ) } })() return (
Print Copy to File | Results | UsTaxes.org {printActions} {navButtons}
) } ================================================ FILE: src/components/DataPropagator.tsx ================================================ import { ReactElement } from 'react' import { useSelector } from 'react-redux' import { YearsTaxesState } from 'ustaxes/redux' import { Information, TaxYears } from 'ustaxes/core/data' import _ from 'lodash' import { enumKeys } from 'ustaxes/core/util' import { useDispatch } from 'ustaxes/redux' import { setInfo } from 'ustaxes/redux/actions' import { Button } from '@material-ui/core' import { Alert } from '@material-ui/lab' const DataPropagator = (): ReactElement => { const wholeState = useSelector((state: YearsTaxesState) => state) const allYears = enumKeys(TaxYears) const yearIndex = _.indexOf(allYears, wholeState.activeYear) const dispatch = useDispatch() const currentYear: Information = wholeState[wholeState.activeYear] const priorYear: Information = wholeState[allYears[yearIndex - 1]] const canPropagate = yearIndex > 0 && currentYear.taxPayer.primaryPerson?.firstName === undefined && priorYear.taxPayer.primaryPerson?.firstName !== undefined const onClick = () => { if (canPropagate) { dispatch(setInfo(priorYear)) } } if (canPropagate) { return (

You have data from the prior tax year but no data for the current tax year. Would you like to migrate your data from the prior year?

) } return <> } export default DataPropagator ================================================ FILE: src/components/FormContainer/Context.tsx ================================================ import { createContext, useContext, PropsWithChildren, Context, ReactElement } from 'react' interface FormContainerProps { onSubmit?: () => void } const getProps = (): FormContainerProps => ({ onSubmit: () => null }) export const formContainerContext: Context = createContext( getProps() ) export const FormContainerProvider = ({ onSubmit, children }: PropsWithChildren): ReactElement => { return ( {children} ) } export const useFormContainer = (): FormContainerProps => useContext(formContainerContext) ================================================ FILE: src/components/FormContainer.tsx ================================================ import { PropsWithChildren, ReactElement, useState } from 'react' import { createStyles, makeStyles, useMediaQuery, IconButton, List, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText, Box, Button, Theme } from '@material-ui/core' import { Delete, Edit } from '@material-ui/icons' import { DefaultValues, SubmitHandler, useFormContext } from 'react-hook-form' import _ from 'lodash' import { ReactNode } from 'react' import { FormContainerProvider } from './FormContainer/Context' import { intentionallyFloat } from 'ustaxes/core/util' interface FormContainerProps { onDone: () => void onCancel?: () => void } export const FormContainer = ({ onDone, onCancel, children }: PropsWithChildren): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') return (
{children}
) } interface MutableListItemProps { remove?: () => void onEdit?: () => void primary: string secondary?: string | ReactElement editing?: boolean icon?: ReactElement disableEdit?: boolean } export const MutableListItem = ({ icon, primary, secondary, remove, onEdit, editing = false, disableEdit = false }: MutableListItemProps): ReactElement => { const canEdit = !editing && !disableEdit && onEdit !== undefined const canDelete = remove !== undefined && !editing const editAction = (() => { if (canEdit) { return ( ) } })() const deleteAction = (() => { if (canDelete) { return ( ) } })() const status = editing ? 'editing' : undefined return ( {icon} {primary}} secondary={secondary} /> {editAction} {deleteAction} {status} ) } interface FormListContainerProps { onSubmitAdd: SubmitHandler onSubmitEdit: (index: number) => SubmitHandler onCancel?: () => void // same default values passed to useForm defaultValues: DefaultValues items: A[] disableEditing?: boolean removeItem?: (v: number) => void primary: (a: A) => string secondary?: (a: A) => string | ReactElement max?: number icon?: (a: A) => ReactElement grouping?: (a: A) => number groupHeaders?: (ReactNode | undefined)[] } const useStyles = makeStyles((theme: Theme) => createStyles({ buttonList: { margin: `${theme.spacing(2)}px 0 ${theme.spacing(3)}px` } }) ) export interface OpenableFormContainerProps { onCancel?: () => void onSave: SubmitHandler isOpen?: boolean defaultValues: DefaultValues onOpenStateChange: (isOpen: boolean) => void allowAdd?: boolean } export const OpenableFormContainer = ( props: PropsWithChildren> ): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const { isOpen = false, allowAdd = true, defaultValues } = props const classes = useStyles() // Note useFormContext here instead of useForm reuses the // existing form context from the parent. const { reset, handleSubmit } = useFormContext() const closeForm = (): void => { props.onOpenStateChange(false) reset(defaultValues) } const onClose = (): void => { if (props.onCancel !== undefined) props.onCancel() closeForm() } const onSave: SubmitHandler = (formData): void => { props.onSave(formData) closeForm() } const openAddForm = () => { props.onOpenStateChange(true) } return ( <> {(() => { if (isOpen) { return ( {props.children} ) } else if (allowAdd) { return (
) } })()} ) } const FormListContainer = ( props: PropsWithChildren> ): ReactElement => { const { children, items, icon, max, primary, defaultValues, secondary, disableEditing = false, removeItem, onSubmitAdd, onSubmitEdit, onCancel = () => { // default do nothing }, grouping = () => 0, groupHeaders = [] } = props const [isOpen, setOpen] = useState(false) const [editing, setEditing] = useState(undefined) const allowAdd = max === undefined || items.length < max // Use the provided grouping function to split the input // array into an array of groups. Each group has a title // and a list of items, along with their original index. const groups: [ReactNode, [A, number][]][] = _.chain(items) .map<[A, number]>((x, n) => [x, n]) .groupBy(([x]) => grouping(x)) .toPairs() .map<[ReactNode, [A, number][]]>(([k, xs]) => [ groupHeaders[parseInt(k)], xs ]) .value() // Note useFormContext here instead of useForm reuses the // existing form context from the parent. const { reset } = useFormContext() const closeForm = (): void => { setEditing(undefined) setOpen(false) reset(defaultValues) } const cancel = (): void => { closeForm() onCancel() } const onSave: SubmitHandler
= (formData): void => { if (editing !== undefined) { onSubmitEdit(editing)(formData) } else { onSubmitAdd(formData) } closeForm() } const openEditForm = (n: number): (() => void) | undefined => { if (!disableEditing && editing === undefined) { return () => { setEditing(n) setOpen(true) reset(items[n]) } } } const itemDisplay = (() => { if (items.length > 0) { return ( {groups.map(([title, group], i) => (
{title} {group.map(([item, originalIndex], k) => ( removeItem(originalIndex) : undefined } icon={icon !== undefined ? icon(item) : undefined} /> ))}
))}
) } })() return ( <> {itemDisplay} {children} ) } export default FormContainer export { FormListContainer } ================================================ FILE: src/components/GettingStarted.tsx ================================================ import { ReactElement } from 'react' import { Helmet } from 'react-helmet' import { Link, useMediaQuery } from '@material-ui/core' import { StartButtons, SingleButtons } from './pager' import { isWeb } from 'ustaxes/core/util' const urls = { repo: 'https://github.com/ustaxes/UsTaxes', releases: 'https://github.com/ustaxes/UsTaxes/releases', issues: 'https://github.com/ustaxes/ustaxes/issues', twitter: 'https://twitter.com/ustaxesorg', discord: 'https://discord.gg/dAaz472mPz', aidan: 'https://github.com/thegrims', zak: 'https://github.com/zakpatterson', startPage: '/info' } const doubleButtons: ReactElement = ( ) const singleButtons: ReactElement = ( ) export default function GettingStarted(): ReactElement { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') return ( <> Getting Started | UsTaxes.org

UsTaxes.org

UsTaxes is an open source tax filing application that can be used to file the Form 1040 United States individual income tax return and some state individual income tax returns. UsTaxes is provided free of charge and requires no sharing of personal data.

Interested in using UsTaxes? The income forms, return attachments, credits, and states of residency are provided below.

Supported Income Forms

The following federal income forms are (mostly) supported:
  • W2
  • 1099-INT
  • 1099-DIV
  • 1099-B
  • 1098-E
  • 1099-R: support for normal distributions from IRA and pension accounts.
  • SSA-1099
UsTaxes can attach the following to your 1040:
  • Schedule 1 (as related to Schedule E and Schedule SE only)
  • Schedule 2
  • Schedule 3 (as related to excess FICA only)
  • Schedule 8812
  • Schedule A
  • Schedule B
  • Schedule D
  • Schedule E
  • Schedule SE
  • F1040-V
  • F6251 (AMT; only supports exercise of incentive stock options)
  • F8889 (Health Savings Accounts)
  • F8949 (Uncovered Investment Transactions)
  • F8959 (Additional Medicare Tax)
  • F8960 (Net Investment Income Tax)
  • F8995/F8995-A (Qualified Business Income Deduction)
These federal income tax credits are supported:
  • Credit for Children and Other Dependents
  • Earned Income Tax Credit

State Income Tax

State tax returns are now in development.

The following states are currently implemented:

  • Illinois
Note the following states have no income tax filing:
  • Alaska
  • Tennessee
  • Wyoming
  • Florida
  • New Hampshire
  • South Dakota
  • Texas
  • Washington
  • Nevada

10/51 states are supported. If your types of income and state residency are supported, you should be able to use UsTaxes to paper file your return!

Get Started

{isWeb() ? doubleButtons : singleButtons}

Get Involved!

The success of this project depends on user feedback. If you notice any issues at all with the project, please reach out to us!

  • File an issue: GitHub Issues
  • Message us on Twitter
  • Think you have something to contribute? Come to our{' '} Discord channel.

UsTaxes is an open source project maintained by{' '} Aidan Grimshaw and{' '} Zak Patterson.

Contributions to the GitHub repository are welcome.

Deploys by Netlify ) } ================================================ FILE: src/components/HelpAndFeedback.tsx ================================================ import { Button, Grid, TextField } from '@material-ui/core' import { Alert } from '@material-ui/lab' import { ReactElement, useEffect, useState } from 'react' import { useSelector } from 'react-redux' import { intentionallyFloat } from 'ustaxes/core/util' import { USTState } from 'ustaxes/redux/store' import anonymize from '../core/data/anonymize' const HelpAndFeedback = (): ReactElement => { const state = useSelector((state: USTState) => state) const [anonymizedState, setAnonymizedState] = useState('') const [copied, doSetCopied] = useState(false) useEffect(() => { setAnonymizedState(JSON.stringify(anonymize(state), undefined, 2)) }, [state]) const setCopied = (): void => { doSetCopied(true) setTimeout(() => doSetCopied(false), 5000) } const copiedAlert = (() => { if (copied) { return ( Copied to clipboard ) } })() const copyToClipboard = async () => { await navigator.clipboard.writeText(anonymizedState) setCopied() } return ( <>

Help and Feedback

Did you notice something wrong?

Please email feedback@ustaxes.org with any questions or bugs. If your personal data might be helpful, please copy paste the below into the body of the email. Your data below should be properly anonymized.

{copiedAlert} {' '} ) } export default HelpAndFeedback ================================================ FILE: src/components/Main.tsx ================================================ import { PropsWithChildren, ReactElement } from 'react' import { createStyles, makeStyles, CssBaseline, Grid, Theme } from '@material-ui/core' import { Routes, Route, Navigate } from 'react-router-dom' import { isMobileOnly as isMobile } from 'react-device-detect' import { PagerProvider } from './pager' import { StateLoader } from './debug' import NoMatchPage from './NoMatchPage' import SkipToLinks from './SkipToLinks' import ScrollTop from './ScrollTop' import Menu, { backPages, drawerSectionsForYear } from './Menu' import { Section, SectionItem } from './ResponsiveDrawer' import Urls from 'ustaxes/data/urls' import DataPropagator from './DataPropagator' import YearStatusBar from './YearStatusBar' import { useSelector } from 'react-redux' import { TaxYear } from 'ustaxes/core/data' import { YearsTaxesState } from 'ustaxes/redux' type Props = { isMobile: boolean } const useStyles = makeStyles((theme: Theme) => createStyles({ container: { display: 'flex' }, content: ({ isMobile }) => ({ padding: '1em 2em', [theme.breakpoints.up('sm')]: { borderRadius: '5px', boxShadow: 'rgba(0, 0, 0, 0.2) 0px 20px 30px', margin: theme.spacing(3), padding: '1em 2em' }, width: isMobile ? '100%' : undefined }), // necessary for content to be below app bar toolbar: { ...theme.mixins.toolbar, [theme.breakpoints.up('sm')]: { display: 'none' } } }) ) export default function Main(): ReactElement { const activeYear: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) const classes = useStyles({ isMobile }) const steps: SectionItem[] = drawerSectionsForYear(activeYear).flatMap( (section: Section) => section.items ) const allItems: SectionItem[] = [...steps, ...backPages] const Layout = ({ showMenu = true, children }: PropsWithChildren<{ showMenu?: boolean }>) => ( <> {showMenu ? : undefined} {showMenu ? ( {' '} ) : undefined} {isMobile && showMenu ? (
) : undefined} {children} ) return ( <>
} /> {allItems.map((item) => ( {item.element} } /> ))} } /> {!isMobile && }
) } ================================================ FILE: src/components/Menu.tsx ================================================ import { ReactElement, useState } from 'react' import { useLocation } from 'react-router-dom' import { useSelector } from 'react-redux' import { createStyles, makeStyles, AppBar, IconButton, Slide, Theme, Toolbar, Typography } from '@material-ui/core' import CloseIcon from '@material-ui/icons/Close' import MenuIcon from '@material-ui/icons/Menu' import ResponsiveDrawer, { item, Section, SectionItem } from './ResponsiveDrawer' import W2JobInfo from './income/W2JobInfo' import CreatePDF from './CreatePDF' import PrimaryTaxpayer from './TaxPayer' import RefundBankAccount from './RefundBankAccount' import SpouseAndDependent from './TaxPayer/SpouseAndDependent' import F1099Info from './income/F1099Info' import EstimatedTaxes from './payments/EstimatedTaxes' import RealEstate from './income/RealEstate' import GettingStarted from './GettingStarted' import F1098eInfo from './deductions/F1098eInfo' import ItemizedDeductions from './deductions/ItemizedDeductions' import Questions from './Questions' import HelpAndFeedback from './HelpAndFeedback' import UserSettings from './UserSettings' import Urls from 'ustaxes/data/urls' import { isMobileOnly as isMobile } from 'react-device-detect' import HealthSavingsAccounts from './savingsAccounts/healthSavingsAccounts' import IRA from './savingsAccounts/IRA' import OtherInvestments from './income/OtherInvestments' import { StockOptions } from './income/StockOptions' import { PartnershipIncome } from './income/PartnershipIncome' import { TaxYear } from 'ustaxes/core/data' import { AdvanceChildTaxCredit } from './Y2021/AdvanceChildTaxCredit' import { YearsTaxesState } from 'ustaxes/redux' const useStyles = makeStyles((theme: Theme) => createStyles({ root: { flexGrow: 1, zIndex: theme.zIndex.drawer + 1, [theme.breakpoints.up('sm')]: { display: 'none' } }, toolbar: { alignItems: 'center' }, title: { position: 'absolute', width: '100%', textAlign: 'center', pointerEvents: 'none' }, menuButton: { marginRight: theme.spacing(2), [theme.breakpoints.up('sm')]: { display: 'none' } }, gutters: { margin: '0 12px', padding: 0 } }) ) const getTitleAndPage = (currentUrl: string, year: TaxYear): string => { const backPage = backPages.find(({ url }) => url === currentUrl) if (backPage) return backPage.title const page = drawerSectionsForYear(year) .flatMap(({ title: sectionTitle, items }) => items.map(({ title, url }) => ({ sectionTitle, title, url })) ) .find(({ url }) => url === currentUrl) return `${page?.sectionTitle ?? ''} - ${page?.title ?? ''}` } export const backPages: SectionItem[] = [ { title: 'User Settings', url: Urls.settings, element: }, { title: 'Help and Feedback', url: Urls.help, element: } ] export const drawerSections: Section[] = [ { title: 'UsTaxes.org', items: [item('Getting Started', Urls.usTaxes.start, )] }, { title: 'Personal', items: [ item('Primary Taxpayer', Urls.taxPayer.info, ), item( 'Spouse and Dependents', Urls.taxPayer.spouseAndDependent, ) ] }, { title: 'Income', items: [ item('Wages (W2)', Urls.income.w2s, ), item('Income (1099)', Urls.income.f1099s, ), item('Rental income', Urls.income.realEstate, ), item( 'Other investments', Urls.income.otherInvestments, ), item('Stock options', Urls.income.stockOptions, ), item( 'Partnership Income', Urls.income.partnershipIncome, ) ] }, { title: 'Payments', items: [ item('Estimated Taxes', Urls.payments.estimatedTaxes, ) ] }, { title: 'Deductions', items: [ item('Student Loan Interest', Urls.deductions.f1098es, ), item( 'Itemized Deductions', Urls.deductions.itemized, ) ] }, { title: 'Savings Accounts', items: [ item( 'Health Savings Account (HSA)', Urls.savingsAccounts.healthSavingsAccounts, ), item( 'Individual Retirement Arrangements (IRA)', Urls.savingsAccounts.ira, ) ] }, { title: 'Results', items: [ item('Refund Information', Urls.refund, ), item('Informational Questions', Urls.questions, ), item('Review and Print', Urls.createPdf, ) ] } ] const yearSpecificPages: Partial<{ [k in TaxYear]: Section[] }> = { Y2021: [ { title: 'Tax Year 2021', items: [ item( 'Advance Child Tax Credit (Letter 6419)', Urls.Y2021.credits, ) ] } ] } export const drawerSectionsForYear = (year: TaxYear): Section[] => [ ...drawerSections.slice(0, -2), ...(yearSpecificPages[year] || []), drawerSections[drawerSections.length - 1] ] const Menu = (): ReactElement => { const classes = useStyles() const [isOpen, setOpen] = useState(!isMobile) const activeYear: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) const allSections = drawerSectionsForYear(activeYear) return ( <> setOpen((isOpen) => !isOpen)} className={classes.menuButton} > {isOpen ? : } Menu {getTitleAndPage(useLocation().pathname, activeYear)} ) } export default Menu ================================================ FILE: src/components/NoMatchPage.tsx ================================================ import { ReactElement } from 'react' import { Helmet } from 'react-helmet' import { SingleButtons } from './pager' export default function NoMatchPage(): ReactElement { return (
404 Page not found | UsTaxes.org

Page not found

We can't find the page you're looking for! Don't worry, your progress has been saved

) } ================================================ FILE: src/components/Patterns.ts ================================================ import { TaxYear, TaxYears } from 'ustaxes/core/data' import { daysInYear } from 'ustaxes/core/util' export interface BasePattern { regexp?: RegExp description?: string format?: string } export interface NumericPattern extends BasePattern { readonly inputType: 'numeric' thousandSeparator?: boolean mask?: string prefix?: string allowEmptyFormatting?: boolean decimalScale?: number min?: number max?: number } export interface TextPattern extends BasePattern { readonly inputType: 'text' } export type PatternConfig = NumericPattern | TextPattern // Convenience record syntax constructor for numeric patterns const numeric = ( regexp: RegExp, description: string, min: number | undefined = undefined, max: number | undefined = undefined, format: string | undefined = undefined, mask = '_', thousandSeparator = false, prefix = '', decimalScale: number | undefined = 0 ): NumericPattern => ({ inputType: 'numeric', regexp, description, decimalScale, min, max, format, mask, thousandSeparator, prefix }) const text = (regexp: RegExp, description: string): TextPattern => ({ inputType: 'text', regexp, description }) export const isNumeric = (p: PatternConfig): p is NumericPattern => p.inputType === 'numeric' export const isText = (p: PatternConfig): p is TextPattern => p.inputType === 'text' export const Patterns = { number: numeric(/^\d+$/, 'Input should be a number'), year: numeric( /[12][0-9]{3}/, 'Input should be a four digit year', 1900, undefined, '####', '_' ), numMonths: numeric(/[0-9]{1,2}/, 'Input should be 0-12', 0, 12, '##', ''), numDays: (year: TaxYear): NumericPattern => numeric( /[0-9]{1,3}/, `Input should be 0-${daysInYear(TaxYears[year])}`, 0, daysInYear(TaxYears[year]), '###', '' ), name: text(/^[A-Za-z ]+$/i, 'Input should only include letters and spaces'), zip: numeric( /[0-9]{5}([0-9]{4})?/, 'Input should be filled with 5 or 9 digits', undefined, undefined, '#####-####' ), ssn: numeric( /[0-9]{9}/, 'Input should be filled with 9 digits', undefined, undefined, '###-##-####' ), ein: numeric( /[0-9]{9}/, 'Input should be filled with 9 digits', undefined, undefined, '##-#######' ), currency: numeric( /[0-9]+(\.[0-9]{1,2})?/, 'Input should be a numeric value', undefined, undefined, undefined, '_', true, '$', 2 ), bankAccount: numeric( /[0-9]{4,17}/, 'Input should be filled with 4-17 digits', undefined, undefined, '#################', '' ), bankRouting: numeric( /[0-9]{9}/, 'Input should be filled with 9 digits', undefined, undefined, '#########', '_' ), usPhoneNumber: numeric( /[2-9][0-9]{9}/, 'Input should be 10 digits, not starting with 0 or 1', undefined, undefined, '(###) ###-####' ), plain: text(/.*/, '') } ================================================ FILE: src/components/Questions.tsx ================================================ import { ReactElement, useEffect } from 'react' import { Helmet } from 'react-helmet' import { Grid, List, ListItem } from '@material-ui/core' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { QuestionTagName, Responses } from 'ustaxes/core/data' import { getRequiredQuestions } from 'ustaxes/core/data/questions' import { LabeledCheckbox, LabeledInput } from './input' import { answerQuestion } from 'ustaxes/redux/actions' import { FormProvider, useForm } from 'react-hook-form' import { usePager } from './pager' import _ from 'lodash' import { intentionallyFloat } from 'ustaxes/core/util' const emptyQuestions: Responses = { CRYPTO: false, FOREIGN_ACCOUNT_EXISTS: false, FINCEN_114: false, FINCEN_114_ACCOUNT_COUNTRY: '', FOREIGN_TRUST_RELATIONSHIP: false, LIVE_APART_FROM_SPOUSE: false } const Questions = (): ReactElement => { const information = useSelector((state: TaxesState) => state.information) const stateAnswers: Responses = { ...emptyQuestions, ...information.questions } const methods = useForm({ defaultValues: stateAnswers }) const { handleSubmit, getValues, reset, formState: { isDirty } } = methods const currentValues = getValues() const { navButtons, onAdvance } = usePager() const questions = getRequiredQuestions({ ...information, questions: { ...information.questions, ...currentValues } }) const currentAnswers: Responses = { ...emptyQuestions, ...currentValues } // This form can be rerendered because the global state was modified by // another control. useEffect(() => { if (!isDirty && !_.isEqual(currentAnswers, stateAnswers)) { reset(stateAnswers) } }, []) const dispatch = useDispatch() const onSubmit = (responses: Responses): void => { // fix to remove unrequired answers: const qtags = questions.map((q) => q.tag) const unrequired = Object.keys(responses).filter( (rtag) => qtags.find((t) => t === (rtag as QuestionTagName)) === undefined ) const newResponses = { ...responses, ...Object.fromEntries(unrequired.map((k) => [k, undefined])) } dispatch(answerQuestion(newResponses)) onAdvance() } const page = (
Informational Questions | Results | UsTaxes.org

Informational Questions

Based on your prior responses, responses to these questions are required.

{questions.map((q, i) => ( {(() => { if (q.valueTag === 'boolean') { return } return })()} ))} {navButtons}
) return {page} } export default Questions ================================================ FILE: src/components/RefundBankAccount.tsx ================================================ import { ReactElement, useEffect } from 'react' import { Helmet } from 'react-helmet' import { useForm, FormProvider } from 'react-hook-form' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { LabeledInput, LabeledRadio } from './input' import { Patterns } from './Patterns' import { saveRefundInfo } from 'ustaxes/redux/actions' import _ from 'lodash' import { Refund } from 'ustaxes/core/data' import { usePager } from './pager' import { Grid } from '@material-ui/core' import { intentionallyFloat } from 'ustaxes/core/util' const blankFormData: Partial = { routingNumber: '', accountNumber: '', accountType: undefined } export default function RefundBankAccount(): ReactElement { const refund: Partial = useSelector((state: TaxesState) => { return state.information.refund }) ?? {} const defaultValues: Partial = { ...blankFormData, ...refund } const { navButtons, onAdvance } = usePager() const methods = useForm({ defaultValues }) const { handleSubmit, reset, getValues, formState: { isDirty } } = methods // const variable dispatch to allow use inside function const dispatch = useDispatch() const currentValues = { ...blankFormData, ...getValues() } // This form can be rerendered because the global state was modified by // another control. useEffect(() => { if (!isDirty && !_.isEqual(currentValues, defaultValues)) { return reset(defaultValues) } }) // component functions const onSubmit = (formData: Refund): void => { dispatch(saveRefundInfo(formData)) onAdvance() } return (
Refund Information | Results | UsTaxes.org

Refund Information

label="Account Type" name="accountType" values={[ ['Checking', 'checking'], ['Savings', 'savings'] ]} /> {navButtons}
) } ================================================ FILE: src/components/ResponsiveDrawer.tsx ================================================ import { Dispatch, Fragment, ReactElement, SetStateAction } from 'react' import { NavLink, Link } from 'react-router-dom' import { isMobileOnly as isMobile } from 'react-device-detect' import { createStyles, makeStyles, useTheme, Divider, SwipeableDrawer, IconButton, List, ListItem, ListItemText, ListSubheader, Theme } from '@material-ui/core' import GitHubIcon from '@material-ui/icons/GitHub' import TwitterIcon from '@material-ui/icons/Twitter' import { HelpOutlineRounded, Settings } from '@material-ui/icons' import Urls from 'ustaxes/data/urls' const drawerWidth = 240 const useStyles = makeStyles((theme) => createStyles({ drawer: { [theme.breakpoints.up('sm')]: { width: drawerWidth, flexShrink: 0 } }, drawerBackdrop: ({ isMobile }) => ({ top: isMobile ? '56px !important' : undefined, height: isMobile ? 'calc(100% - 56px)' : undefined }), drawerContainer: ({ isMobile }) => ({ top: isMobile ? '56px !important' : 0 }), drawerPaper: ({ isMobile }) => ({ top: isMobile ? '56px !important' : undefined, width: isMobile ? '100%' : drawerWidth, height: isMobile ? 'calc(100% - 56px)' : undefined }), listSocial: { display: 'flex', justifyContent: 'flex-end', marginRight: theme.spacing(2) }, listItemSocial: { flex: 0, padding: 0 }, list: { marginLeft: theme.spacing(0), paddingLeft: theme.spacing(0) }, listItem: { marginLeft: theme.spacing(0), paddingLeft: theme.spacing(2) }, sectionHeader: { marginLeft: theme.spacing(2) } }) ) export interface Section { title: string items: SectionItem[] } export interface SectionItem { title: string url: string element: ReactElement } export const item = ( title: string, url: string, element: ReactElement ): SectionItem => ({ title, url, element }) export interface DrawerItemsProps { sections: Section[] isOpen: boolean setOpen: Dispatch> } function ResponsiveDrawer(props: DrawerItemsProps): ReactElement { const classes = useStyles({ isMobile }) const theme = useTheme() const { sections, isOpen, setOpen } = props const drawer = ( <> {/* {isMobile && } */} {sections.map(({ title, items }) => ( {title}} className={classes.list} > {items.map((item) => ( ))} ))} ) return ( ) } export default ResponsiveDrawer ================================================ FILE: src/components/SaveToFile.tsx ================================================ import { Button, ButtonProps } from '@material-ui/core' import { PropsWithChildren, ReactElement } from 'react' import { useDispatch } from 'react-redux' import { fsPersist } from 'ustaxes/redux/fs/Actions' const SaveToFile = (props: PropsWithChildren): ReactElement => { const dispatch = useDispatch() const { children, ...rest } = props const onClick = () => dispatch(fsPersist()) return ( ) } export default SaveToFile ================================================ FILE: src/components/ScrollTop.tsx ================================================ import { ReactElement } from 'react' import { makeStyles, useScrollTrigger, Fab, Zoom } from '@material-ui/core' import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp' const useStyles = makeStyles((theme) => ({ root: { position: 'fixed', bottom: theme.spacing(2), right: theme.spacing(2) } })) const ScrollTop = (): ReactElement => { const classes = useStyles() const trigger = useScrollTrigger({ target: window, disableHysteresis: true, threshold: 100 }) const handleClick = () => { window.scrollTo({ top: 0, behavior: 'smooth' }) } return (
) } export default ScrollTop ================================================ FILE: src/components/SkipToLinks.tsx ================================================ import { ReactElement } from 'react' import { makeStyles, Link, Theme, Typography, useMediaQuery } from '@material-ui/core' type Props = { prefersDarkMode: boolean } const useStyles = makeStyles((theme: Theme) => ({ root: { position: 'absolute', display: 'flex', top: 0, left: 0, zIndex: 1201 }, container: { alignItems: 'center', display: 'flex', overflow: 'hidden' }, main: ({ prefersDarkMode }) => ({ '&:not(:focus)': { position: 'absolute', clip: 'rect(1px, 1px, 1px, 1px)', overflow: 'hidden', padding: 0 }, '&:focus': { borderRadius: 2, color: prefersDarkMode ? 'white' : 'rgba(0, 0, 0, 0.54)', backgroundColor: prefersDarkMode ? '#303030' : 'white', border: `1px solid ${theme.palette.primary.main}`, cursor: 'pointer', display: 'inline-block', margin: 12, minHeight: 32, textAlign: 'center', width: 130, padding: 12 } }) })) const SkipToLinks = (): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const classes = useStyles({ prefersDarkMode }) return (
{ document.getElementsByTagName('main')[0].focus() }} onKeyDown={(e) => { if (e.key === 'Enter') { document.getElementsByTagName('main')[0].focus() } }} > Skip to main content
) } export default SkipToLinks ================================================ FILE: src/components/Summary.tsx ================================================ import { ReactElement } from 'react' import { Avatar, Box, Grid, List, Typography, TableContainer, Table, TableBody, TableRow, TableCell, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core' import { YearsTaxesState } from 'ustaxes/redux' import { TaxYear } from 'ustaxes/core/data' import { Check, Close, ExpandMore } from '@material-ui/icons' import Alert from '@material-ui/lab/Alert' import { useSelector } from 'react-redux' import { createSummary, SummaryData } from './SummaryData' import { Currency } from './input' import { displayRound } from 'ustaxes/core/irsForms/util' import StateForm from 'ustaxes/core/stateForms/Form' import Form from 'ustaxes/core/irsForms/Form' interface BinaryStateListItemProps { active: boolean children: string | ReactElement | ReactElement[] } const BinaryStateListItem = ({ active, children }: BinaryStateListItemProps): ReactElement => { return ( {active ? ( ) : ( )} {children} ) } interface F1040SummaryProps { summary: SummaryData } const F1040Summary = ({ summary }: F1040SummaryProps): ReactElement => ( <> {(() => { if (summary.amountOwed !== undefined && summary.amountOwed > 0) { return (
Amount Owed:
) } if (summary.refundAmount !== undefined && summary.refundAmount > 0) { return (
Refund Amount:
) } })()}

Credits

{summary.credits.map((credit) => ( {credit.name} {(() => { if (credit.value !== undefined) { return ( Credit: ) } return <> })()} ))} {(() => { if (summary.worksheets.length > 0) { return (

Worksheets

{summary.worksheets.map((worksheet, idx) => ( }> {worksheet.name} {worksheet.lines.map((line) => ( Line {line.line} {typeof line.value === 'number' ? ( ) : ( line.value )} ))}
))}
) } return <> })()} ) interface SummaryProps { errors: string[] stateErrors?: string[] irsForms: Form[] stateForms?: StateForm[] } const Summary = ({ errors, irsForms }: SummaryProps): ReactElement => { const year: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) const summaryBody = ( <>

Federal

{(() => { if (errors.length > 0) { return ( {errors.map((error, i) => ( {error} ))} ) } else { const summary = createSummary(year, irsForms) if (summary === undefined) { return undefined } return } })()} ) return (

Summary

{summaryBody}
) } export default Summary ================================================ FILE: src/components/SummaryData.ts ================================================ import Form from 'ustaxes/core/irsForms/Form' import { TaxYear } from 'ustaxes/core/data' import F1040For2020 from 'ustaxes/forms/Y2020/irsForms/F1040' import F1040For2021 from 'ustaxes/forms/Y2021/irsForms/F1040' interface Credit { name: string value?: number notes?: string allowed: boolean } export interface WorksheetLine { line: number | string value: string | number | undefined } export interface WorksheetData { name: string lines: WorksheetLine[] } export interface SummaryData { credits: Credit[] worksheets: WorksheetData[] refundAmount?: number amountOwed?: number } interface SummaryCreator { summary: (a: A) => SummaryData } const emptySummary = { credits: [], worksheets: [] } export const SummaryCreatorFor2020: SummaryCreator = { summary: (f: F1040For2020): SummaryData => ({ credits: [ { name: 'Earned Income Credit', value: f.scheduleEIC.credit(), allowed: f.scheduleEIC.allowed() }, { name: 'Children and Other Dependents', value: f.childTaxCreditWorksheet.credit(), allowed: f.childTaxCreditWorksheet.isAllowed() } ], worksheets: [], refundAmount: f.l35a(), amountOwed: f.l37() }) } export const SummaryCreatorFor2021: SummaryCreator = { summary: (f: F1040For2021): SummaryData => ({ credits: [ { name: 'Earned income credit', value: f.scheduleEIC.credit(), allowed: f.scheduleEIC.allowed() } ], worksheets: [ ...(f.qualifiedAndCapGainsWorksheet !== undefined ? [f.qualifiedAndCapGainsWorksheet.getSummaryData()] : []) ], refundAmount: f.l35a(), amountOwed: f.l37() }) } export const createSummary = ( year: TaxYear, forms: Form[] ): SummaryData | undefined => { const f1040 = forms.find((f) => f.tag === 'f1040') if (f1040 === undefined) { return undefined } switch (year) { case 'Y2019': { return emptySummary } case 'Y2020': { return SummaryCreatorFor2020.summary(f1040 as F1040For2020) } case 'Y2021': { return SummaryCreatorFor2021.summary(f1040 as F1040For2021) } } } ================================================ FILE: src/components/TaxPayer/Address.tsx ================================================ import { ReactElement } from 'react' import { useFormContext, useWatch } from 'react-hook-form' import { LabeledCheckbox, LabeledInput, USStateDropDown } from 'ustaxes/components/input' import { CountryDropDown } from 'ustaxes/components/input/LabeledDropdown' import { Patterns } from 'ustaxes/components/Patterns' interface AddressProps { autofocus?: boolean checkboxText: string allowForeignCountry?: boolean } export default function AddressFields(props: AddressProps): ReactElement { const { autofocus, checkboxText = 'Check if you have a foreign address', allowForeignCountry = true } = props const { control } = useFormContext<{ isForeignCountry: boolean }>() const isForeignCountry = useWatch({ name: 'isForeignCountry', control }) const csz: ReactElement = (() => { if (!allowForeignCountry || !isForeignCountry) { return ( <> ) } return ( <> ) })() return ( <> {(() => { if (allowForeignCountry) { return ( ) } })()} {csz} ) } ================================================ FILE: src/components/TaxPayer/PersonFields.tsx ================================================ import { PropsWithChildren, ReactElement } from 'react' import { IconButton, List, ListItem, ListItemIcon, ListItemSecondaryAction } from '@material-ui/core' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { formatSSID, LabeledInput, LabeledCheckbox, DatePicker } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { removeDependent } from 'ustaxes/redux/actions' import { Person } from 'ustaxes/core/data' import DeleteIcon from '@material-ui/icons/Delete' import EditIcon from '@material-ui/icons/Edit' import ListItemText from '@material-ui/core/ListItemText' import PersonIcon from '@material-ui/icons/Person' export const labels = { fname: 'First Name and Initial', lname: 'Last Name', dateOfBirth: 'Date of Birth', ssn: 'SSN / TIN' } export const PersonFields = ({ children, autofocus }: PropsWithChildren<{ autofocus?: boolean }>): ReactElement => { return ( <> {children} ) } interface PersonListItemProps { person: Person remove: () => void onEdit?: () => void editing?: boolean } export const PersonListItem = ({ person, remove, onEdit, editing = false }: PersonListItemProps): ReactElement => ( {(() => { if (editing) { return ( ) } })()} ) interface ListDependentsProps { onEdit?: (index: number) => void editing?: number } export function ListDependents({ onEdit = () => { /* default do nothing */ }, editing }: ListDependentsProps): ReactElement { const dependents = useSelector( (state: TaxesState) => state.information.taxPayer.dependents ) const dispatch = useDispatch() const drop = (i: number): void => dispatch(removeDependent(i)) return ( {dependents.map((p, i) => ( drop(i)} person={p} editing={editing === i} onEdit={() => onEdit(i)} /> ))} ) } ================================================ FILE: src/components/TaxPayer/SpouseAndDependent.tsx ================================================ import { ReactElement, useEffect, useState } from 'react' import { Helmet } from 'react-helmet' import { useForm, FormProvider } from 'react-hook-form' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { Patterns } from 'ustaxes/components/Patterns' import { LabeledInput, LabeledCheckbox, formatSSID, GenericLabeledDropdown } from 'ustaxes/components/input' import { TaxPayer, Dependent, Spouse, PersonRole, FilingStatus, FilingStatusTexts, filingStatuses } from 'ustaxes/core/data' import { addDependent, addSpouse, editDependent, removeDependent, removeSpouse, saveFilingStatusInfo } from 'ustaxes/redux/actions' import { PersonFields } from './PersonFields' import { FormListContainer } from 'ustaxes/components/FormContainer' import { usePager } from 'ustaxes/components/pager' import { Box, Grid } from '@material-ui/core' import { Person } from '@material-ui/icons' import { Alert } from '@material-ui/lab' import { intentionallyFloat } from 'ustaxes/core/util' interface UserPersonForm { firstName: string lastName: string ssid: string isBlind: boolean dateOfBirth?: Date } const blankUserPersonForm: UserPersonForm = { firstName: '', lastName: '', ssid: '', isBlind: false, dateOfBirth: undefined } interface UserDependentForm extends UserPersonForm { relationship: string isStudent: boolean numberOfMonths: string } const blankUserDependentForm: UserDependentForm = { ...blankUserPersonForm, relationship: '', isStudent: false, numberOfMonths: '' } const toDependent = (formData: UserDependentForm): Dependent => { const { isStudent, numberOfMonths, ...rest } = formData if (formData.dateOfBirth === undefined) { throw new Error('Called with undefined date of birth') } return { ...rest, role: PersonRole.DEPENDENT, qualifyingInfo: { numberOfMonths: parseInt(numberOfMonths), isStudent }, dateOfBirth: formData.dateOfBirth.toISOString() } } const toDependentForm = (dependent: Dependent): UserDependentForm => { const { qualifyingInfo, dateOfBirth, ...rest } = dependent return { ...rest, numberOfMonths: qualifyingInfo?.numberOfMonths.toString() ?? '', isStudent: qualifyingInfo?.isStudent ?? false, dateOfBirth } } interface UserSpouseForm extends UserPersonForm { isTaxpayerDependent: boolean } const blankUserSpouseForm = { ...blankUserPersonForm, isTaxpayerDependent: false } const toSpouse = (formData: UserSpouseForm): Spouse => { if (formData.dateOfBirth === undefined) { throw new Error('Called with undefined date of birth') } return { ...formData, role: PersonRole.SPOUSE, dateOfBirth: formData.dateOfBirth.toISOString() } } const toSpouseForm = (spouse: Spouse): UserSpouseForm => ({ ...spouse, dateOfBirth: new Date(spouse.dateOfBirth) }) export const AddDependentForm = (): ReactElement => { const dependents = useSelector( (state: TaxesState) => state.information.taxPayer.dependents ) const defaultValues = blankUserDependentForm const dispatch = useDispatch() const methods = useForm({ defaultValues }) const onSubmitAdd = (formData: UserDependentForm): void => { dispatch(addDependent(toDependent(formData))) } const onSubmitEdit = (index: number) => (formData: UserDependentForm): void => { dispatch( editDependent({ index, value: toDependent(formData) }) ) } const page = ( defaultValues={defaultValues} onSubmitAdd={onSubmitAdd} onSubmitEdit={onSubmitEdit} items={dependents.map((a) => toDependentForm(a))} primary={(a) => `${a.firstName} ${a.lastName}`} secondary={(a) => formatSSID(a.ssid)} icon={() => } removeItem={(i) => dispatch(removeDependent(i))} > ) return {page} } export const SpouseInfo = (): ReactElement => { const defaultValues = blankUserSpouseForm const methods = useForm({ defaultValues }) const { getValues } = methods const dispatch = useDispatch() const spouse: Spouse | undefined = useSelector((state: TaxesState) => { return state.information.taxPayer.spouse }) const onSubmit = (): void => { dispatch(addSpouse(toSpouse(getValues()))) } const onSubmitEdit = (): (() => void) => onSubmit const page = ( `${s.firstName} ${s.lastName}`} secondary={(s) => formatSSID(s.ssid)} icon={() => } onSubmitAdd={onSubmit} onSubmitEdit={onSubmitEdit} max={1} removeItem={() => dispatch(removeSpouse)} > ) return {page} } export const FilingStatusDropdown = (): ReactElement => { const dispatch = useDispatch() const onSubmit = (formData: { filingStatus: FilingStatus }): void => { dispatch(saveFilingStatusInfo(formData.filingStatus)) onAdvance() } const taxPayer: TaxPayer | undefined = useSelector((state: TaxesState) => { return state.information.taxPayer }) const allowedFilingStatuses = filingStatuses(taxPayer) const { onAdvance, navButtons } = usePager() const defaultValues: { filingStatus: FilingStatus | '' } = { filingStatus: (() => { if ( taxPayer.filingStatus !== undefined && allowedFilingStatuses.includes(taxPayer.filingStatus) ) { return taxPayer.filingStatus } return '' })() } const methods = useForm({ defaultValues }) const [error, setError] = useState(undefined) const { handleSubmit, getValues, reset, watch, formState: { isDirty } } = methods const currentFilingStatus = getValues().filingStatus const newValue = watch() useEffect(() => { // Handle state updates outside this control if (!isDirty && currentFilingStatus !== defaultValues.filingStatus) { reset(defaultValues) } // Handle other state updates that cause current // value to be invalid else if ( currentFilingStatus !== '' && !allowedFilingStatuses.includes(currentFilingStatus) ) { reset({}) setError( Filing status was set to {FilingStatusTexts[currentFilingStatus]}{' '} which is no longer allowed due to your inputs. Make another selection. ) } else if (currentFilingStatus !== '' || newValue.filingStatus !== '') { setError(undefined) } }, [defaultValues.filingStatus, currentFilingStatus]) return (
label="Filing Status" dropDownData={allowedFilingStatuses} valueMapping={(x) => x} keyMapping={(x, i) => i} textMapping={(status) => FilingStatusTexts[status]} name="filingStatus" /> {error} {navButtons}
) } const SpouseAndDependent = (): ReactElement => ( <> Family Information | Personal | UsTaxes.org

Family Information

Spouse Information

Dependent Information

) export default SpouseAndDependent ================================================ FILE: src/components/TaxPayer/TaxPayer.tsx ================================================ import { ReactElement, useEffect } from 'react' import { Helmet } from 'react-helmet' import { FormProvider, useForm } from 'react-hook-form' import _ from 'lodash' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { savePrimaryPersonInfo, saveStateResidencyInfo, saveContactInfo } from 'ustaxes/redux/actions' import { Address, ContactInfo, PersonRole, PrimaryPerson, State, StateResidency, TaxPayer } from 'ustaxes/core/data' import { PersonFields } from './PersonFields' import { usePager } from 'ustaxes/components/pager' import { LabeledCheckbox, USStateDropDown, LabeledInput } from 'ustaxes/components/input' import AddressFields from './Address' import { Grid } from '@material-ui/core' import { Patterns } from 'ustaxes/components/Patterns' import { intentionallyFloat } from 'ustaxes/core/util' interface TaxPayerUserForm { firstName: string lastName: string ssid: string contactPhoneNumber?: string contactEmail?: string role: PersonRole address: Address isForeignCountry: boolean isTaxpayerDependent: boolean stateResidency?: State isBlind: boolean dateOfBirth?: Date } const defaultTaxpayerUserForm: TaxPayerUserForm = { firstName: '', lastName: '', ssid: '', contactPhoneNumber: '', contactEmail: '', role: PersonRole.PRIMARY, isForeignCountry: false, address: { address: '', city: '', aptNo: '', state: undefined, zip: undefined }, isTaxpayerDependent: false, isBlind: false, dateOfBirth: undefined } const asPrimaryPerson = (formData: TaxPayerUserForm): PrimaryPerson => { if (formData.dateOfBirth === undefined) { throw new Error('Called with undefined date of birth') } return { address: formData.address, firstName: formData.firstName, lastName: formData.lastName, ssid: formData.ssid.replace(/-/g, ''), isTaxpayerDependent: formData.isTaxpayerDependent, role: PersonRole.PRIMARY, dateOfBirth: formData.dateOfBirth.toISOString(), isBlind: formData.isBlind } } const asContactInfo = (formData: TaxPayerUserForm): ContactInfo => ({ contactPhoneNumber: formData.contactPhoneNumber, contactEmail: formData.contactEmail }) const asTaxPayerUserForm = (person: PrimaryPerson): TaxPayerUserForm => ({ ...person, isForeignCountry: person.address.foreignCountry !== undefined, role: PersonRole.PRIMARY, dateOfBirth: new Date(person.dateOfBirth) }) export default function PrimaryTaxpayer(): ReactElement { // const variable dispatch to allow use inside function const dispatch = useDispatch() const { onAdvance, navButtons } = usePager() const taxPayer: TaxPayer | undefined = useSelector((state: TaxesState) => { return state.information.taxPayer }) const stateResidency: StateResidency[] = useSelector( (state: TaxesState) => state.information.stateResidencies ) const newTpForm: TaxPayerUserForm = { ...defaultTaxpayerUserForm, ...(taxPayer.primaryPerson !== undefined ? { ...asTaxPayerUserForm(taxPayer.primaryPerson), contactPhoneNumber: taxPayer.contactPhoneNumber, contactEmail: taxPayer.contactEmail, stateResidency: stateResidency[0]?.state ?? taxPayer.primaryPerson.address.state } : {}) } const methods = useForm({ defaultValues: newTpForm }) const { handleSubmit, getValues, reset, formState: { isDirty } } = methods // This form can be rerendered because the global state was modified by // another control. const currentValues = { ...defaultTaxpayerUserForm, ...getValues() } useEffect(() => { if (!isDirty && !_.isEqual(currentValues, newTpForm)) { return reset(newTpForm) } }) const onSubmit = (form: TaxPayerUserForm): void => { dispatch(savePrimaryPersonInfo(asPrimaryPerson(form))) dispatch(saveContactInfo(asContactInfo(form))) dispatch(saveStateResidencyInfo({ state: form.stateResidency as State })) onAdvance() } const page = (
Primary Taxpayer Information | Personal | UsTaxes.org

Primary Taxpayer Information

{navButtons}
) return {page} } ================================================ FILE: src/components/TaxPayer/index.tsx ================================================ import TaxPayer from './TaxPayer' export default TaxPayer ================================================ FILE: src/components/UserSettings.tsx ================================================ import { Check } from '@material-ui/icons' import { ReactElement, useState } from 'react' import { useDispatch } from 'react-redux' import { fsRecover } from 'ustaxes/redux/fs/Actions' import { LoadRaw } from 'ustaxes/redux/fs/Load' import SaveToFile from './SaveToFile' const UserSettings = (): ReactElement => { const dispatch = useDispatch() const [done, setDone] = useState(false) return ( <>

User Settings

Save data

Save your data for backup or to import into desktop application

Save data to file

Load data

Load your saved data from a file. Warning, this will overwrite present state.

: undefined} accept="*.json" handleData={(state) => { if (!done) { dispatch(fsRecover(state)) setDone(true) } }} variant="contained" color="primary" > Load ) } export default UserSettings ================================================ FILE: src/components/Y2021/AdvanceChildTaxCredit.tsx ================================================ import { ReactElement } from 'react' import { Grid } from '@material-ui/core' import { FormProvider, useForm } from 'react-hook-form' import { Credit, CreditType, Person, PersonRole } from 'ustaxes/core/data' import { useDispatch, useSelector } from 'ustaxes/redux' import { addCredit, editCredit } from 'ustaxes/redux/actions' import { FormListContainer } from '../FormContainer' import { Currency, GenericLabeledDropdown, LabeledInput } from '../input' import { usePager } from '../pager' import { Patterns } from '../Patterns' interface CreditUserInput { recipient: PersonRole amount: string } const blankCreditUserInput: Partial = { amount: '' } const toCredit = (u: CreditUserInput): Credit => { const amount = parseFloat(u.amount) return { type: CreditType.AdvanceChildTaxCredit, amount, recipient: u.recipient } } const toCreditUserInput = (c: Credit): CreditUserInput => { return { ...blankCreditUserInput, ...c, amount: c.amount.toFixed(2) } } export const AdvanceChildTaxCredit = (): ReactElement => { const defaultValues = blankCreditUserInput const credits = useSelector(({ information }) => information.credits) const taxPayer = useSelector(({ information }) => information.taxPayer) const spouse = taxPayer.spouse const blank = { firstName: '', lastName: '' } const { firstName: primaryFirst, lastName: primaryLast } = taxPayer.primaryPerson ?? blank const { firstName: spouseFirst, lastName: spouseLast } = taxPayer.spouse ?? blank const primaryName = `${primaryFirst} ${primaryLast}` const spouseName = `${spouseFirst} ${spouseLast}` const { navButtons, onAdvance } = usePager() const advanceChildTaxCredits = credits .map<[number, Credit]>((c, i) => [i, c]) .filter(([, c]) => c.type === CreditType.AdvanceChildTaxCredit) const dispatch = useDispatch() const methods = useForm({ defaultValues }) const onSubmitAdd = (formData: CreditUserInput): void => { dispatch(addCredit(toCredit(formData))) } const onSubmitEdit = (index: number) => (formData: CreditUserInput): void => { dispatch(editCredit({ index, value: toCredit(formData) })) } // People for recipient selector const people: Person[] = [taxPayer.primaryPerson, spouse].flatMap((p) => p !== undefined ? [p as Person] : [] ) return (

Advance Child Tax Credit Payments

Enter the advance Child Tax Credit payments you received, if any, indicated on Letter 6419.

defaultValues={defaultValues} primary={(c) => c.recipient === PersonRole.PRIMARY ? primaryName : spouseName } secondary={(c) => } items={advanceChildTaxCredits.map(([, c]) => toCreditUserInput(c))} onSubmitAdd={onSubmitAdd} onSubmitEdit={onSubmitEdit} > [PersonRole.PRIMARY, PersonRole.SPOUSE][i] } name="recipient" keyMapping={(p: Person, i: number) => i} textMapping={(p) => `${p.firstName} ${p.lastName}`} />
{navButtons}
) } ================================================ FILE: src/components/YearDropDown.tsx ================================================ import { Button, Grid } from '@material-ui/core' import { ReactElement } from 'react' import { FormProvider, useForm } from 'react-hook-form' import { useSelector } from 'react-redux' import { setActiveYear } from 'ustaxes/redux/actions' import { YearsTaxesState, useDispatch } from 'ustaxes/redux' import { enumKeys, intentionallyFloat } from 'ustaxes/core/util' import { GenericLabeledDropdown } from './input' import { TaxYear, TaxYears } from 'ustaxes/core/data' interface YearForm { year: TaxYear } interface YearDropProps { onDone?: () => void } const YearDropDown = (props: YearDropProps): ReactElement => { const { onDone = () => { // default do nothing } } = props const year = useSelector((state: YearsTaxesState) => state.activeYear) const methods = useForm({ defaultValues: { year } }) const { handleSubmit, watch } = methods const selected = watch('year') const dirty = selected !== year const dispatch = useDispatch() const onSubmit = ({ year }: YearForm) => { dispatch(setActiveYear(year)) onDone() } return ( dropDownData={enumKeys(TaxYears)} name="year" label="Select Tax Year" valueMapping={(x) => x} keyMapping={(x) => x} textMapping={(x) => `${TaxYears[x]}`} sizes={{ xs: 9 }} /> ) } export default YearDropDown ================================================ FILE: src/components/YearStatusBar.tsx ================================================ import { Link } from '@material-ui/core' import { ReactElement, useState } from 'react' import { useSelector } from 'react-redux' import { YearsTaxesState } from 'ustaxes/redux' import { TaxYears } from 'ustaxes/core/data' import YearDropDown from './YearDropDown' const YearStatusBar = (): ReactElement => { const year = useSelector((state: YearsTaxesState) => state.activeYear) const [isOpen, setOpen] = useState(false) const openButton = ( { e.preventDefault() setOpen(true) }} > {TaxYears[year]} ) return (

Editing Information for {isOpen ? TaxYears[year] : openButton}

{isOpen ? setOpen(false)} /> : undefined}
) } export default YearStatusBar ================================================ FILE: src/components/debug.tsx ================================================ import { ReactElement } from 'react' import { IconButton, makeStyles } from '@material-ui/core' import { Star } from '@material-ui/icons' import fc from 'fast-check' import { useDispatch, YearsTaxesState } from 'ustaxes/redux' import { setInfo } from 'ustaxes/redux/actions' import { Information, TaxYears } from 'ustaxes/core/data' import * as arbitraries from 'ustaxes/core/tests/arbitraries' import * as prand from 'pure-rand' import { useSelector } from 'react-redux' const useStyles = makeStyles(() => ({ root: { position: 'absolute', top: '0px', right: '30px' }, button: {} })) export const StateLoader = (): ReactElement => { if (process.env.NODE_ENV === 'production') { return <> } const dispatch = useDispatch() const year = useSelector((state: YearsTaxesState) => state.activeYear) const classes = useStyles() const gen = new fc.Random(prand.mersenne(new Date().getMilliseconds())) const information = arbitraries.forYear(TaxYears[year]).information() const generator = (): Information => information.noShrink().generate(gen).value return (
dispatch(setInfo(generator()))} > Seed random state
) } ================================================ FILE: src/components/deductions/F1098eInfo.tsx ================================================ import { ReactElement } from 'react' import { Helmet } from 'react-helmet' import { FormProvider, useForm } from 'react-hook-form' import SchoolIcon from '@material-ui/icons/School' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { add1098e, edit1098e, remove1098e } from 'ustaxes/redux/actions' import { usePager } from 'ustaxes/components/pager' import { Currency, LabeledInput } from 'ustaxes/components/input' import { F1098e } from 'ustaxes/core/data' import { Patterns } from 'ustaxes/components/Patterns' import { FormListContainer } from 'ustaxes/components/FormContainer' import { Grid } from '@material-ui/core' import { intentionallyFloat } from 'ustaxes/core/util' const showInterest = (a: F1098e): ReactElement => { return } interface F1098EUserInput { lender: string interest: string | number } const blankUserInput: F1098EUserInput = { lender: '', interest: '' } const toUserInput = (f: F1098e): F1098EUserInput => ({ ...blankUserInput, lender: f.lender, interest: f.interest }) const toF1098e = (f: F1098EUserInput): F1098e => { return { lender: f.lender, interest: Number(f.interest) } } export default function F1098eInfo(): ReactElement { const f1098es = useSelector((state: TaxesState) => state.information.f1098es) const defaultValues: F1098EUserInput = blankUserInput const { onAdvance, navButtons } = usePager() const methods = useForm({ defaultValues }) const { handleSubmit } = methods const dispatch = useDispatch() const onAdd1098e = (formData: F1098EUserInput): void => { dispatch(add1098e(toF1098e(formData))) } const onEdit1098e = (index: number) => (formData: F1098EUserInput): void => { dispatch(edit1098e({ value: toF1098e(formData), index })) } const form: ReactElement | undefined = ( toUserInput(a))} removeItem={(i) => dispatch(remove1098e(i))} primary={(f) => f.lender} secondary={(f) => showInterest(toF1098e(f))} icon={() => } >

Input data from 1098-E

) return (
1098-E Information | Deductions | UsTaxes.org

1098-E Information

{form} {navButtons}
) } ================================================ FILE: src/components/deductions/ItemizedDeductions.tsx ================================================ import { ReactElement, ReactNode } from 'react' import { Helmet } from 'react-helmet' import { FormProvider, useForm } from 'react-hook-form' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { setItemizedDeductions } from 'ustaxes/redux/actions' import { usePager } from 'ustaxes/components/pager' import { LabeledInput, LabeledCheckbox } from 'ustaxes/components/input' import { ItemizedDeductions } from 'ustaxes/core/data' import { Patterns } from 'ustaxes/components/Patterns' import { Grid, Box } from '@material-ui/core' import { Alert } from '@material-ui/lab' import { intentionallyFloat } from 'ustaxes/core/util' interface ItemizedDeductionUserInput { medicalAndDental: string | number stateAndLocalTaxes: string | number isSalesTax: boolean stateAndLocalRealEstateTaxes: string | number stateAndLocalPropertyTaxes: string | number interest8a: string | number interest8b: string | number interest8c: string | number interest8d: string | number investmentInterest: string | number charityCashCheck: string | number charityOther: string | number } const blankUserInput: ItemizedDeductionUserInput = { medicalAndDental: '', stateAndLocalTaxes: '', isSalesTax: false, stateAndLocalRealEstateTaxes: '', stateAndLocalPropertyTaxes: '', interest8a: '', interest8b: '', interest8c: '', interest8d: '', investmentInterest: '', charityCashCheck: '', charityOther: '' } const toUserInput = (f: ItemizedDeductions): ItemizedDeductionUserInput => ({ ...blankUserInput, medicalAndDental: f.medicalAndDental, stateAndLocalTaxes: f.stateAndLocalTaxes, isSalesTax: f.isSalesTax, stateAndLocalRealEstateTaxes: f.stateAndLocalRealEstateTaxes, stateAndLocalPropertyTaxes: f.stateAndLocalPropertyTaxes, interest8a: f.interest8a, interest8b: f.interest8b, interest8c: f.interest8c, interest8d: f.interest8d, investmentInterest: f.investmentInterest, charityCashCheck: f.charityCashCheck, charityOther: f.charityOther }) const toItemizedDeductions = ( f: ItemizedDeductionUserInput ): ItemizedDeductions => { return { medicalAndDental: Number(f.medicalAndDental), stateAndLocalTaxes: Number(f.stateAndLocalTaxes), isSalesTax: f.isSalesTax, stateAndLocalPropertyTaxes: Number(f.stateAndLocalPropertyTaxes), stateAndLocalRealEstateTaxes: Number(f.stateAndLocalRealEstateTaxes), interest8a: Number(f.interest8a), interest8b: Number(f.interest8b), interest8c: Number(f.interest8c), interest8d: Number(f.interest8d), investmentInterest: Number(f.investmentInterest), charityCashCheck: Number(f.charityCashCheck), charityOther: Number(f.charityOther) } } export const ItemizedDeductionsInfo = (): ReactElement => { const itemizedDeductions: ItemizedDeductions | undefined = useSelector( (state: TaxesState) => { return state.information.itemizedDeductions } ) const defaultValues: ItemizedDeductionUserInput = { ...blankUserInput, ...(itemizedDeductions !== undefined ? toUserInput(itemizedDeductions) : {}) } const { onAdvance, navButtons } = usePager() const methods = useForm({ defaultValues }) const { handleSubmit, watch } = methods const dispatch = useDispatch() const onSubmit = (form: ItemizedDeductionUserInput): void => { dispatch(setItemizedDeductions(toItemizedDeductions(form))) onAdvance() } const charityCashCheck: string | number = watch('charityCashCheck') const charityWarning: ReactNode = (() => { if (Number(charityCashCheck || 0) > 0) { return (
See Pub. 526 to figure the amount of your deduction if any of the following applies.
1. Your cash contributions or contributions of ordinary income property are more than 30% of the amount on Form 1040 or 1040-SR, line 11.
2. Your gifts of capital gain property are more than 20% of the amount on Form 1040 or 1040-SR, line 11.
3. You gave gifts of property that increased in value or gave gifts of the use of property.
) } })() // Limit charity to $500 const currencyMax500Pattern = Object.assign({}, Patterns.currency) currencyMax500Pattern.max = 500 const form: ReactElement | undefined = (

Medical and Dental Expenses

Taxes You Paid

Interest You Paid

Gifts To Charity

{charityWarning}
) return (

If you do not wish to itemize, you can skip this form. The itemized deductions will only be used if they result in a higher deduction than the standard deduction.

Itemized Deduction Information | Deductions | UsTaxes.org

Itemized Deduction Information

{form} {navButtons}
) } export default ItemizedDeductionsInfo ================================================ FILE: src/components/income/F1099Info.tsx ================================================ import { ReactElement } from 'react' import { Helmet } from 'react-helmet' import { Link } from 'react-router-dom' import Alert from '@material-ui/lab/Alert' import { useForm, FormProvider } from 'react-hook-form' import { Icon, Grid } from '@material-ui/core' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { add1099, edit1099, remove1099 } from 'ustaxes/redux/actions' import { usePager } from 'ustaxes/components/pager' import { Person, PersonRole, Supported1099, Income1099Type, PlanType1099, PlanType1099Texts } from 'ustaxes/core/data' import { Currency, formatSSID, GenericLabeledDropdown, LabeledInput, boxLabel } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { FormListContainer } from 'ustaxes/components/FormContainer' import { intentionallyFloat } from 'ustaxes/core/util' const showIncome = (a: Supported1099): ReactElement => { switch (a.type) { case Income1099Type.INT: { return } case Income1099Type.B: { const ltg = a.form.longTermProceeds - a.form.longTermCostBasis const stg = a.form.shortTermProceeds - a.form.shortTermCostBasis return ( Long term:
Short term:
) } case Income1099Type.DIV: { return } case Income1099Type.R: { return ( Plan Type: {a.form.planType}
Gross Distribution:
Taxable Amount:
Federal Income Tax Withheld:{' '}
) } case Income1099Type.SSA: { return ( {/* Benefits Paid:
Benefits Repaid:
*/} Net Benefits:
Federal Income Tax Withweld:{' '}
) } } } interface F1099UserInput { formType: Income1099Type | undefined payer: string // Int fields interest: string | number // B Fields shortTermProceeds: string | number shortTermCostBasis: string | number longTermProceeds: string | number longTermCostBasis: string | number // Div fields dividends: string | number qualifiedDividends: string | number totalCapitalGainsDistributions: string | number personRole?: PersonRole.PRIMARY | PersonRole.SPOUSE // R fields grossDistribution: string | number taxableAmount: string | number federalIncomeTaxWithheld: string | number RPlanType: PlanType1099 // SSA fields // benefitsPaid: string | number // benefitsRepaid: string | number netBenefits: string | number } const blankUserInput: F1099UserInput = { formType: undefined, payer: '', interest: '', // B Fields shortTermProceeds: '', shortTermCostBasis: '', longTermProceeds: '', longTermCostBasis: '', // Div fields dividends: '', qualifiedDividends: '', totalCapitalGainsDistributions: '', // R fields grossDistribution: '', taxableAmount: '', federalIncomeTaxWithheld: '', RPlanType: PlanType1099.Pension, // SSA fields // benefitsPaid: '', // benefitsRepaid: '', netBenefits: '' } const toUserInput = (f: Supported1099): F1099UserInput => ({ ...blankUserInput, formType: f.type, payer: f.payer, personRole: f.personRole, ...(() => { switch (f.type) { case Income1099Type.INT: { return { interest: f.form.income } } case Income1099Type.B: { return f.form } case Income1099Type.DIV: { return f.form } case Income1099Type.R: { return f.form } case Income1099Type.SSA: { return f.form } } })() }) const toF1099 = (input: F1099UserInput): Supported1099 | undefined => { switch (input.formType) { case Income1099Type.INT: { return { payer: input.payer, personRole: input.personRole ?? PersonRole.PRIMARY, type: input.formType, form: { income: Number(input.interest) } } } case Income1099Type.B: { return { payer: input.payer, personRole: input.personRole ?? PersonRole.PRIMARY, type: input.formType, form: { shortTermCostBasis: Number(input.shortTermCostBasis), shortTermProceeds: Number(input.shortTermProceeds), longTermCostBasis: Number(input.longTermCostBasis), longTermProceeds: Number(input.longTermProceeds) } } } case Income1099Type.DIV: { return { payer: input.payer, personRole: input.personRole ?? PersonRole.PRIMARY, type: input.formType, form: { dividends: Number(input.dividends), qualifiedDividends: Number(input.qualifiedDividends), totalCapitalGainsDistributions: Number( input.totalCapitalGainsDistributions ) } } } case Income1099Type.R: { return { payer: input.payer, personRole: input.personRole ?? PersonRole.PRIMARY, type: input.formType, form: { grossDistribution: Number(input.grossDistribution), taxableAmount: Number(input.taxableAmount), federalIncomeTaxWithheld: Number(input.federalIncomeTaxWithheld), planType: PlanType1099.Pension } } } case Income1099Type.SSA: { return { payer: input.payer, personRole: input.personRole ?? PersonRole.PRIMARY, type: input.formType, form: { // benefitsPaid: Number(input.benefitsPaid), // benefitsRepaid: Number(input.benefitsRepaid), netBenefits: Number(input.netBenefits), federalIncomeTaxWithheld: Number(input.federalIncomeTaxWithheld) } } } } } export default function F1099Info(): ReactElement { const f1099s = useSelector((state: TaxesState) => state.information.f1099s) const defaultValues = blankUserInput const methods = useForm({ defaultValues }) const { handleSubmit, watch } = methods const selectedType: Income1099Type | undefined = watch('formType') const dispatch = useDispatch() const { onAdvance, navButtons } = usePager() const onSubmitAdd = (formData: F1099UserInput): void => { const payload = toF1099(formData) if (payload !== undefined) { dispatch(add1099(payload)) } } const onSubmitEdit = (index: number) => (formData: F1099UserInput): void => { const payload = toF1099(formData) if (payload !== undefined) { dispatch(edit1099({ value: payload, index })) } } const people: Person[] = useSelector((state: TaxesState) => [ state.information.taxPayer.primaryPerson, state.information.taxPayer.spouse ]) .filter((p) => p !== undefined) .map((p) => p as Person) const intFields = ( Box 1 - Interest Income } patternConfig={Patterns.currency} name="interest" /> ) const bFields = ( <>

Long Term Covered Transactions

Short Term Covered Transactions

) const divFields = ( ) const rFields = ( Use this form only for 1099-R forms related to your 401(k) or other retirement plans. If you have 1099-R forms from IRA accounts please see the IRA page label="Type of 1099-R" dropDownData={Object.values(PlanType1099)} valueMapping={(x) => x} keyMapping={(_, i) => i} textMapping={(status) => PlanType1099Texts[status]} name="RPlanType" /> ) const ssaFields = ( {/* */} Box 5 - Net Benefits } patternConfig={Patterns.currency} name="netBenefits" /> Box 6 - Voluntary Federal Income Tax Withheld } patternConfig={Patterns.currency} name="federalIncomeTaxWithheld" /> ) const specificFields = { [Income1099Type.INT]: intFields, [Income1099Type.B]: bFields, [Income1099Type.DIV]: divFields, [Income1099Type.R]: rFields, [Income1099Type.SSA]: ssaFields } const titles = { [Income1099Type.INT]: '1099-INT', [Income1099Type.B]: '1099-B', [Income1099Type.DIV]: '1099-DIV', [Income1099Type.R]: '1099-R', [Income1099Type.SSA]: 'SSA-1099' } const form: ReactElement | undefined = ( toUserInput(a))} removeItem={(i) => dispatch(remove1099(i))} primary={(f) => f.payer} secondary={(f) => { const form = toF1099(f) if (form !== undefined) { return showIncome(form) } return '' }} icon={(f) => ( {f.formType} )} >

Input data from 1099

v} name="formType" keyMapping={(_, i: number) => i} textMapping={(name: string) => `1099-${name}`} /> {selectedType !== undefined ? specificFields[selectedType] : undefined} [PersonRole.PRIMARY, PersonRole.SPOUSE][i] } name="personRole" keyMapping={(p: Person, i: number) => i} textMapping={(p: Person) => `${p.firstName} ${p.lastName} (${formatSSID(p.ssid)})` } />
) return (
1099 Information | Income | UsTaxes.org

1099 Information

{form} {navButtons}
) } ================================================ FILE: src/components/income/OtherInvestments.tsx ================================================ import { ReactElement, useState } from 'react' import { Helmet } from 'react-helmet' import { useForm, FormProvider } from 'react-hook-form' import { useDispatch, YearsTaxesState } from 'ustaxes/redux' import { useSelector } from 'react-redux' import { addAsset } from 'ustaxes/redux/actions' import { usePager } from 'ustaxes/components/pager' import { Asset, AssetType, State, TaxYears } from 'ustaxes/core/data' import { GenericLabeledDropdown, USStateDropDown, LabeledInput } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { OpenableFormContainer } from 'ustaxes/components/FormContainer' import { Grid } from '@material-ui/core' import { Alert } from '@material-ui/lab' import { TransactionImporter } from './assets/TransactionImporter' import FilteredAssetsTable from './assets/FilteredAssetsTable' import { DatePicker } from '../input/DatePicker' import { intentionallyFloat } from 'ustaxes/core/util' const showAssetType = (p: AssetType) => { switch (p) { case 'Security': return 'Security (Stock, bond, option, mutual fund, etc.)' case 'Real Estate': return 'Real Estate' } } interface AssetUserInput { name: string positionType: AssetType openDate?: Date closeDate?: Date openPrice: string closePrice?: string openFee: string closeFee: string quantity: string state?: State } const blankAssetUserInput: AssetUserInput = { name: '', positionType: 'Security', openPrice: '', openFee: '', closeFee: '', quantity: '' } const toAsset = (input: AssetUserInput): Asset | undefined => { const { name, openDate, closeDate, openPrice, closePrice, quantity, state, openFee, closeFee, positionType } = input if (name === '' || openDate === undefined) { return undefined } return { positionType, name, openDate, closeDate, openFee: Number(openFee), closeFee: Number(closeFee), openPrice: Number(openPrice), closePrice: Number(closePrice), quantity: input.positionType === 'Real Estate' ? 1 : Number(quantity), state } } export const OtherInvestments = (): ReactElement => { const year = useSelector((state: YearsTaxesState) => state.activeYear) const [isOpen, setOpen] = useState(false) const defaultValues = blankAssetUserInput const methods = useForm({ defaultValues }) const { handleSubmit, watch } = methods const positionType = watch('positionType') const closeDate = watch('closeDate') const dispatch = useDispatch() const { onAdvance, navButtons } = usePager() const onSubmitAdd = (formData: AssetUserInput): void => { const payload = toAsset(formData) if (payload !== undefined) { dispatch(addAsset(payload)) } } const form: ReactElement | undefined = (

Add Assets

label="Asset Type" name="positionType" dropDownData={['Security', 'Real Estate']} keyMapping={(x) => x} textMapping={showAssetType} valueMapping={(x) => x} /> {(() => { if (positionType === 'Real Estate') { return ( <> ) } else { return ( <> ) } })()} {(() => { if ( closeDate !== undefined && closeDate.getFullYear() !== TaxYears[year] ) { return ( This asset will not be included in the current year's return because you have not selected a date in the current year. ) } })()}
) return ( <> Other Investments | Income | UsTaxes.org

Other Investments

{form} {navButtons}
) } export default OtherInvestments ================================================ FILE: src/components/income/PartnershipIncome.tsx ================================================ import { ReactElement, ReactNode } from 'react' import { Helmet } from 'react-helmet' import { useForm, FormProvider } from 'react-hook-form' import { TaxesState, useSelector, useDispatch } from 'ustaxes/redux' import { addScheduleK1Form1065, editScheduleK1Form1065, removeScheduleK1Form1065 } from 'ustaxes/redux/actions' import { usePager } from 'ustaxes/components/pager' import { boxLabel, LabeledInput, GenericLabeledDropdown, formatSSID, LabeledCheckbox, formatEIN } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { FormListContainer } from 'ustaxes/components/FormContainer' import { Grid, Box } from '@material-ui/core' import { Alert } from '@material-ui/lab' import { Business } from '@material-ui/icons' import { ScheduleK1Form1065, FilingStatus, Information, Person, PersonRole, PrimaryPerson, Spouse } from 'ustaxes/core/data' import { intentionallyFloat } from 'ustaxes/core/util' interface ScheduleK1Form1065UserInput { personRole: PersonRole.PRIMARY | PersonRole.SPOUSE partnershipName: string partnershipEin: string partnerOrSCorp: 'P' | 'S' isForeign: boolean isPassive: boolean ordinaryBusinessIncome: string interestIncome: string guaranteedPaymentsForServices: string guaranteedPaymentsForCapital: string selfEmploymentEarningsA: string selfEmploymentEarningsB: string selfEmploymentEarningsC: string distributionsCodeAAmount: string section199AQBI: string } const blankUserInput: ScheduleK1Form1065UserInput = { personRole: PersonRole.PRIMARY, partnershipName: '', partnershipEin: '', partnerOrSCorp: 'P', isForeign: false, isPassive: false, ordinaryBusinessIncome: '', interestIncome: '', guaranteedPaymentsForServices: '', guaranteedPaymentsForCapital: '', selfEmploymentEarningsA: '', selfEmploymentEarningsB: '', selfEmploymentEarningsC: '', distributionsCodeAAmount: '', section199AQBI: '' } const toUserInput = (k1: ScheduleK1Form1065): ScheduleK1Form1065UserInput => ({ ...blankUserInput, personRole: k1.personRole, partnershipName: k1.partnershipName.toString(), partnershipEin: k1.partnershipEin.toString(), partnerOrSCorp: k1.partnerOrSCorp, isForeign: k1.isForeign, isPassive: k1.isPassive, ordinaryBusinessIncome: k1.ordinaryBusinessIncome.toString(), interestIncome: k1.interestIncome.toString(), guaranteedPaymentsForServices: k1.guaranteedPaymentsForServices.toString(), guaranteedPaymentsForCapital: k1.guaranteedPaymentsForCapital.toString(), selfEmploymentEarningsA: k1.selfEmploymentEarningsA.toString(), selfEmploymentEarningsB: k1.selfEmploymentEarningsB.toString(), selfEmploymentEarningsC: k1.selfEmploymentEarningsC.toString(), distributionsCodeAAmount: k1.distributionsCodeAAmount.toString(), section199AQBI: k1.section199AQBI.toString() }) const toScheduleK1Form1065 = ( input: ScheduleK1Form1065UserInput ): ScheduleK1Form1065 | undefined => { const { personRole, partnershipName, partnershipEin, partnerOrSCorp, isForeign, isPassive, ordinaryBusinessIncome, interestIncome, guaranteedPaymentsForServices, guaranteedPaymentsForCapital, selfEmploymentEarningsA, selfEmploymentEarningsB, selfEmploymentEarningsC, distributionsCodeAAmount, section199AQBI } = input if (partnershipName === '') { return undefined } return { personRole: personRole, partnershipName: partnershipName, partnershipEin: partnershipEin, partnerOrSCorp: partnerOrSCorp, isForeign: isForeign, isPassive: isPassive, ordinaryBusinessIncome: Number(ordinaryBusinessIncome), interestIncome: Number(interestIncome), guaranteedPaymentsForServices: Number(guaranteedPaymentsForServices), guaranteedPaymentsForCapital: Number(guaranteedPaymentsForCapital), selfEmploymentEarningsA: Number(selfEmploymentEarningsA), selfEmploymentEarningsB: Number(selfEmploymentEarningsB), selfEmploymentEarningsC: Number(selfEmploymentEarningsC), distributionsCodeAAmount: Number(distributionsCodeAAmount), section199AQBI: Number(section199AQBI) } } export const PartnershipIncome = (): ReactElement => { const information: Information = useSelector( (state: TaxesState) => state.information ) const ScheduleK1Form1065s = information.scheduleK1Form1065s const spouseScheduleK1Form1065s = ScheduleK1Form1065s.filter( (k1) => k1.personRole === PersonRole.SPOUSE ) const spouse: Spouse | undefined = information.taxPayer.spouse const primary: PrimaryPerson | undefined = information.taxPayer.primaryPerson const filingStatus: FilingStatus | undefined = information.taxPayer.filingStatus // People for employee selector const people: Person[] = [primary, spouse].flatMap((p) => p !== undefined ? [p as Person] : [] ) const defaultValues = blankUserInput const methods = useForm({ defaultValues }) const { handleSubmit } = methods const dispatch = useDispatch() const { onAdvance, navButtons } = usePager() const onSubmitAdd = (formData: ScheduleK1Form1065UserInput): void => { const payload = toScheduleK1Form1065(formData) if (payload !== undefined) { dispatch(addScheduleK1Form1065(payload)) } } const onSubmitEdit = (index: number) => (formData: ScheduleK1Form1065UserInput): void => { const payload = toScheduleK1Form1065(formData) if (payload !== undefined) { dispatch(editScheduleK1Form1065({ value: payload, index })) } } const form: ReactElement | undefined = ( defaultValues={defaultValues} onSubmitAdd={onSubmitAdd} onSubmitEdit={onSubmitEdit} items={ScheduleK1Form1065s.map((a) => toUserInput(a))} removeItem={(i) => dispatch(removeScheduleK1Form1065(i))} icon={() => } primary={(k1) => k1.partnershipName} secondary={(k1) => { const scheduleK1Form1065 = toScheduleK1Form1065(k1) if (scheduleK1Form1065 === undefined) return '' return {formatEIN(scheduleK1Form1065.partnershipEin)} }} > {' '}

Partnership Income from Schedule K1 (Form 1065)

t.substring(0, 1)} name="partnerOrSCorp" keyMapping={(k: string, i: number) => i} textMapping={(t) => t} /> [PersonRole.PRIMARY, PersonRole.SPOUSE][i] } name="personRole" keyMapping={(p: Person, i: number) => i} textMapping={(p) => `${p.firstName} ${p.lastName} (${formatSSID(p.ssid)})` } />
) const spouseScheduleK1Form1065Message: ReactNode = (() => { if ( spouse !== undefined && spouseScheduleK1Form1065s.length > 0 && filingStatus === FilingStatus.MFS ) { return (
Filing status is set to Married Filing Separately.{' '} {spouse.firstName} 's ScheduleK1Form1065s will not be added to the return.
) } })() return (
Partnership Income | Income | UsTaxes.org

Partnership Income

If you received Schedule K-1 (Form 1065), enter the information here.

{form} {spouseScheduleK1Form1065Message} {navButtons}
) } export default PartnershipIncome ================================================ FILE: src/components/income/RealEstate.tsx ================================================ import { ReactElement } from 'react' import { Message, useForm, useWatch, FormProvider } from 'react-hook-form' import { useDispatch } from 'ustaxes/redux' import { useYearSelector } from 'ustaxes/redux/yearDispatch' import { useSelector } from 'react-redux' import { Helmet } from 'react-helmet' import { addProperty, editProperty, removeProperty } from 'ustaxes/redux/actions' import { usePager } from 'ustaxes/components/pager' import { Property, Address, PropertyExpenseType, PropertyExpenseTypeName, PropertyType, PropertyTypeName, TaxYear, TaxYears } from 'ustaxes/core/data' import { YearsTaxesState } from 'ustaxes/redux' import AddressFields from 'ustaxes/components/TaxPayer/Address' import { Currency, GenericLabeledDropdown, LabeledCheckbox, LabeledInput } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { daysInYear, enumKeys, intentionallyFloat } from 'ustaxes/core/util' import { HouseOutlined } from '@material-ui/icons' import { FormListContainer } from 'ustaxes/components/FormContainer' import { Grid } from '@material-ui/core' import _ from 'lodash' interface PropertyAddForm { address?: Address rentReceived?: number rentalDays?: number personalUseDays?: number qualifiedJointVenture: boolean propertyType?: PropertyTypeName otherPropertyType?: string expenses: Partial<{ [K in PropertyExpenseTypeName]: number }> otherExpenseType?: string } const blankAddForm: PropertyAddForm = { qualifiedJointVenture: false, expenses: {} } const displayExpense = (k: PropertyExpenseType): string => { const lookup = { [PropertyExpenseType.advertising]: 'Advertising', [PropertyExpenseType.auto]: 'Auto and travel', [PropertyExpenseType.cleaning]: 'Cleaning and maintenance', [PropertyExpenseType.commissions]: 'Commissions', [PropertyExpenseType.insurance]: 'Insurance', [PropertyExpenseType.legal]: 'Legal and other professional fees', [PropertyExpenseType.management]: 'Management fees', [PropertyExpenseType.mortgage]: 'Mortgage interest paid to banks, etc', [PropertyExpenseType.otherInterest]: 'Other interest', [PropertyExpenseType.repairs]: 'Repairs', [PropertyExpenseType.supplies]: 'Supplies', [PropertyExpenseType.taxes]: 'Taxes', [PropertyExpenseType.utilities]: 'Utilities', [PropertyExpenseType.depreciation]: 'Depreciation expense or depletion', [PropertyExpenseType.other]: 'Other' } return lookup[k] } const displayPropertyType = (k: PropertyType): string => { const lookup = { [PropertyType.singleFamily]: 'Single family', [PropertyType.multiFamily]: 'Multifamily', [PropertyType.vacation]: 'Vacation', [PropertyType.commercial]: 'Commercial', [PropertyType.land]: 'Land', [PropertyType.selfRental]: 'Self rental', [PropertyType.other]: 'Other' } return lookup[k] } const toProperty = (formData: PropertyAddForm): Property => { const { address, rentReceived, rentalDays, qualifiedJointVenture, propertyType, otherPropertyType, personalUseDays, expenses, otherExpenseType } = formData if (address === undefined || propertyType === undefined) { throw new Error('Validation failed') } const newExpenses: Partial<{ [K in PropertyExpenseTypeName]: number }> = Object.fromEntries( enumKeys(PropertyExpenseType) .filter((e) => e in expenses && (expenses[e] as number) > 0) .map((e) => [e, Number(expenses[e])]) ) return { address, rentalDays: Number(rentalDays), qualifiedJointVenture, rentReceived: Number(rentReceived), personalUseDays: Number(personalUseDays), propertyType, otherPropertyType, expenses: newExpenses, otherExpenseType } } const toUserInput = (property: Property): PropertyAddForm => { return { ...blankAddForm, address: property.address, rentReceived: property.rentReceived, rentalDays: property.rentalDays, personalUseDays: property.personalUseDays, qualifiedJointVenture: property.qualifiedJointVenture, propertyType: property.propertyType, otherPropertyType: property.otherPropertyType, expenses: property.expenses, otherExpenseType: property.otherExpenseType } } export default function RealEstate(): ReactElement { const defaultValues = blankAddForm const methods = useForm({ defaultValues }) const { handleSubmit, control, getValues } = methods const dispatch = useDispatch() const { onAdvance, navButtons } = usePager() const activeYear: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) const properties: Property[] = useYearSelector( (state) => state.information.realEstate ) const propertyType = useWatch({ control, name: 'propertyType' }) const otherExpensesEntered: number | undefined = useWatch({ control, name: 'expenses.other' }) const validateDays = (n: number, other: number): Message | true => { const days = daysInYear(TaxYears[activeYear]) return n + other <= days ? true : `Total use days must be less than ${days}` } const validatePersonal = (n: number): Message | true => validateDays(n, Number(getValues().rentalDays ?? 0)) const validateRental = (n: number): Message | true => validateDays(n, Number(getValues().personalUseDays ?? 0)) const deleteProperty = (n: number): void => { dispatch(removeProperty(n)) } const onAddProperty = (formData: PropertyAddForm): void => { dispatch(addProperty(toProperty(formData))) } const onEditProperty = (index: number) => (formData: PropertyAddForm): void => { dispatch(editProperty({ value: toProperty(formData), index })) } const expenseFields: ReactElement[] = enumKeys(PropertyExpenseType).map( (k, i) => ( ) ) const otherExpenseDescription = (() => { if ((otherExpensesEntered ?? 0) !== 0) { return ( ) } })() const form = ( toUserInput(a))} icon={() => } primary={(p) => toProperty(p).address.address} secondary={(p) => } onSubmitAdd={onAddProperty} onSubmitEdit={onEditProperty} removeItem={(i) => deleteProperty(i)} >

Property Location

displayPropertyType(PropertyType[t])} keyMapping={(_, n) => n} name="propertyType" valueMapping={(n) => n} /> {(() => { if (propertyType === 'other') { return ( ) } })()}

Use

validateRental(Number(n)) }} label="Number of days in the year used for rental" patternConfig={Patterns.numDays(activeYear)} /> validatePersonal(Number(n)) }} label="Number of days in the year for personal use" patternConfig={Patterns.numDays(activeYear)} />

Property Financials

Income

Expenses

{_.chain([...expenseFields, otherExpenseDescription]) .chunk(2) .map((segment, i) => segment.map((item, k) => ( {item} )) ) .value()}
) return (
Real Estate | Income | UsTaxes.org

Properties

{form} {navButtons}
) } ================================================ FILE: src/components/income/StockOptions.tsx ================================================ import { ReactElement, ReactNode } from 'react' import { Helmet } from 'react-helmet' import { useForm, FormProvider } from 'react-hook-form' import { TaxesState, useSelector, useDispatch } from 'ustaxes/redux' import { addF3921, editF3921, removeF3921 } from 'ustaxes/redux/actions' import { usePager } from 'ustaxes/components/pager' import { LabeledInput, GenericLabeledDropdown, formatSSID } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { FormListContainer } from 'ustaxes/components/FormContainer' import { Currency } from 'ustaxes/components/input' import { Grid, Box } from '@material-ui/core' import { Alert } from '@material-ui/lab' import { ShowChartOutlined as StockIcon } from '@material-ui/icons' import { F3921, FilingStatus, Information, Person, PersonRole, PrimaryPerson, Spouse } from 'ustaxes/core/data' import { intentionallyFloat } from 'ustaxes/core/util' interface F3921UserInput { name: string personRole: PersonRole.PRIMARY | PersonRole.SPOUSE exercisePricePerShare?: string fmv?: string numShares?: string } const blankUserInput: F3921UserInput = { name: '', personRole: PersonRole.PRIMARY, exercisePricePerShare: '', fmv: '', numShares: '' } const toUserInput = (f: F3921): F3921UserInput => ({ ...blankUserInput, name: f.name, personRole: f.personRole, exercisePricePerShare: f.exercisePricePerShare.toString(), fmv: f.fmv.toString(), numShares: f.numShares.toString() }) const toF3921 = (input: F3921UserInput): F3921 | undefined => { const { name, personRole, exercisePricePerShare, fmv, numShares } = input if (name === '') { return undefined } return { name, personRole: personRole, exercisePricePerShare: Number(exercisePricePerShare), fmv: Number(fmv), numShares: Number(numShares) } } export const StockOptions = (): ReactElement => { const defaultValues = blankUserInput const information: Information = useSelector( (state: TaxesState) => state.information ) const f3921s = information.f3921s const spouseF3921s = f3921s.filter( (f3921) => f3921.personRole === PersonRole.SPOUSE ) const spouse: Spouse | undefined = information.taxPayer.spouse const primary: PrimaryPerson | undefined = information.taxPayer.primaryPerson const filingStatus: FilingStatus | undefined = information.taxPayer.filingStatus // People for employee selector const people: Person[] = [primary, spouse].flatMap((p) => p !== undefined ? [p as Person] : [] ) const methods = useForm({ defaultValues }) const { handleSubmit } = methods const dispatch = useDispatch() const { onAdvance, navButtons } = usePager() const onSubmitAdd = (formData: F3921UserInput): void => { const payload = toF3921(formData) if (payload !== undefined) { dispatch(addF3921(payload)) } } const onSubmitEdit = (index: number) => (formData: F3921UserInput): void => { const payload = toF3921(formData) if (payload !== undefined) { dispatch(editF3921({ value: payload, index })) } } const form: ReactElement | undefined = ( defaultValues={defaultValues} onSubmitAdd={onSubmitAdd} onSubmitEdit={onSubmitEdit} items={f3921s.map((a) => toUserInput(a))} removeItem={(i) => dispatch(removeF3921(i))} icon={() => } primary={(f) => f.name} secondary={(f) => { const f3921 = toF3921(f) if (f3921 === undefined) return '' return ( {f3921.numShares} shares @{' '} ;{' '} FMV ) }} > {' '}

Manage Stock Options

[PersonRole.PRIMARY, PersonRole.SPOUSE][i] } name="personRole" keyMapping={(p: Person, i: number) => i} textMapping={(p) => `${p.firstName} ${p.lastName} (${formatSSID(p.ssid)})` } />
) const spouseF3921Message: ReactNode = (() => { if ( spouse !== undefined && spouseF3921s.length > 0 && filingStatus === FilingStatus.MFS ) { return (
Filing status is set to Married Filing Separately.{' '} {spouse.firstName} 's F3921s will not be added to the return.
) } })() return (
Stock Options | Income | UsTaxes.org

Stock Options

If you received Form 3921, enter the information here.

{form} {spouseF3921Message} {navButtons}
) } export default StockOptions ================================================ FILE: src/components/income/W2JobInfo.tsx ================================================ import { Fragment, ReactElement, ReactNode, useState } from 'react' import _ from 'lodash' import { useDispatch, useSelector, TaxesState } from 'ustaxes/redux' import { Helmet } from 'react-helmet' import { FormProvider, useForm, useFormContext } from 'react-hook-form' import { usePager } from 'ustaxes/components/pager' import { IncomeW2, Person, PersonRole, Employer, Spouse, PrimaryPerson, FilingStatus, Information, State, W2Box12Info, W2Box12Code, W2Box12CodeDescriptions } from 'ustaxes/core/data' import { boxLabel, Currency, formatSSID, GenericLabeledDropdown, LabeledInput, USStateDropDown } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { FormListContainer } from 'ustaxes/components/FormContainer' import { Grid, Box, Button, Paper } from '@material-ui/core' import { Work } from '@material-ui/icons' import { addW2, editW2, removeW2 } from 'ustaxes/redux/actions' import { Alert } from '@material-ui/lab' import { enumKeys, parseFormNumber, parseFormNumberOrThrow } from 'ustaxes/core/util' interface IncomeW2UserInput { employer?: Employer occupation: string income: string medicareIncome: string fedWithholding: string ssWages: string ssWithholding: string medicareWithholding: string personRole?: PersonRole.PRIMARY | PersonRole.SPOUSE state?: State stateWages: string stateWithholding: string box12: W2Box12Info } const blankW2UserInput: IncomeW2UserInput = { employer: { EIN: '' }, occupation: '', income: '', medicareIncome: '', fedWithholding: '', ssWages: '', ssWithholding: '', medicareWithholding: '', stateWages: '', stateWithholding: '', box12: {} } const toIncomeW2 = (formData: IncomeW2UserInput): IncomeW2 => ({ ...formData, // Note we are not error checking here because // we are already in the input validated happy path // of handleSubmit. income: parseFormNumberOrThrow(formData.income), medicareIncome: parseFormNumberOrThrow(formData.medicareIncome), fedWithholding: parseFormNumberOrThrow(formData.fedWithholding), ssWages: parseFormNumberOrThrow(formData.ssWages), ssWithholding: parseFormNumberOrThrow(formData.ssWithholding), medicareWithholding: parseFormNumberOrThrow(formData.medicareWithholding), state: formData.state, stateWages: parseFormNumberOrThrow(formData.stateWages), stateWithholding: parseFormNumberOrThrow(formData.stateWithholding), personRole: formData.personRole ?? PersonRole.PRIMARY, box12: _.mapValues(formData.box12, (v) => parseFormNumber(v)) }) const toIncomeW2UserInput = (data: IncomeW2): IncomeW2UserInput => ({ ...blankW2UserInput, ...data, income: data.income.toString(), medicareIncome: data.medicareIncome.toString(), fedWithholding: data.fedWithholding.toString(), ssWages: data.ssWages.toString(), ssWithholding: data.ssWithholding.toString(), medicareWithholding: data.medicareWithholding.toString(), state: data.state, stateWages: data.stateWages?.toString() ?? '', stateWithholding: data.stateWithholding?.toString() ?? '', box12: _.mapValues(data.box12, (v) => v?.toString()) }) const Box12Data = (): ReactElement => { const [editBox12, setEditBox12] = useState(false) const { getValues } = useFormContext() const { box12 } = getValues() const box12Fields = ( <> {enumKeys(W2Box12Code).map((code) => (

Code {code}: {W2Box12CodeDescriptions[code]}

))} ) const openCloseButton = ( ) const box12Data = (
    {enumKeys(W2Box12Code) .filter((code) => box12[code] !== undefined) .map((code) => (
  • {code}: {' '} ({W2Box12CodeDescriptions[code]})
  • ))}
) return (

Box 12 Information

{editBox12 ? box12Fields : box12Data} {openCloseButton}
) } export default function W2JobInfo(): ReactElement { const dispatch = useDispatch() const defaultValues = blankW2UserInput const methods = useForm({ defaultValues }) const { navButtons, onAdvance } = usePager() const information: Information = useSelector( (state: TaxesState) => state.information ) const spouse: Spouse | undefined = information.taxPayer.spouse const primary: PrimaryPerson | undefined = information.taxPayer.primaryPerson const filingStatus: FilingStatus | undefined = information.taxPayer.filingStatus // People for employee selector const people: Person[] = [primary, spouse].flatMap((p) => p !== undefined ? [p as Person] : [] ) const w2s: IncomeW2[] = information.w2s const spouseW2s = w2s.filter((w2) => w2.personRole === PersonRole.SPOUSE) const onSubmitAdd = (formData: IncomeW2UserInput): void => { dispatch(addW2(toIncomeW2(formData))) } const onSubmitEdit = (index: number) => (formData: IncomeW2UserInput): void => { dispatch(editW2({ index, value: toIncomeW2(formData) })) } const w2sBlock = ( defaultValues={defaultValues} items={w2s.map((a) => toIncomeW2UserInput(a))} onSubmitAdd={onSubmitAdd} onSubmitEdit={onSubmitEdit} removeItem={(i) => dispatch(removeW2(i))} icon={() => } primary={(w2: IncomeW2UserInput) => w2.employer?.employerName ?? w2.occupation } secondary={(w2: IncomeW2UserInput) => ( Income: )} grouping={(w2) => (w2.personRole === PersonRole.PRIMARY ? 0 : 1)} groupHeaders={[primary?.firstName, spouse?.firstName].map((x) => x !== undefined ?

{x}' W2s

: undefined )} >

Input data from W-2

[PersonRole.PRIMARY, PersonRole.SPOUSE][i] } name="personRole" keyMapping={(p: Person, i: number) => i} textMapping={(p) => `${p.firstName} ${p.lastName} (${formatSSID(p.ssid)})` } /> ) const spouseW2Message: ReactNode = (() => { if ( spouse !== undefined && spouseW2s.length > 0 && filingStatus === FilingStatus.MFS ) { return (
Filing status is set to Married Filing Separately.{' '} {spouse.firstName} 's W2s will not be added to the return.
) } })() const form: ReactElement = ( <> {w2sBlock} {spouseW2Message} ) return (
Job Information | Income | UsTaxes.org

Job Information

{form} {navButtons}
) } ================================================ FILE: src/components/income/assets/AssetSummary.tsx ================================================ import { useMediaQuery } from '@material-ui/core' import { ReactElement } from 'react' import DataTable, { TableColumn } from 'react-data-table-component' import { Currency } from 'ustaxes/components/input' import { Asset } from 'ustaxes/core/data' import { numberOfDaysBetween } from 'ustaxes/core/util' interface AssetSummaryProps { title: string assets: Asset[] } type Row = { name: string shortTerm: number longTerm: number } const AssetSummary = ({ title, assets }: AssetSummaryProps): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const totals = assets.reduce( (acc, a) => { const isLongTerm = numberOfDaysBetween( a.openDate, a.closeDate !== undefined ? a.closeDate : new Date() ) > 365 return { ...acc, longTermCostBasis: isLongTerm && a.closePrice !== undefined ? acc.longTermCostBasis + a.openPrice * a.quantity + a.openFee : acc.longTermCostBasis, shortTermCostBasis: !isLongTerm && a.closePrice !== undefined ? acc.shortTermCostBasis + a.openPrice * a.quantity + a.openFee : acc.shortTermCostBasis, longTermProceeds: isLongTerm && a.closePrice !== undefined ? acc.longTermProceeds + a.closePrice * a.quantity - (a.closeFee ?? 0) : acc.longTermProceeds, shortTermProceeds: !isLongTerm && a.closePrice !== undefined ? acc.shortTermProceeds + a.closePrice * a.quantity - (a.closeFee ?? 0) : acc.shortTermProceeds, openCostBasis: a.closeDate === undefined ? acc.openCostBasis + a.openPrice * a.quantity + a.openFee : acc.openCostBasis } }, { longTermCostBasis: 0, longTermProceeds: 0, shortTermCostBasis: 0, shortTermProceeds: 0, openCostBasis: 0 } ) const columns: TableColumn[] = [ { name: '', selector: ({ name }) => name }, { name: 'Short Term', cell: ({ shortTerm }) => }, { name: 'Long Term', cell: ({ longTerm }) => }, { name: 'Total', cell: ({ shortTerm, longTerm }) => ( ) } ] const data: Array<{ name: string; shortTerm: number; longTerm: number }> = [ { name: 'Cost Basis', shortTerm: totals.shortTermCostBasis, longTerm: totals.longTermCostBasis }, { name: 'Proceeds', shortTerm: totals.shortTermProceeds, longTerm: totals.longTermProceeds }, { name: 'Gain/Loss', shortTerm: totals.shortTermProceeds - totals.shortTermCostBasis, longTerm: totals.longTermProceeds - totals.longTermCostBasis } ] const totalFees = assets.reduce( (acc, a) => acc + a.openFee + (a.closeFee ?? 0), 0 ) return ( <> {(() => { if (totalFees > 0) { return (

Cost basis and proceeds include{' '} in fees.

) } })()} ) } export default AssetSummary ================================================ FILE: src/components/income/assets/ConfigurableDataTable.tsx ================================================ import { Grid, TextField, useMediaQuery } from '@material-ui/core' import { Alert } from '@material-ui/lab' import { ReactElement } from 'react' import DataTable, { ConditionalStyles, TableColumn } from 'react-data-table-component' import useStyles from '../../input/styles' import { columnInputStyles, useRowStyles } from './DataTableStyle' interface ColumnHeaderDropProps { fields: string[] value?: string onChange: (field: string) => void undefinedName?: string } const ColumnHeaderDropDown = ({ fields, value, onChange, undefinedName = '' }: ColumnHeaderDropProps) => { const columnClasses = columnInputStyles() return ( onChange(e.target.value as string) }} InputLabelProps={{ shrink: true }} > {fields.map((field) => ( ))} ) } export interface ColumnDef { name: string required?: boolean } interface ConfigurableDataTableProps { fields: ColumnDef[] fieldAssignments: (string | undefined)[] rows: string[][] assignField: (colIndex: number, field: string | undefined) => void dropFirstNRows?: number updateDropFirstNRows: (dropFirstNRows: number) => void } export const ConfigurableDataTable = ({ fieldAssignments, fields, assignField, rows, dropFirstNRows = 0, updateDropFirstNRows }: ConfigurableDataTableProps): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const firstRow = rows[0] const classes = useStyles() const rowClasses = useRowStyles({ prefersDarkMode }) const conditionalCellStyles: ConditionalStyles<[number, string[]]>[] = [ { when: ([index]) => index < dropFirstNRows, classNames: [rowClasses.disabledRow] }, { when: ([index]) => index >= dropFirstNRows, classNames: [rowClasses.normal] } ] const columns: TableColumn<[number, string[]]>[] = firstRow.map((c, i) => ({ name: ( f.name)} onChange={(field) => assignField(i, field)} value={fieldAssignments[i]} undefinedName={`Col ${i + 1}`} /> ), selector: ([, row]) => row[i], conditionalCellStyles })) const unassignedColumns = fields.filter( (c) => c.required && !fieldAssignments.includes(c.name) ) const errorColumns = fields.filter( (c) => c.required && fieldAssignments.filter((f) => f === c.name).length > 1 ) const assignAlert = (() => { if (unassignedColumns.length > 0) { return ( Assign the following fields:
    {unassignedColumns.map((c) => (
  • {c.name}
  • ))}
) } })() const errorAlert = (() => { if (errorColumns.length > 0) { return ( {errorColumns.map((c) => c.name).join(', ') + (errorColumns.length > 1 ? ' fields are' : ' field is')}{' '} assigned more than once. ) } })() const dropFirstNRowsInput = ( { const newv = parseInt(v.target.value) updateDropFirstNRows(isNaN(newv) ? 0 : newv) }} /> ) return ( {assignAlert} {errorAlert} {dropFirstNRowsInput} data={rows.map((r, i) => [i, r])} columns={columns} theme={prefersDarkMode ? 'dark' : 'normal'} /> ) } export default ConfigurableDataTable ================================================ FILE: src/components/income/assets/DataTableStyle.ts ================================================ import { createStyles, makeStyles, Theme } from '@material-ui/core' import { createTheme } from 'react-data-table-component' type DarkModeProps = { prefersDarkMode: boolean } createTheme('normal', { backgroundColor: 'white', color: 'rgba(0, 0, 0, 0.54)', '& .MuiFilledInput-input': { fontSize: '.8rem', fontWeight: 'bold', padding: '0.9rem 0rem' } }) createTheme( 'dark', { backgroundColor: '#303030', color: 'white', '& .MuiFilledInput-input': { fontSize: '.8rem', fontWeight: 'bold', padding: '0.9rem 0rem' } }, 'normal' ) export const baseCellStyle = ( prefersDarkMode = false ): { [k: string]: string } => ({ color: prefersDarkMode ? 'white' : 'rgba(0, 0, 0, 0.54)', backgroundColor: prefersDarkMode ? '#303030' : 'white' }) export const useRowStyles = makeStyles(() => createStyles({ disabledRow: { backgroundColor: '#aaaaaa', color: 'black' }, normal: ({ prefersDarkMode }) => baseCellStyle(prefersDarkMode) }) ) export const columnInputStyles = makeStyles(() => createStyles({ column: { '& .MuiFilledInput-input': { fontSize: '.8rem', fontWeight: 'bold', padding: '0.9rem 0rem' } } }) ) ================================================ FILE: src/components/income/assets/FilteredAssetsTable.tsx ================================================ import { Button, Grid, useMediaQuery } from '@material-ui/core' import { ReactElement, useMemo, useState } from 'react' import DataTable, { TableColumn } from 'react-data-table-component' import { FormProvider, useForm } from 'react-hook-form' import { useDispatch, useSelector } from 'react-redux' import { Currency, GenericLabeledDropdown, LabeledInput } from 'ustaxes/components/input' import { Asset, AssetType, TaxYear, TaxYears } from 'ustaxes/core/data' import { enumKeys } from 'ustaxes/core/util' import { YearsTaxesState } from 'ustaxes/redux' import * as actions from 'ustaxes/redux/actions' import AssetSummary from './AssetSummary' type CloseYear = TaxYear | 'none' | 'all' interface AssetFilter { securityName: string positionType: AssetType closeYear: string | undefined } const blankFilter: AssetFilter = { securityName: '', positionType: 'Security', closeYear: 'none' } type Row = WithIndex> const assetTableColumns: TableColumn[] = [ { name: 'Security', selector: ({ name }) => name, sortable: true }, { name: 'Open Date', selector: ({ openDate }) => openDate.toISOString().slice(0, 10), sortable: true }, { name: 'Sale Date', selector: ({ closeDate }) => closeDate?.toISOString().slice(0, 10) ?? '', sortable: true }, { name: 'Cost basis', sortFunction: (a, b) => a.openPrice * a.quantity - b.openPrice * b.quantity, cell: ({ openPrice, quantity }) => ( ), sortable: true }, { name: 'Proceeds', sortFunction: (a, b) => (a.closePrice ?? 0) * a.quantity - (b.closePrice ?? 0) * b.quantity, cell: ({ closePrice, quantity }) => closePrice !== undefined ? ( ) : ( '' ), sortable: true } ] type SelectionEvent = { allSelected: boolean selectedCount: number selectedRows: T[] } type WithIndex
= { idx: number } & A interface DisplayAssetsProps { assets: WithIndex>[] deleteRows: (rows: number[]) => void } const DisplayAssets = ({ assets, deleteRows }: DisplayAssetsProps): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const [selectedRows, setSelectedRows] = useState([]) const [cleared, setCleared] = useState(false) const handleRowSelected = (event: SelectionEvent>>) => { setSelectedRows(event.selectedRows.map(({ idx }) => idx)) } const contextActions = useMemo(() => { const handleDelete = () => { const promptResult = window.confirm( `Are you sure you want to delete ${selectedRows.length} positions?` ) if (promptResult) { setCleared(!cleared) deleteRows(selectedRows) } } return ( ) }, [selectedRows, cleared]) return ( ) } const FilteredAssetsTable = (): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const activeYear: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) const dispatch = useDispatch() const assets = useSelector((state: YearsTaxesState) => state.assets) const allAssets: WithIndex>[] = assets.map((a, i) => ({ ...a, idx: i })) const methods = useForm({ defaultValues: { ...blankFilter, closeYear: activeYear } }) const closeYear = methods.watch('closeYear') const securityName = methods.watch('securityName') const yearFilter = (a: Asset): boolean => closeYear === 'all' || (a.closeDate === undefined && closeYear === 'none') || (a.closeDate !== undefined && closeYear !== 'none' && a.closeDate.getFullYear() === TaxYears[closeYear as TaxYear]) const securityFilter = (a: Asset): boolean => securityName === '' || a.name.toLowerCase() === securityName.toLowerCase() const displayAssets = allAssets.filter( (a) => yearFilter(a) && securityFilter(a) ) const title = [ ['Summary of'], securityName !== '' ? [securityName] : [], closeYear === 'all' ? ['all time'] : closeYear === 'none' ? ['current holdings'] : [`sales in ${TaxYears[closeYear as TaxYear]}`] ] .flat() .join(' ') const filterForm = (() => { // Show filter if there are any assets to filter from if (assets.length > 0) { return (

Filter by

noUndefined sizes={{ xs: 6 }} label="Sale Year" name="closeYear" dropDownData={[ ['All', 'all'], ['Still open', 'none'], ...enumKeys(TaxYears).map<[string, TaxYear]>((x) => [ TaxYears[x].toString(), x ]) ]} keyMapping={(x) => x[1].toString()} valueMapping={(x) => x[1]} textMapping={(x) => x[0]} />
) } })() const assetSummary = (() => { if (assets.length > 0) { return } })() const asCsv = (): string[] => [ [ 'Security', 'Quantity', 'Open Date', 'Open Price', 'Open Fee', 'Cost basis', 'Close Date', 'Close Price', 'Close Fee', 'Proceeds', 'Gain / Loss' ], ...displayAssets.map((a) => [ a.name, a.quantity, a.openDate.toISOString().slice(0, 10), a.openPrice, a.openFee, a.openPrice * a.quantity + a.openFee, a.closeDate?.toISOString().slice(0, 10) ?? '', a.closePrice, a.closeFee, a.closePrice === undefined ? '' : a.closePrice * a.quantity - (a.closeFee ?? 0), a.closePrice === undefined ? '' : (a.closePrice - a.openPrice) * a.quantity - (a.closeFee ?? 0) - a.openFee ]) ].map((line) => line.join(',')) const exportView = (() => { if (displayAssets.length > 0) { return ( ) } })() return ( {filterForm} {assetSummary} {exportView} dispatch(actions.removeAssets(rows)(activeYear)) } /> ) } export default FilteredAssetsTable ================================================ FILE: src/components/income/assets/TransactionImporter.tsx ================================================ import { ReactElement, useState } from 'react' import { Button, Grid, useMediaQuery } from '@material-ui/core' import { useDispatch } from 'ustaxes/redux' import * as actions from 'ustaxes/redux/actions' import { preflightCsv, preflightCsvAll } from 'ustaxes/data/csvImport' import { LoadRaw } from 'ustaxes/redux/fs/Load' import DataTable, { TableColumn } from 'react-data-table-component' import { Alert } from '@material-ui/lab' import { Portfolio, Position, processTransactions, Side, Transaction, TransactionError } from 'ustaxes/data/transactions' import { Either, EitherMethods, isLeft, left, pure, pureLeft, right, run } from 'ustaxes/core/util' import { Asset } from 'ustaxes/core/data' import ConfigurableDataTable, { ColumnDef } from './ConfigurableDataTable' interface PortfolioTableProps { portfolio: Portfolio } export const PortfolioTable = ({ portfolio }: PortfolioTableProps): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const columns: TableColumn[] = [ { name: 'Asset Name', selector: (p) => p.security.name }, { name: 'Open Date', selector: (p) => p.openDate }, { name: 'Quantity', selector: (p) => p.quantity }, { name: 'Price per unit', selector: (p) => p.price }, { name: 'Basis', selector: (p) => p.price * p.quantity }, { name: 'Close Date', selector: (p) => p.closeDate ?? '' }, { name: 'Close Fee', selector: (p) => (p.closeDate === undefined ? '' : p.closeFee ?? 0) }, { name: 'Proceeds', selector: (p) => p.closePrice !== undefined ? p.quantity * p.closePrice : '' }, { name: 'Gain or Loss', selector: (p) => p.closePrice !== undefined ? (p.closePrice - p.price) * p.quantity : '' } ] return ( ) } const field = (name: string, required = true): ColumnDef => ({ name, required }) // The fields that must be set by the user after importing a CSV file const fields: ColumnDef[] = [ field('Asset Name'), field('Transaction date'), field('Buy or Sell'), field('Price per unit'), field('Quantity'), field('Fee / commissions', false) ] export const TransactionImporter = (): ReactElement => { const [preflightTransactions, setPreflightTransactions] = useState< string[][] >([]) // FieldAssignements will be an array of column indices in the imported CSV // to the string name of the field we want to assign to that column. const [fieldAssignments, setFieldAssignments] = useState< (string | undefined)[] >([]) const [dropFirstNRows, setDropFirstNRows] = useState(0) const [rawContents, setRawContents] = useState('') const [portfolio, setPortfolio] = useState({ positions: [] }) const [portfolioError, setPortfolioError] = useState< TransactionError | undefined >(undefined) const dispatch = useDispatch() const ready = () => fields.every( (f) => !f.required || fieldAssignments.filter((a) => a === f.name).length === 1 ) const assignField = (colIndex: number, field: string | undefined) => { const newFieldAssignments = [...fieldAssignments] while (newFieldAssignments.length <= colIndex) { newFieldAssignments.push(undefined) } newFieldAssignments[colIndex] = field setFieldAssignments(newFieldAssignments) } const onHandle = (contents: string): void => { setRawContents(contents) run(preflightCsv(contents)).fold( (e) => console.error("Couldn't parse CSV", e), setPreflightTransactions ) } const parseRow = (row: string[]): Either => { const assignments = fieldAssignments as string[] const date: Either = (() => { const dateStr = row[assignments.indexOf('Transaction date')] try { return right(new Date(dateStr.slice(0, 10))) } catch (e) { return left( `Parsing transaction date value (${ row[assignments.indexOf('Transaction date')] }) as date failed` ) } })() const quantity: Either = (() => { const quantityStr = row[assignments.indexOf('Quantity')] const v = parseFloat(quantityStr) if (isNaN(v)) { return left(`Could not parse quantity value (${quantityStr}) as number`) } else { return right(v) } })() const price: Either = (() => { const priceStr = row[assignments.indexOf('Price per unit')] const v = parseFloat(priceStr) if (isNaN(v)) { return left(`Could not parse price value (${priceStr}) as number`) } else { return right(v) } })() const side: Either = (() => { const cell = row[assignments.indexOf('Buy or Sell')].toLowerCase() if (cell === 'buy') { return right('BUY') } if (cell === 'sell') { return right('SELL') } return left(`Could not parse value ${cell} as buy or sell`) })() const feeIdx = assignments.indexOf('Fee / commissions') const fee: Either = (() => { if (feeIdx < 0) { return right(undefined) } const feeStr = row[feeIdx] const v = parseFloat(feeStr) if (isNaN(v)) { return left(`Could not parse fee value (${feeStr}) as number`) } else { return right(v) } })() // Either is a fail-fast construct, so // we dont (yet) have a way to nicely combine // errors. const errors = [] if (isLeft(date)) { errors.push(date.left) } if (isLeft(quantity)) { errors.push(quantity.left) } if (isLeft(price)) { errors.push(price.left) } if (isLeft(side)) { errors.push(side.left) } // bad if condition necessary for typechecking below. if ( isLeft(date) || isLeft(quantity) || isLeft(price) || isLeft(side) || isLeft(fee) ) { return left(errors) } else { return right({ security: { name: row[assignments.indexOf('Asset Name')] }, date: date.right.toISOString().slice(0, 10), quantity: quantity.right, price: price.right, side: side.right, fee: fee.right }) } } const addToPortfolio = () => run(preflightCsvAll(rawContents)).fold( (csvReadErrors) => { setPortfolioError({ messages: ['Could not parse CSV', csvReadErrors[0].message], errorIndex: csvReadErrors[0].row }) }, (rows) => { const accumulatedErrors = rows.reduce((acc, row, idx) => { // Skip configured header rows if (idx < dropFirstNRows) { return acc } return run(parseRow(row)).fold( (e) => acc.fold( (errors) => pureLeft(errors.concat({ errorIndex: idx, messages: e })), () => pureLeft([ { errorIndex: idx, messages: e } ]) ), (res) => acc.map((ts) => ts.concat(res)) ) }, pure([])) const processedTransactions: EitherMethods< TransactionError[], Portfolio > = accumulatedErrors.chain((transactions) => run(processTransactions(portfolio, transactions)) .mapLeft((e) => [ { ...e, messages: [ 'This usually means you have sell transactions in excess of securities you hold at the time of sale. Either these are short sales, which are not yet supported here, or buy transactions that predate these sales are missing. If this is the case, add the required positions as BUY transactions at the beginning of this CSV file and try again.' ] } ]) .value() ) processedTransactions.fold( (errors) => { setPortfolioError(errors[0]) }, (portfolio) => { setPortfolioError(undefined) setPortfolio(portfolio) } ) } ) const readyButton = (() => { if (ready() && portfolio.positions.length === 0) { return ( ) } })() const importable = () => portfolio.positions.length > 0 && portfolioError === undefined const resetPortfolioBuildingState = () => { setPortfolio({ positions: [] }) setRawContents('') setDropFirstNRows(0) setFieldAssignments([]) setPreflightTransactions([]) setPortfolioError(undefined) } const addAssets = () => { const assets: Asset[] = portfolio.positions.map((position) => ({ name: position.security.name, openDate: new Date(position.openDate), openPrice: position.price, positionType: 'Security', quantity: position.quantity, openFee: position.openFee, closeFee: position.closeFee, closeDate: position.closeDate !== undefined ? new Date(position.closeDate) : undefined, closePrice: position.closePrice })) dispatch(actions.addAssets(assets)) resetPortfolioBuildingState() } const addAssetsButton = (() => { if (importable()) { return ( ) } })() const resetButton = (() => { if ( preflightTransactions.length > 0 || portfolioError !== undefined || portfolio.positions.length > 0 ) { return ( ) } })() return ( onHandle(contents)} > Load CSV File {(() => { if (preflightTransactions.length > 0) { return ( <>

Import Transactions

setDropFirstNRows(num)} dropFirstNRows={dropFirstNRows} /> {readyButton} {resetButton} {addAssetsButton} ) } })()} {(() => { if (portfolioError) { const { errorTransaction, previousPortfolio } = portfolioError const errorTransactionMessage = (() => { if (errorTransaction !== undefined) { const { side, security: { name }, quantity, price, date } = errorTransaction return ( <>

Erroneous transaction

  • Line {portfolioError.errorIndex + 1}
  • Date: {date}
  • {`${side} ${name} ${quantity}@${price}`}
) } })() const prevPortfolioMessage = (() => { if (previousPortfolio !== undefined) { return ( <>

Portfolio before erroneous transaction

) } })() return (

Transaction List Import Failed

{errorTransactionMessage} {portfolioError.messages.map((message, i) => (

{message}

))}
{prevPortfolioMessage}
) } })()} {(() => { if (portfolio.positions.length > 0) { return (

Portfolio

) } })()}
) } ================================================ FILE: src/components/input/Currency.tsx ================================================ import { ReactElement } from 'react' import { CurrencyProps } from './types' import NumberFormat from 'react-number-format' import { makeStyles } from '@material-ui/core/styles' const useStyles = makeStyles(() => ({ positive: { color: 'green' }, negative: { color: 'red' } })) export default function Currency(props: CurrencyProps): ReactElement { const { prefix = '', value, plain = false } = props const classes = useStyles() const className: string | undefined = (() => { if (plain) { return undefined } if (value > 0) { return classes.positive } else if (value < 0) { return classes.negative } })() const showValue = (() => { if (value === Math.trunc(value)) { return Math.abs(value) } return Math.abs(value).toFixed(2) })() return ( ) } ================================================ FILE: src/components/input/DatePicker.tsx ================================================ import { ReactElement } from 'react' import { FormControl, Grid } from '@material-ui/core' import { Controller, useFormContext } from 'react-hook-form' import useStyles from './styles' import ConditionallyWrap from 'ustaxes/components/ConditionallyWrap' import { MuiPickersUtilsProvider, KeyboardDatePicker as MuiDatePicker } from '@material-ui/pickers' import DateFnsUtils from '@date-io/date-fns' import { DatePickerProps } from './types' export function DatePicker( props: DatePickerProps ): ReactElement { const { label, required = false, name, minDate = new Date(1900, 0, 1), maxDate, useGrid = true, sizes = { xs: 12 } } = props const classes = useStyles() const { control, formState: { isSubmitted } } = useFormContext() return ( ( {children} )} > { const forceError: boolean | undefined = (isSubmitted && required && ((value as string | undefined | null) ?? undefined) === undefined) || value === '' const forceErrorProps = forceError ? { helperText: 'Input is required', error: true } : {} return (
) }} />
) } ================================================ FILE: src/components/input/LabeledCheckbox.tsx ================================================ import { ReactElement } from 'react' import { Checkbox, FormControl, FormControlLabel, FormGroup, Grid } from '@material-ui/core' import { Controller, useFormContext } from 'react-hook-form' import { LabeledCheckboxProps } from './types' import ConditionallyWrap from 'ustaxes/components/ConditionallyWrap' export function LabeledCheckbox( props: LabeledCheckboxProps ): ReactElement { const { label, name, useGrid = true, sizes = { xs: 12 } } = props const { control } = useFormContext() return ( ( {children} )} > ( onChange(checked)} color="primary" /> } label={label} value={value} /> )} control={control} /> ) } export default LabeledCheckbox ================================================ FILE: src/components/input/LabeledDropdown.tsx ================================================ import { useEffect, useRef, ReactElement } from 'react' import _ from 'lodash' import { useForkRef } from 'rooks' import { Grid, TextField } from '@material-ui/core' import { Controller, useFormContext } from 'react-hook-form' import locationPostalCodes from 'ustaxes/core/data/locationPostalCodes' import countries from 'ustaxes/core/data/countries' import { State } from 'ustaxes/core/data' import useStyles from './styles' import { BaseDropdownProps, LabeledDropdownProps } from './types' import ConditionallyWrap from 'ustaxes/components/ConditionallyWrap' export function GenericLabeledDropdown( props: LabeledDropdownProps ): ReactElement { const classes = useStyles() const { control, formState: { errors } } = useFormContext() const { autofocus, label, dropDownData, valueMapping, keyMapping, textMapping, noUndefined = false, required = true, name, useGrid = true, sizes = { xs: 12 } } = props const error: string | undefined = _.get(errors, name, undefined) as | string | undefined const inputRef = useRef(null) useEffect(() => { if (autofocus && inputRef.current) { inputRef.current.focus() } }, [inputRef.current]) return ( ( {children} )} > ( {(() => { if (!noUndefined) { return ))} )} name={name} rules={{ required: required }} control={control} /> ) } /** * A specialized version of a dropdown that just handles an array of strings * * @param props */ export const LabeledDropdown = ( props: BaseDropdownProps & { dropDownData: string[] } ): ReactElement => ( {...props} valueMapping={(x) => x} keyMapping={(x, n) => n} textMapping={(x) => x} /> ) export const USStateDropDown = ( props: BaseDropdownProps ): ReactElement => ( {...props} dropDownData={locationPostalCodes} valueMapping={([, code]) => code} keyMapping={([, code]) => code} textMapping={([name, code]) => `${code} - ${name}`} /> ) export const CountryDropDown = ( props: BaseDropdownProps ): ReactElement => ( {...props} dropDownData={countries} valueMapping={(name) => name} keyMapping={(_, idx) => idx} textMapping={(name) => name} /> ) export default LabeledDropdown ================================================ FILE: src/components/input/LabeledInput.tsx ================================================ import { useEffect, useRef, KeyboardEvent, ReactElement } from 'react' import { useForkRef } from 'rooks' import { InputAdornment, Grid, TextField } from '@material-ui/core' import { LabeledInputProps } from './types' import NumberFormat from 'react-number-format' import { Controller, FieldError, useFormContext } from 'react-hook-form' import { isNumeric, Patterns } from 'ustaxes/components/Patterns' import ConditionallyWrap from 'ustaxes/components/ConditionallyWrap' import useStyles from './styles' import { useFormContainer } from 'ustaxes/components/FormContainer/Context' import { getNestedValue } from 'ustaxes/core/util' export function LabeledInput( props: LabeledInputProps ): ReactElement { const { onSubmit } = useFormContainer() const { label, patternConfig: patternConfigDefined, name, rules = {} } = props const { required = patternConfigDefined !== undefined } = props const { autofocus, patternConfig = Patterns.plain, useGrid = true, sizes = { xs: 12 } } = props const classes = useStyles() const inputRef = useRef(null) useEffect(() => { if (autofocus && inputRef.current) { inputRef.current.focus() } }, [inputRef.current]) const { control, handleSubmit, register, formState: { errors } } = useFormContext() const error: FieldError | undefined = getNestedValue(errors, name, undefined) const errorMessage: string | undefined = (() => { if (error?.message !== undefined && error.message !== '') { return error.message } if (isNumeric(patternConfig)) { if (error?.type === 'max' && patternConfig.max !== undefined) { return `Input must be less than or equal to ${ patternConfig.prefix ?? '' }${patternConfig.max}` } if (error?.type === 'min' && patternConfig.min !== undefined) { return `Input must be greater than or equal to ${ patternConfig.prefix ?? '' }${patternConfig.min}` } } })() const input: ReactElement = (() => { if (isNumeric(patternConfig)) { return ( ( onChange(v.value)} value={value as number} error={error !== undefined} fullWidth helperText={errorMessage} variant="filled" InputLabelProps={{ shrink: true }} InputProps={{ startAdornment: patternConfig.prefix ? ( {patternConfig.prefix} ) : undefined, onKeyDown: (e: KeyboardEvent) => { if (e.key === 'Enter') { onSubmit?.() void handleSubmit(() => { //do nothing })() } } }} /> )} rules={{ ...rules, min: patternConfig.min, max: patternConfig.max, required: required ? 'Input is required' : undefined, pattern: { value: patternConfig.regexp ?? (required ? /.+/ : /.*/), message: patternConfig.description ?? (required ? 'Input is required' : '') } }} /> ) } return ( ( { if (e.key === 'Enter') { void handleSubmit(() => { // do nothing })() onSubmit?.() } }} fullWidth helperText={error?.message} error={error !== undefined} variant="filled" InputLabelProps={{ shrink: true }} /> )} /> ) })() return ( ( {children} )} > {input} ) } export default LabeledInput ================================================ FILE: src/components/input/LabeledRadio.tsx ================================================ import { ReactElement } from 'react' import { FormControl, FormControlLabel, FormLabel, Grid, Radio, RadioGroup } from '@material-ui/core' import { Controller, useFormContext } from 'react-hook-form' import { LabeledRadioProps } from './types' import useStyles from './styles' import ConditionallyWrap from 'ustaxes/components/ConditionallyWrap' export function LabeledRadio
(props: LabeledRadioProps): ReactElement { const { label, name, values, useGrid = true, sizes = { xs: 12 } } = props const classes = useStyles() const { control } = useFormContext() return ( ( {children} )} > (
{label} {values.map(([rowLabel, rowValue], i) => ( } label={rowLabel} /> ))}
)} control={control} />
) } ================================================ FILE: src/components/input/boxLabel.tsx ================================================ import { ReactElement } from 'react' const boxLabel = (box: string, description: string): ReactElement => ( <> Box {box} - {description} ) export default boxLabel ================================================ FILE: src/components/input/index.ts ================================================ import Currency from './Currency' import LabeledInput from './LabeledInput' import { LabeledCheckbox } from './LabeledCheckbox' import { LabeledRadio } from './LabeledRadio' import LabeledDropdown, { GenericLabeledDropdown, USStateDropDown } from './LabeledDropdown' import boxLabel from './boxLabel' import { DatePicker } from './DatePicker' const ssid = /^([0-9]{3})([0-9]{2})([0-9]{4}$)/ const ein = /^([0-9]{2})([0-9]{7})/ /** * Format a string like "123456789" as "123-45-6789". If the string is not * exactly 9 digits long, it returns empty string */ const formatSSID = (a: string): string => (ssid.exec(a) ?? []).slice(1).join('-') /** * Format a string like "123456789" as "12-3456789". If the string is not * exactly 9 digits long, it returns empty string */ const formatEIN = (a: string): string => (ein.exec(a) ?? []).slice(1).join('-') export { boxLabel, Currency, formatSSID, formatEIN, LabeledInput, LabeledCheckbox, LabeledDropdown, LabeledRadio, GenericLabeledDropdown, USStateDropDown, DatePicker } ================================================ FILE: src/components/input/styles.ts ================================================ import { createStyles, makeStyles, Theme } from '@material-ui/core' const useStyles = makeStyles(({ palette: { type: themeType } }: Theme) => createStyles({ root: { '& .MuiFormLabel-root': { color: themeType === 'dark' ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.54)' } } }) ) export default useStyles ================================================ FILE: src/components/input/types.ts ================================================ import { ReactElement } from 'react' import { Path, RegisterOptions } from 'react-hook-form' import { PatternConfig } from 'ustaxes/components/Patterns' import { GridSize } from '@material-ui/core/Grid' export interface BaseDropdownProps { label: string | ReactElement required?: boolean name: Path } export interface CurrencyProps { prefix?: string value: number plain?: boolean } interface SizeList { xs?: boolean | GridSize sm?: boolean | GridSize md?: boolean | GridSize lg?: boolean | GridSize } export interface LabeledDropdownProps extends BaseDropdownProps { autofocus?: boolean useGrid?: boolean sizes?: SizeList dropDownData: A[] valueMapping: (a: A, n: number) => string keyMapping: (a: A, n: number) => string | number textMapping: (a: A, n: number) => string noUndefined?: boolean } export interface LabeledInputProps { autofocus?: boolean useGrid?: boolean sizes?: SizeList patternConfig?: PatternConfig label: string | ReactElement required?: boolean name: Path defaultValue?: string rules?: Exclude< RegisterOptions, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' > } export interface LabeledFormProps { name: Path label: string useGrid?: boolean sizes?: SizeList } export type LabeledCheckboxProps = LabeledFormProps export interface LabeledRadioProps extends LabeledFormProps { values: Array<[string, string]> } export interface DatePickerProps extends LabeledFormProps { required?: boolean minDate?: Date maxDate: Date } ================================================ FILE: src/components/pager.tsx ================================================ import { createContext, useContext, PropsWithChildren, ReactElement, ReactNode } from 'react' import { useMediaQuery, Button, Grid } from '@material-ui/core' import { isMobileOnly as isMobile } from 'react-device-detect' import { Link } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router' export interface PagerProps { onAdvance: () => void navButtons?: ReactElement } export const PagerContext = createContext({ onAdvance: () => { // do nothing } }) interface PagerProviderProps
{ pages: A[] } interface Page { url: string } export const PagerProvider = ({ children, pages }: PropsWithChildren>): ReactElement => { const lookup = new Map(pages.map((p, i) => [p.url, i])) const currentLoc = useLocation() const currentPage = lookup.get(currentLoc.pathname) ?? 0 const navigate = useNavigate() const onAdvance: (() => void) | undefined = (() => { if (currentPage < pages.length - 1) { return () => navigate(pages[currentPage + 1].url) } })() const prev = currentPage > 0 ? pages[currentPage - 1] : undefined const navButtons: ReactElement = ( ) return ( { // end of pages }), navButtons }} > {children} ) } interface PagerButtonsProps { previousUrl?: string nextUrl?: string submitText?: string } export const PagerButtons = ({ submitText, previousUrl }: PagerButtonsProps): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const backButton = (() => { if (previousUrl !== undefined && previousUrl !== '/start') { return ( ) } })() const submitButton: ReactNode = (() => { if (submitText !== undefined) { return ( ) } })() return ( {backButton} {submitButton} ) } interface StartButtonsProps { firstUrl: string firstText: string secondUrl: string secondText: string } export const StartButtons = ({ firstUrl, firstText, secondUrl, secondText }: StartButtonsProps): ReactElement => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') return ( ) } interface SingleButtonsProps { url: string text: string } export const SingleButtons = ({ url, text }: SingleButtonsProps): ReactElement => { return ( ) } export const usePager = (): PagerProps => useContext(PagerContext) ================================================ FILE: src/components/payments/EstimatedTaxes.tsx ================================================ import { ReactElement } from 'react' import { FormProvider, useForm } from 'react-hook-form' import { usePager } from 'ustaxes/components/pager' import { EstimatedTaxPayments, TaxYear } from 'ustaxes/core/data' import { YearsTaxesState } from 'ustaxes/redux' import { Currency, LabeledInput } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { FormListContainer } from 'ustaxes/components/FormContainer' import { Grid } from '@material-ui/core' import { Work } from '@material-ui/icons' import { addEstimatedPayment, editEstimatedPayment, removeEstimatedPayment } from 'ustaxes/redux/actions' import { useDispatch } from 'ustaxes/redux' import { useSelector } from 'react-redux' import { useYearSelector } from 'ustaxes/redux/yearDispatch' interface EstimatedTaxesUserInput { label: string payment: string } const blankUserInput: EstimatedTaxesUserInput = { label: '', payment: '' } const toPayments = ( formData: EstimatedTaxesUserInput ): EstimatedTaxPayments => ({ ...formData, // Note we are not error checking here because // we are already in the input validated happy path // of handleSubmit. label: formData.label, payment: parseInt(formData.payment) }) const toEstimatedTaxesUserInput = ( data: EstimatedTaxPayments ): EstimatedTaxesUserInput => ({ ...blankUserInput, ...data, label: data.label, payment: data.payment.toString() }) export default function EstimatedTaxes(): ReactElement { const defaultValues = blankUserInput const activeYear: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) const estimatedTaxes = useYearSelector( (state) => state.information.estimatedTaxes ) const dispatch = useDispatch() const methods = useForm({ defaultValues }) const { navButtons, onAdvance } = usePager() const onSubmitAdd = (formData: EstimatedTaxesUserInput): void => { dispatch(addEstimatedPayment(toPayments(formData))) } const onSubmitEdit = (index: number) => (formData: EstimatedTaxesUserInput): void => { dispatch(editEstimatedPayment({ index, value: toPayments(formData) })) } const w2sBlock = ( defaultValues={defaultValues} items={estimatedTaxes.map((a) => toEstimatedTaxesUserInput(a))} onSubmitAdd={onSubmitAdd} onSubmitEdit={onSubmitEdit} removeItem={(i) => dispatch(removeEstimatedPayment(i))} icon={() => } primary={(estimatedTaxes: EstimatedTaxesUserInput) => estimatedTaxes.label } secondary={(estimatedTaxes: EstimatedTaxesUserInput) => ( Payment: )} > ) const form: ReactElement = <>{w2sBlock} return (

Estimated Taxes

Did you already make payments towards your {activeYear} taxes this year or last year?

{form} {navButtons}
) } ================================================ FILE: src/components/savingsAccounts/IRA.tsx ================================================ import { ReactElement } from 'react' import { Helmet } from 'react-helmet' import { useYearSelector, useYearDispatch } from 'ustaxes/redux/yearDispatch' import { FormProvider, useForm } from 'react-hook-form' import { usePager } from 'ustaxes/components/pager' import { Ira, IraPlanType, IraPlanTypeTexts, Person, PersonRole } from 'ustaxes/core/data' import { Currency, LabeledInput, GenericLabeledDropdown, formatSSID, LabeledCheckbox, boxLabel } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { FormListContainer } from 'ustaxes/components/FormContainer' import { Grid } from '@material-ui/core' import { Work } from '@material-ui/icons' import { TaxesState } from 'ustaxes/redux' import { addIRA, editIRA, removeIRA } from 'ustaxes/redux/actions' import { intentionallyFloat } from 'ustaxes/core/util' interface IraUserInput { payer: string personRole: PersonRole.PRIMARY | PersonRole.SPOUSE // fields about distributions from form 1099-R grossDistribution?: string // 1099-R box 1 taxableAmount?: string // 1099-R box 2a taxableAmountNotDetermined?: boolean // 1099-R box 2b totalDistribution?: boolean // 1099-R box 2b federalIncomeTaxWithheld?: string // 1099-R box 4 planType: IraPlanType // fields about contributions from form 5498 contributions?: string // 5498 box 1 rolloverContributions?: string // 5498 box 2 rothIraConversion?: string // 5498 box 3 recharacterizedContributions?: string // 5498 box 4 requiredMinimumDistributions?: string // 5498 box 12b lateContributions?: string // 5498 box 13a repayments?: string // 5498 box 14a } const blankUserInput: IraUserInput = { payer: '', personRole: PersonRole.PRIMARY, grossDistribution: '', taxableAmount: '', taxableAmountNotDetermined: false, totalDistribution: false, federalIncomeTaxWithheld: '', planType: IraPlanType.IRA, // fields about contributions from form 5498 contributions: '', rolloverContributions: '', rothIraConversion: '', recharacterizedContributions: '', requiredMinimumDistributions: '', lateContributions: '', repayments: '' } const toIra = (formData: IraUserInput): Ira => ({ ...formData, // Note we are not error checking here because // we are already in the input validated happy path // of handleSubmit. payer: formData.payer, personRole: formData.personRole, grossDistribution: Number(formData.grossDistribution), taxableAmount: Number(formData.taxableAmount), taxableAmountNotDetermined: formData.taxableAmountNotDetermined ?? false, totalDistribution: formData.totalDistribution ?? false, federalIncomeTaxWithheld: Number(formData.federalIncomeTaxWithheld), planType: formData.planType, // fields about contributions from form 5498 contributions: Number(formData.contributions), rolloverContributions: Number(formData.rolloverContributions), rothIraConversion: Number(formData.rothIraConversion), recharacterizedContributions: Number(formData.recharacterizedContributions), requiredMinimumDistributions: Number(formData.requiredMinimumDistributions), lateContributions: Number(formData.lateContributions), repayments: Number(formData.repayments) }) const toIraUserInput = (data: Ira): IraUserInput => ({ ...blankUserInput, ...data, grossDistribution: data.grossDistribution.toString(), taxableAmount: data.taxableAmount.toString(), federalIncomeTaxWithheld: data.federalIncomeTaxWithheld.toString(), // fields about contributions from form 5498 contributions: data.contributions.toString(), rolloverContributions: data.rolloverContributions.toString(), rothIraConversion: data.rothIraConversion.toString(), recharacterizedContributions: data.recharacterizedContributions.toString(), requiredMinimumDistributions: data.requiredMinimumDistributions.toString(), lateContributions: data.lateContributions.toString(), repayments: data.repayments.toString() }) export default function IRA(): ReactElement { const defaultValues = blankUserInput const ira = useYearSelector( (state: TaxesState) => state.information.individualRetirementArrangements ) const people: Person[] = useYearSelector((state: TaxesState) => [ state.information.taxPayer.primaryPerson, state.information.taxPayer.spouse ]) .filter((p) => p !== undefined) .map((p) => p as Person) const dispatch = useYearDispatch() const methods = useForm({ defaultValues }) const { handleSubmit } = methods const { navButtons, onAdvance } = usePager() const onSubmitAdd = (formData: IraUserInput): void => { dispatch(addIRA(toIra(formData))) } const onSubmitEdit = (index: number) => (formData: IraUserInput): void => { dispatch(editIRA({ index, value: toIra(formData) })) } const hsaBlock = ( defaultValues={defaultValues} items={ira.map((a) => toIraUserInput(a))} onSubmitAdd={onSubmitAdd} onSubmitEdit={onSubmitEdit} removeItem={(i) => dispatch(removeIRA(i))} icon={() => } primary={(ira: IraUserInput) => ira.payer} secondary={(ira: IraUserInput) => ( {IraPlanTypeTexts[ira.planType]}
gross distribution:
contribution:
)} > name="payer" label="Payer for this account" patternConfig={Patterns.plain} sizes={{ xs: 12, lg: 12 }} /> label="IRA Type" dropDownData={Object.values(IraPlanType)} valueMapping={(x) => x} keyMapping={(_, i) => i} textMapping={(status) => IraPlanTypeTexts[status]} name="planType" /> dropDownData={people} label="Recipient" valueMapping={(p: Person, i: number) => [PersonRole.PRIMARY, PersonRole.SPOUSE][i] } name="personRole" keyMapping={(p: Person, i: number) => i} textMapping={(p: Person) => `${p.firstName} ${p.lastName} (${formatSSID(p.ssid)})` } />

Contributions (Form 5498)

If you made no contributions you may not have received form 5498 for this account and may leave these fields blank.

Distributions (Form 1099-R)

If you have no distributions from this account then you may not have received form 1099-R and may leave these fields blank.

) const form: ReactElement = <>{hsaBlock} return (
Individual Retirement Arrangements (IRA) | Savings Accounts | UsTaxes.org

Individual Retirement Arrangements (IRA)

{form} {navButtons}
) } ================================================ FILE: src/components/savingsAccounts/healthSavingsAccounts.tsx ================================================ import { ReactElement } from 'react' import { Helmet } from 'react-helmet' import { useSelector } from 'react-redux' import { useYearSelector, useYearDispatch } from 'ustaxes/redux/yearDispatch' import { FormProvider, useForm } from 'react-hook-form' import { usePager } from 'ustaxes/components/pager' import { HealthSavingsAccount, Person, PersonRole, TaxYear, TaxYears } from 'ustaxes/core/data' import { Currency, LabeledInput, GenericLabeledDropdown, LabeledDropdown, formatSSID, DatePicker } from 'ustaxes/components/input' import { Patterns } from 'ustaxes/components/Patterns' import { FormListContainer } from 'ustaxes/components/FormContainer' import { Grid } from '@material-ui/core' import { Work } from '@material-ui/icons' import { TaxesState } from 'ustaxes/redux' import { addHSA, editHSA, removeHSA } from 'ustaxes/redux/actions' import { YearsTaxesState } from 'ustaxes/redux' import { format } from 'date-fns' import { intentionallyFloat } from 'ustaxes/core/util' interface HSAUserInput { label: string coverageType: 'self-only' | 'family' contributions: string personRole: PersonRole.PRIMARY | PersonRole.SPOUSE startDate: Date endDate: Date totalDistributions: string qualifiedDistributions: string } const blankUserInput: HSAUserInput = { label: '', coverageType: 'self-only', contributions: '', personRole: PersonRole.PRIMARY, startDate: new Date(), endDate: new Date(), totalDistributions: '', qualifiedDistributions: '' } const toHSA = (formData: HSAUserInput): HealthSavingsAccount => ({ ...formData, // Note we are not error checking here because // we are already in the input validated happy path // of handleSubmit. label: formData.label, coverageType: formData.coverageType, contributions: parseInt(formData.contributions), personRole: formData.personRole, startDate: formData.startDate.toISOString(), endDate: formData.endDate.toISOString(), totalDistributions: parseInt(formData.totalDistributions), qualifiedDistributions: parseInt(formData.qualifiedDistributions) }) const toHSAUserInput = (data: HealthSavingsAccount): HSAUserInput => ({ ...blankUserInput, ...data, coverageType: data.coverageType, contributions: data.contributions.toString(), startDate: new Date(data.startDate), endDate: new Date(data.endDate), totalDistributions: data.totalDistributions.toString(), qualifiedDistributions: data.qualifiedDistributions.toString() }) export default function HealthSavingsAccounts(): ReactElement { const defaultValues = blankUserInput const hsa = useYearSelector( (state: TaxesState) => state.information.healthSavingsAccounts ) const people: Person[] = useYearSelector((state: TaxesState) => [ state.information.taxPayer.primaryPerson, state.information.taxPayer.spouse ]) .filter((p) => p !== undefined) .map((p) => p as Person) const dispatch = useYearDispatch() const methods = useForm({ defaultValues }) const { handleSubmit } = methods const activeYear: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) const { navButtons, onAdvance } = usePager() const onSubmitAdd = (formData: HSAUserInput): void => { dispatch(addHSA(toHSA(formData))) } const onSubmitEdit = (index: number) => (formData: HSAUserInput): void => { dispatch(editHSA({ index, value: toHSA(formData) })) } const hsaBlock = ( defaultValues={defaultValues} items={hsa.map((a) => toHSAUserInput(a))} onSubmitAdd={onSubmitAdd} onSubmitEdit={onSubmitEdit} removeItem={(i) => dispatch(removeHSA(i))} icon={() => } primary={(hsa: HSAUserInput) => hsa.label} secondary={(hsa: HSAUserInput) => ( contributions:
total distributions:{' '}
qualified distributions:{' '}
coverage type: {hsa.coverageType}
coverage span: {format(hsa.startDate, 'MMMM do, yyyy')} to{' '} {format(hsa.endDate, 'MMMM do, yyyy')}
)} > name="label" label="label for this account" patternConfig={Patterns.plain} sizes={{ xs: 12, lg: 6 }} /> name="contributions" label="Your total contributions to this account." patternConfig={Patterns.currency} sizes={{ xs: 12, lg: 6 }} /> name="totalDistributions" label="Total distributions from this account." patternConfig={Patterns.currency} sizes={{ xs: 12, lg: 6 }} /> name="qualifiedDistributions" label="Qualified medical distributions from this account." patternConfig={Patterns.currency} sizes={{ xs: 12, lg: 6 }} /> dropDownData={['self-only', 'family']} label="Coverage Type" name="coverageType" /> dropDownData={people} label="Recipient" valueMapping={(p: Person, i: number) => [PersonRole.PRIMARY, PersonRole.SPOUSE][i] } name="personRole" keyMapping={(p: Person, i: number) => i} textMapping={(p: Person) => `${p.firstName} ${p.lastName} (${formatSSID(p.ssid)})` } /> ) const form: ReactElement = <>{hsaBlock} return (
Health Savings Accounts (HSA) | Savings Accounts | UsTaxes.org

Health Savings Accounts (HSA)

{form} {navButtons}
) } ================================================ FILE: src/core/data/anonymize.ts ================================================ import { YearsTaxesState } from 'ustaxes/redux' import { F3921, Ira, Supported1099, Dependent, Employer, F1098e, Information, Person, PersonRole, TaxPayer, Address, IncomeW2, Property, ScheduleK1Form1065 } from '.' const anonymizePerson = (person: Person): Person => { const roles = [ PersonRole.PRIMARY, PersonRole.SPOUSE, PersonRole.DEPENDENT, PersonRole.EMPLOYER ] return { ...person, ssid: `${100000000 + roles.findIndex((r) => r === person.role)}`, firstName: `${person.role}first`, lastName: `${person.role}last` } } const anonymizeDependent = (dependent: Dependent, idx: number): Dependent => { return { ...dependent, ...anonymizePerson(dependent), ssid: `${200000000 + idx}`, firstName: `dependent-${idx}-first`, lastName: `dependent-${idx}-first` } } const anonymizeAddress = (address: Address, prefix = ''): Address => ({ ...address, address: `${prefix} address`, city: `${prefix} city`, aptNo: address.aptNo !== undefined ? `${prefix} apt` : undefined, zip: address.zip !== undefined ? '99999' : undefined, foreignCountry: address.foreignCountry === undefined ? undefined : `${prefix} foreign country`, postalCode: address.postalCode !== undefined ? `${prefix} postal` : undefined, province: address.province !== undefined ? `${prefix} province` : undefined }) const anonymizeEmployer = (employer: Employer): Employer => ({ ...employer, EIN: '999999999', employerName: 'anonymous employer', address: employer.address === undefined ? undefined : anonymizeAddress(employer.address, 'employer') }) const anonymizeF1098e = (f: F1098e): F1098e => ({ ...f, lender: 'lender' }) const anonymizeF1099 = (f: Supported1099, idx: number): Supported1099 => ({ ...f, payer: `payer ${idx}` }) const anonymizeF3921 = (f: F3921, idx: number): F3921 => ({ ...f, name: `3921 name ${idx}` }) const anonymizeIra = (ira: Ira, idx: number): Ira => ({ ...ira, payer: `ira payer ${idx}` }) const anonymizeTaxPayer = (tp: TaxPayer): TaxPayer => ({ ...tp, contactEmail: tp.contactEmail === undefined ? undefined : 'a@b.com', contactPhoneNumber: tp.contactPhoneNumber === undefined ? undefined : '1234567890', dependents: tp.dependents.map(anonymizeDependent), primaryPerson: tp.primaryPerson === undefined ? undefined : { ...tp.primaryPerson, ...anonymizePerson(tp.primaryPerson) }, spouse: tp.spouse === undefined ? undefined : { ...tp.spouse, ...anonymizePerson(tp.spouse) } }) const anonymizeW2 = (w2: IncomeW2): IncomeW2 => ({ ...w2, employer: w2.employer === undefined ? undefined : anonymizeEmployer(w2.employer), occupation: 'occupation' }) const anonymizeRealEstate = (realEstate: Property): Property => ({ ...realEstate, address: anonymizeAddress(realEstate.address, 'property') }) const anonymizeK1 = ( k1: ScheduleK1Form1065, idx: number ): ScheduleK1Form1065 => ({ ...k1, partnershipName: `partnership ${idx}`, partnershipEin: '888888888' }) export const anonymizeInformation = (info: Information): Information => ({ ...info, f1098es: info.f1098es.map(anonymizeF1098e), f1099s: info.f1099s.map(anonymizeF1099), f3921s: info.f3921s.map(anonymizeF3921), individualRetirementArrangements: info.individualRetirementArrangements.map(anonymizeIra), refund: info.refund === undefined ? undefined : { ...info.refund, accountNumber: '123456789', routingNumber: '123456789' }, taxPayer: anonymizeTaxPayer(info.taxPayer), w2s: info.w2s.map(anonymizeW2), realEstate: info.realEstate.map(anonymizeRealEstate), scheduleK1Form1065s: info.scheduleK1Form1065s.map(anonymizeK1) }) export default (input: YearsTaxesState): YearsTaxesState => ({ ...input, Y2019: anonymizeInformation(input.Y2019), Y2020: anonymizeInformation(input.Y2020), Y2021: anonymizeInformation(input.Y2021) }) ================================================ FILE: src/core/data/countries.ts ================================================ const countries: string[] = [ 'Afghanistan', 'Åland Islands', 'Albania', 'Algeria', 'American Samoa', 'Andorra', 'Angola', 'Anguilla', 'Antarctica', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Aruba', 'Australia', 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bermuda', 'Bhutan', 'Bolivia (Plurinational State of)', 'Bonaire, Sint Eustatius and Saba', 'Bosnia and Herzegovina', 'Botswana', 'Bouvet Island', 'Brazil', 'British Indian Ocean Territory', 'United States Minor Outlying Islands', 'Virgin Islands (British)', 'Virgin Islands (U.S.)', 'Brunei Darussalam', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cambodia', 'Cameroon', 'Canada', 'Cabo Verde', 'Cayman Islands', 'Central African Republic', 'Chad', 'Chile', 'China', 'Christmas Island', 'Cocos (Keeling) Islands', 'Colombia', 'Comoros', 'Congo', 'Congo (Democratic Republic of the)', 'Cook Islands', 'Costa Rica', 'Croatia', 'Cuba', 'Curaçao', 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia', 'Falkland Islands (Malvinas)', 'Faroe Islands', 'Fiji', 'Finland', 'France', 'French Guiana', 'French Polynesia', 'French Southern Territories', 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Gibraltar', 'Greece', 'Greenland', 'Grenada', 'Guadeloupe', 'Guam', 'Guatemala', 'Guernsey', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Heard Island and McDonald Islands', 'Holy See', 'Honduras', 'Hong Kong', 'Hungary', 'Iceland', 'India', 'Indonesia', "Côte d'Ivoire", 'Iran (Islamic Republic of)', 'Iraq', 'Ireland', 'Isle of Man', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jersey', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'Kuwait', 'Kyrgyzstan', "Lao People's Democratic Republic", 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Macao', 'Macedonia (the former Yugoslav Republic of)', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Martinique', 'Mauritania', 'Mauritius', 'Mayotte', 'Mexico', 'Micronesia (Federated States of)', 'Moldova (Republic of)', 'Monaco', 'Mongolia', 'Montenegro', 'Montserrat', 'Morocco', 'Mozambique', 'Myanmar', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Caledonia', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'Niue', 'Norfolk Island', "Korea (Democratic People's Republic of)", 'Northern Mariana Islands', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palestine, State of', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Pitcairn', 'Poland', 'Portugal', 'Puerto Rico', 'Qatar', 'Republic of Kosovo', 'Réunion', 'Romania', 'Russian Federation', 'Rwanda', 'Saint Barthélemy', 'Saint Helena, Ascension and Tristan da Cunha', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Martin (French part)', 'Saint Pierre and Miquelon', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Sint Maarten (Dutch part)', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Georgia and the South Sandwich Islands', 'Korea (Republic of)', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Svalbard and Jan Mayen', 'Swaziland', 'Sweden', 'Switzerland', 'Syrian Arab Republic', 'Taiwan', 'Tajikistan', 'Tanzania, United Republic of', 'Thailand', 'Timor-Leste', 'Togo', 'Tokelau', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Turks and Caicos Islands', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom of Great Britain and Northern Ireland', 'United States of America', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Venezuela (Bolivarian Republic of)', 'Viet Nam', 'Wallis and Futuna', 'Western Sahara', 'Yemen', 'Zambia', 'Zimbabwe' ] export default countries ================================================ FILE: src/core/data/index.ts ================================================ import { enumKeys } from '../util' export enum TaxYears { Y2019 = 2019, Y2020 = 2020, Y2021 = 2021 } export type TaxYear = keyof typeof TaxYears export enum PersonRole { PRIMARY = 'PRIMARY', SPOUSE = 'SPOUSE', DEPENDENT = 'DEPENDENT', EMPLOYER = 'EMPLOYER' } /** * Types such as the following are generic with respect to the Date * type. AJV tests the typed serialization of these interfaces * in JSON, and Date is not a valid type in JSON. So when our data * is serialized in and out of local storage, or to a JSON file, * these data must be parsed / serialized from / to strings. * * Our AJV schema generator ignores generic types. */ export interface Person { firstName: string lastName: string ssid: string role: PersonRole isBlind: boolean dateOfBirth: D } // Concrete type for our AJV schema generator. export type PersonDateString = Person export interface QualifyingInformation { numberOfMonths: number isStudent: boolean } export interface Dependent extends Person { relationship: string qualifyingInfo?: QualifyingInformation } export type DependentDateString = Dependent export interface Address { address: string aptNo?: string city: string state?: State zip?: string foreignCountry?: string province?: string postalCode?: string } export interface PrimaryPerson extends Person { address: Address isTaxpayerDependent: boolean } export type PrimaryPersonDateString = PrimaryPerson export interface Spouse extends Person { isTaxpayerDependent: boolean } export type SpouseDateString = Spouse export interface Employer { EIN?: string employerName?: string address?: Address } export enum AccountType { checking = 'checking', savings = 'savings' } export interface Refund { routingNumber: string accountNumber: string accountType: AccountType } export interface IncomeW2 { occupation: string income: number medicareIncome: number fedWithholding: number ssWages: number ssWithholding: number medicareWithholding: number employer?: Employer personRole: PersonRole.PRIMARY | PersonRole.SPOUSE state?: State stateWages?: number stateWithholding?: number box12?: W2Box12Info } export interface EstimatedTaxPayments { label: string payment: number } export enum Income1099Type { B = 'B', INT = 'INT', DIV = 'DIV', R = 'R', SSA = 'SSA' } export interface F1099BData { shortTermProceeds: number shortTermCostBasis: number longTermProceeds: number longTermCostBasis: number } export interface F1099IntData { income: number } export interface F1099DivData { dividends: number qualifiedDividends: number totalCapitalGainsDistributions: number } /* TODO: Add in logic for various different distributions that should go in box 4a and 5a. Will need to implement form 8606 and Schedule 1 line 19. */ export enum PlanType1099 { /* IRA includes a traditional IRA, Roth IRA, * simplified employee pension (SEP) IRA, * and a savings incentive match plan for employees (SIMPLE) IRA */ IRA = 'IRA', RothIRA = 'RothIRA', SepIRA = 'SepIRA', SimpleIRA = 'SimpleIRA', // Pension and annuity payments include distributions from 401(k), 403(b), and governmental 457(b) plans. Pension = 'Pension' } export const PlanType1099Texts: { [k in keyof typeof PlanType1099]: string } = { IRA: 'traditional IRA', RothIRA: 'Roth IRA', SepIRA: 'simplified employee pension (SEP) IRA', SimpleIRA: 'savings incentive match plan for employees (SIMPLE) IRA', Pension: '401(k), 403(b), or 457(b) plan' } export interface F1099RData { grossDistribution: number taxableAmount: number federalIncomeTaxWithheld: number planType: PlanType1099 } export interface F1099SSAData { // benefitsPaid: number // benefitsRepaid: number netBenefits: number federalIncomeTaxWithheld: number } export interface Income1099 { payer: string type: T form: D personRole: PersonRole.PRIMARY | PersonRole.SPOUSE } export enum W2Box12Code { A = 'A', // Uncollected social security or RRTA tax on tips. B = 'B', // Uncollected Medicare tax on tips. C = 'C', // Taxable cost of group-term life insurance over $50,000. D = 'D', // Elective deferrals under a section 401(k) cash or deferred arrangement (plan). E = 'E', // Elective deferrals under a section 403(b) salary reduction agreement. F = 'F', // Elective deferrals under a section 408(k)(6) salary reduction SEP. G = 'G', // Elective deferrals and employer contributions (including nonelective deferrals) to any governmental or nongovernmental section 457(b) deferred compensation plan. H = 'H', // Elective deferrals under section 501(c)(18)(D) tax-exempt organization plan. J = 'J', // Nontaxable sick pay. K = 'K', // 20% excise tax on excess golden parachute payments (not applicable to Forms W-2AS, W-2CM, W-2GU, or W-2VI). L = 'L', // Substantiated employee business expense reimbursements. M = 'M', // Uncollected social security or RRTA tax on taxable cost of group-term life insurance over $50,000 (for former employees). N = 'N', // Uncollected Medicare tax on taxable cost of group-term life insurance over $50,000 (for former employees). P = 'P', // Excludable moving expense reimbursements paid directly to a member of the U.S. Armed Forces. Q = 'Q', // Nontaxable combat pay. R = 'R', // Employer contributions to an Archer MSA. S = 'S', // Employee salary reduction contributions under a section 408(p) SIMPLE plan. T = 'T', // Adoption benefits. V = 'V', // Income from the exercise of nonstatutory stock option(s). W = 'W', // Employer contributions to a health savings account (HSA). Y = 'Y', // Deferrals under a section 409A nonqualified deferred compensation plan. Z = 'Z', // Income under a nonqualified deferred compensation plan that fails to satisfy section 409A. AA = 'AA', // Designated Roth contributions under a section 401(k) plan. BB = 'BB', // Designated Roth contributions under a section 403(b) plan. DD = 'DD', // Cost of employer-sponsored health coverage. EE = 'EE', // Designated Roth contributions under a governmental section 457(b) plan. FF = 'FF', // Permitted benefits under a qualified small employer health reimbursement arrangement. GG = 'GG', // Income from qualified equity grants under section 83(i). HH = 'HH' // Aggregate deferrals under section 83(i) elections as of the close of the calendar year.} } export const W2Box12CodeDescriptions: { [key in W2Box12Code]: string } = { A: 'Uncollected social security or RRTA tax on tips.', B: 'Uncollected Medicare tax on tips.', C: 'Taxable cost of group-term life insurance over $50,000.', D: 'Elective deferrals under a section 401(k) cash or deferred arrangement plan.', E: 'Elective deferrals under a section 403(b) salary reduction agreement.', F: 'Elective deferrals under a section 408(k)(6) salary reduction SEP.', G: 'Elective deferrals and employer contributions (including nonelective deferrals) to any governmental or nongovernmental section 457(b) deferred compensation plan.', H: 'Elective deferrals under section 501(c)(18)(D) tax-exempt organization plan.', J: 'Nontaxable sick pay.', K: '20% excise tax on excess golden parachute payments (not applicable to Forms W-2AS, W-2CM, W-2GU, or W-2VI).', L: 'Substantiated employee business expense reimbursements.', M: 'Uncollected social security or RRTA tax on taxable cost of group-term life insurance over $50,000 (for former employees).', N: 'Uncollected Medicare tax on taxable cost of group-term life insurance over $50,000 (for former employees).', P: 'Excludable moving expense reimbursements paid directly to a member of the U.S. Armed Forces.', Q: 'Nontaxable combat pay.', R: 'Employer contributions to an Archer MSA.', S: 'Employee salary reduction contributions under a section 408(p) SIMPLE plan.', T: 'Adoption benefits.', V: 'Income from the exercise of nonstatutory stock option(s).', W: 'Employer contributions to a health savings account (HSA).', Y: 'Deferrals under a section 409A nonqualified deferred compensation plan.', Z: 'Income under a nonqualified deferred compensation plan that fails to satisfy section 409A.', AA: 'Designated Roth contributions under a section 401(k) plan.', BB: 'Designated Roth contributions under a section 403(b) plan.', DD: 'Cost of employer-sponsored health coverage.', EE: 'Designated Roth contributions under a governmental section 457(b) plan.', FF: 'Permitted benefits under a qualified small employer health reimbursement arrangement.', GG: 'Income from qualified equity grants under section 83(i).', HH: 'Aggregate deferrals under section 83(i) elections as of the close of the calendar year.' } export type W2Box12Info
= { [key in W2Box12Code]?: A } export interface HealthSavingsAccount { label: string coverageType: 'self-only' | 'family' contributions: number personRole: PersonRole.PRIMARY | PersonRole.SPOUSE startDate: D endDate: D totalDistributions: number qualifiedDistributions: number } export type HealthSavingsAccountDateString = HealthSavingsAccount export enum IraPlanType { IRA = 'IRA', RothIRA = 'RothIRA', SepIRA = 'SepIRA', SimpleIRA = 'SimpleIRA' } export const IraPlanTypeTexts = { [IraPlanType.IRA]: 'Traditional IRA', [IraPlanType.RothIRA]: 'Roth IRA', [IraPlanType.SepIRA]: 'Simplified employee pension (SEP) IRA', [IraPlanType.SimpleIRA]: 'Savings incentive match plan for employees (SIMPLE) IRA' } export type IraPlanName = keyof typeof IraPlanType export const iraPlanNames: IraPlanName[] = enumKeys(IraPlanType) // export const iraPlanNames: IraPlanName[] = [ // 'IRA', // 'RothIRA', // 'SepIRA', // 'SimpleIRA' // ] export interface Ira { payer: string personRole: PersonRole.PRIMARY | PersonRole.SPOUSE // fields about distributions from form 1099-R grossDistribution: number // 1099-R box 1 taxableAmount: number // 1099-R box 2a taxableAmountNotDetermined: boolean // 1099-R box 2b totalDistribution: boolean // 1099-R box 2b federalIncomeTaxWithheld: number // 1099-R box 4 planType: IraPlanType // fields about contributions from form 5498 contributions: number // contributions depending on the plan type rolloverContributions: number // 5498 box 2 rothIraConversion: number // 5498 box 3 recharacterizedContributions: number // 5498 box 4 requiredMinimumDistributions: number // 5498 box 12b lateContributions: number // 5498 box 13a repayments: number // 5498 box 14a } export enum FilingStatus { S = 'S', MFJ = 'MFJ', MFS = 'MFS', HOH = 'HOH', W = 'W' } export type FilingStatusName = keyof typeof FilingStatus export const FilingStatusTexts = { [FilingStatus.S]: 'Single', [FilingStatus.MFJ]: 'Married Filing Jointly', [FilingStatus.MFS]: 'Married Filing Separately', [FilingStatus.HOH]: 'Head of Household', [FilingStatus.W]: 'Widow(er)' } export const filingStatuses = ( p: TaxPayer | undefined ): FilingStatus[] => { let withDependents: FilingStatus[] = [] let spouseStatuses: FilingStatus[] = [] if ((p?.dependents ?? []).length > 0) { withDependents = [FilingStatus.HOH] } if (p?.spouse !== undefined) { spouseStatuses = [FilingStatus.MFJ, FilingStatus.MFS] // HoH not available if married withDependents = [] } else { spouseStatuses = [FilingStatus.S, FilingStatus.W] } return [...spouseStatuses, ...withDependents] } export interface ContactInfo { contactPhoneNumber?: string contactEmail?: string } export interface TaxPayer extends ContactInfo { filingStatus?: FilingStatus primaryPerson?: PrimaryPerson spouse?: Spouse dependents: Dependent[] } export type TaxPayerDateString = TaxPayer export type Income1099Int = Income1099 export type Income1099B = Income1099 export type Income1099Div = Income1099 export type Income1099R = Income1099 export type Income1099SSA = Income1099 export type Supported1099 = | Income1099Int | Income1099B | Income1099Div | Income1099R | Income1099SSA export enum PropertyType { singleFamily, multiFamily, vacation, commercial, land, selfRental, other } export type PropertyTypeName = keyof typeof PropertyType export enum PropertyExpenseType { advertising, auto, cleaning, commissions, insurance, legal, management, mortgage, otherInterest, repairs, supplies, taxes, utilities, depreciation, other } export type PropertyExpenseTypeName = keyof typeof PropertyExpenseType export interface Property { address: Address rentalDays: number personalUseDays: number rentReceived: number propertyType: PropertyTypeName otherPropertyType?: string qualifiedJointVenture: boolean expenses: Partial<{ [K in PropertyExpenseTypeName]: number }> otherExpenseType?: string } export interface F1098e { lender: string interest: number } export interface F3921 { name: string personRole: PersonRole.PRIMARY | PersonRole.SPOUSE exercisePricePerShare: number fmv: number numShares: number } // See https://www.irs.gov/instructions/i1065sk1 export interface ScheduleK1Form1065 { personRole: PersonRole.PRIMARY | PersonRole.SPOUSE partnershipName: string partnershipEin: string partnerOrSCorp: 'P' | 'S' isForeign: boolean isPassive: boolean ordinaryBusinessIncome: number // Schedule E (Form 1040), line 28, column (i) or (k). interestIncome: number // Form 1040, line 2b guaranteedPaymentsForServices: number // Schedule E (Form 1040), line 28, column (k) guaranteedPaymentsForCapital: number // Schedule E (Form 1040), line 28, column (k) selfEmploymentEarningsA: number // Schedule SE (Form 1040) selfEmploymentEarningsB: number // Schedule SE (Form 1040) selfEmploymentEarningsC: number // Schedule SE (Form 1040) distributionsCodeAAmount: number // If the amount shown as code A exceeds the adjusted basis of your partnership interest immediately before the distribution, the excess is treated as gain from the sale or exchange of your partnership interest. Generally, this gain is treated as gain from the sale of a capital asset and should be reported on Form 8949 and the Schedule D for your return. section199AQBI: number // Form 8995 or 8995-A } export interface ItemizedDeductions { medicalAndDental: string | number stateAndLocalTaxes: string | number isSalesTax: boolean stateAndLocalRealEstateTaxes: string | number stateAndLocalPropertyTaxes: string | number interest8a: string | number interest8b: string | number interest8c: string | number interest8d: string | number investmentInterest: string | number charityCashCheck: string | number charityOther: string | number } export type State = | 'AL' | 'AK' | 'AZ' | 'CO' | 'DC' | 'FL' | 'HI' | 'ID' | 'IN' | 'KY' | 'MA' | 'ME' | 'MN' | 'MS' | 'NC' | 'NE' | 'NJ' | 'NV' | 'OH' | 'OR' | 'RI' | 'SD' | 'TX' | 'VA' | 'WA' | 'WV' | 'AR' | 'CA' | 'CT' | 'DE' | 'GA' | 'IA' | 'IL' | 'KS' | 'LA' | 'MD' | 'MI' | 'MO' | 'MT' | 'ND' | 'NH' | 'NM' | 'NY' | 'OK' | 'PA' | 'SC' | 'TN' | 'UT' | 'VT' | 'WI' | 'WY' // Hold information about state residency // TODO: Support part-year state residency export interface StateResidency { state: State } // Defines usable tag names for each question later defined, // and maps to a type which is the expected response type. export interface QuestionTag { CRYPTO: boolean FOREIGN_ACCOUNT_EXISTS: boolean FINCEN_114: boolean FINCEN_114_ACCOUNT_COUNTRY: string FOREIGN_TRUST_RELATIONSHIP: boolean LIVE_APART_FROM_SPOUSE: boolean } export type QuestionTagName = keyof QuestionTag // Typescript provides no way to access // keys of an interface at runtime. export const questionTagNames: QuestionTagName[] = [ 'CRYPTO', 'FOREIGN_ACCOUNT_EXISTS', 'FINCEN_114', 'FINCEN_114_ACCOUNT_COUNTRY', 'FOREIGN_TRUST_RELATIONSHIP', 'LIVE_APART_FROM_SPOUSE' ] export type ValueTag = 'string' | 'boolean' export type Responses = Partial // Defines usable tag names for each question later defined, export enum CreditType { AdvanceChildTaxCredit = 'CreditType/AdvanceChildTaxCredit', Other = 'CreditType/Other' } export interface Credit { recipient: PersonRole amount: number type: CreditType } export interface Information { f1099s: Supported1099[] w2s: IncomeW2[] realEstate: Property[] estimatedTaxes: EstimatedTaxPayments[] f1098es: F1098e[] f3921s: F3921[] scheduleK1Form1065s: ScheduleK1Form1065[] itemizedDeductions: ItemizedDeductions | undefined refund?: Refund taxPayer: TaxPayer questions: Responses credits: Credit[] stateResidencies: StateResidency[] healthSavingsAccounts: HealthSavingsAccount[] individualRetirementArrangements: Ira[] } export type InformationDateString = Information /** * An asset can be anything that is transactable, such as a stock, * bond, mutual fund, real estate, or cryptocurrency, which is not reported * on 1099-B. A position always has an open date. A position may * be sold, at which time its gain or loss will be reported, * or it may be gifted to another person, at which time its * gain or loss will not be reported. * * An asset can be carried across multiple tax years, * so it should not be a sibling rather than a member of `Information`. * * If a position is real estate, then it has a state, which will * require state apportionment. * * "Closing an asset" can result in a long-term or short-term capital * gain. An asset is closed when it gets a closeDate. */ export type AssetType = 'Security' | 'Real Estate' export interface Asset { name: string positionType: AssetType openDate: D closeDate?: D giftedDate?: D openPrice: number openFee: number closePrice?: number closeFee?: number quantity: number state?: State } export type SoldAsset = Asset & { closePrice: number closeDate: D } export const isSold = (p: Asset): p is SoldAsset => { return p.closeDate !== undefined && p.closePrice !== undefined } export type AssetString = Asset // Validated action types: export interface ArrayItemEditAction { index: number value: A } export type EditDependentAction = ArrayItemEditAction export type EditW2Action = ArrayItemEditAction export type EditEstimatedTaxesAction = ArrayItemEditAction export type Edit1099Action = ArrayItemEditAction export type EditPropertyAction = ArrayItemEditAction export type Edit1098eAction = ArrayItemEditAction export type EditHSAAction = ArrayItemEditAction export type EditIraAction = ArrayItemEditAction export type EditAssetAction = ArrayItemEditAction> export type EditF3921Action = ArrayItemEditAction export type EditScheduleK1Form1065Action = ArrayItemEditAction export type EditCreditAction = ArrayItemEditAction ================================================ FILE: src/core/data/locationPostalCodes.ts ================================================ import { State } from '.' const locationPostalCodes: Array<[string, State]> = [ ['Alabama', 'AL'], ['Alaska', 'AK'], ['Arizona', 'AZ'], ['Arkansas', 'AR'], ['California', 'CA'], ['Colorado', 'CO'], ['Connecticut', 'CT'], ['Delaware', 'DE'], ['Florida', 'FL'], ['Georgia', 'GA'], ['Hawaii', 'HI'], ['Idaho', 'ID'], ['Illinois', 'IL'], ['Indiana', 'IN'], ['Iowa', 'IA'], ['Kansas', 'KS'], ['Kentucky', 'KY'], ['Louisiana', 'LA'], ['Maine', 'ME'], ['Maryland', 'MD'], ['Massachusetts', 'MA'], ['Michigan', 'MI'], ['Minnesota', 'MN'], ['Mississippi', 'MS'], ['Missouri', 'MO'], ['Montana', 'MT'], ['Nebraska', 'NE'], ['Nevada', 'NV'], ['New Hampshire', 'NH'], ['New Jersey', 'NJ'], ['New Mexico', 'NM'], ['New York', 'NY'], ['North Carolina', 'NC'], ['North Dakota', 'ND'], ['Ohio', 'OH'], ['Oklahoma', 'OK'], ['Oregon', 'OR'], ['Pennsylvania', 'PA'], ['Rhode Island', 'RI'], ['South Carolina', 'SC'], ['South Dakota', 'SD'], ['Tennessee', 'TN'], ['Texas', 'TX'], ['Utah', 'UT'], ['Vermont', 'VT'], ['Virginia', 'VA'], ['Washington', 'WA'], ['West Virginia', 'WV'], ['Wisconsin', 'WI'], ['Wyoming', 'WY'], ['District of Columbia', 'DC'] ] export default locationPostalCodes ================================================ FILE: src/core/data/questions.ts ================================================ import { FilingStatus, Income1099Type, Information, QuestionTagName, ValueTag } from '.' export interface Question { text: string required?: (state: Information) => boolean tag: QuestionTagName // This is repeated effort, as it has to mirror value type from QuestionTag: readonly valueTag: ValueTag } function q( tag: QuestionTagName, text: string, valueTag: ValueTag, required: (s: Information) => boolean ): Question { return { text, tag, required, valueTag } } function qr( tag: QuestionTagName, text: string, valueTag: ValueTag = 'boolean' ): Question { return { text, tag, valueTag } } export const questions: Question[] = [ qr('CRYPTO', 'Do you have any crypto-currency transactions?'), qr( 'FOREIGN_ACCOUNT_EXISTS', 'At any time in this year, did you have a financial interest in or signature authority over a financial account such as a bank account, securities account, or brokerage account) located in a foreign country?' ), q( 'FINCEN_114', 'Are you required to file FinCEN Form 114, Report of Foreign Bank and Financial Accounts (FBAR), to report that financial interest or signature authority? See FinCEN Form 114 and its instructions for filing requirements and exceptions to those requirements', 'boolean', (s: Information) => s.questions.FOREIGN_ACCOUNT_EXISTS ?? false ), q( 'FINCEN_114_ACCOUNT_COUNTRY', 'Enter the name of the foreign country where the financial account is located', 'string', (s: Information) => s.questions.FINCEN_114 ?? false ), qr( 'FOREIGN_TRUST_RELATIONSHIP', 'During this tax year, did you receive a distribution from, or were you the grantor of, or a transferor to, a foreign trust?' ), q( 'LIVE_APART_FROM_SPOUSE', `Did you live apart from your spouse for all of the year?`, 'boolean', (s: Information) => s.taxPayer.filingStatus == FilingStatus.MFS && s.f1099s.some((i) => i.type == Income1099Type.SSA) ) ] export const getRequiredQuestions = (state: Information): Question[] => questions.filter((q) => q.required === undefined || q.required(state)) ================================================ FILE: src/core/data/validate.ts ================================================ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { DefinedError, ValidateFunction } from 'ajv' import log from '../log' import * as fns from './validate-fns' import * as types from 'ustaxes/core/data' // We will simply throw a runtime error if the data does not // validate against the schema.definitions. export const checkType = (data: A, validate: ValidateFunction): A => { validate(data) if ((validate.errors ?? undefined) !== undefined) { // Taken from doc example: The type cast is needed to allow user-defined keywords and errors // You can extend this type to include your error types as needed. const errs = validate.errors as DefinedError[] for (const err of errs) { log.error(err.message) } log.error(validate.errors) log.error(data) const errorMessage = validate.errors ?.map((e) => `${e.instancePath}: ${e.message ?? ''}`) .join('\n') ?? 'Unknown error' validate.errors?.forEach(console.error) throw new Error(`Validation Failed: ${errorMessage}`) } return data } export const index = fns.Index as ValidateFunction export const personRole = fns.PersonRole as ValidateFunction export const contactInfo = fns.ContactInfo as ValidateFunction export const address = fns.Address as ValidateFunction export const accountType = fns.AccountType as ValidateFunction export const employer = fns.Employer as ValidateFunction export const filingStatus = fns.FilingStatus as ValidateFunction export const primaryPerson = fns.PrimaryPerson as ValidateFunction export const spouse = fns.Spouse as ValidateFunction export const person = fns.Person as ValidateFunction export const dependent = fns.Dependent as ValidateFunction export const f1099IntData = fns.F1099IntData as ValidateFunction export const f1099BData = fns.F1099BData as ValidateFunction export const income1099Int = fns.Income1099Int as ValidateFunction export const income1099B = fns.Income1099B as ValidateFunction export const supported1099 = fns.Supported1099 as ValidateFunction export const incomeW2 = fns.IncomeW2 as ValidateFunction export const estimatedTaxPayments = fns.EstimatedTaxPayments as ValidateFunction export const refund = fns.Refund as ValidateFunction export const taxPayer = fns.TaxPayer as ValidateFunction export const information = fns.Information as ValidateFunction export const property = fns.Property as ValidateFunction export const propertyType = fns.PropertyType as ValidateFunction export const f1098e = fns.F1098e as ValidateFunction export const itemizedDeductions = fns.ItemizedDeductions as ValidateFunction export const responses = fns.Responses as ValidateFunction export const stateResidency = fns.StateResidency as ValidateFunction export const healthSavingsAccount = fns.HealthSavingsAccount as ValidateFunction export const ira = fns.Ira as ValidateFunction export const assetType = fns.AssetType as ValidateFunction export const assetString = fns.AssetString as ValidateFunction export const taxYear = fns.TaxYear as ValidateFunction export const credit = fns.Credit as ValidateFunction export const editIraAction = fns.EditIRAAction as ValidateFunction export const editHSAAction = fns.EditHSAAction as ValidateFunction export const editCreditAction = fns.EditCreditAction as ValidateFunction ================================================ FILE: src/core/irsForms/Form.ts ================================================ import Fill from '../pdfFiller/Fill' export type FormTag = string /** * Base interface for what every form implementation should include. * Any PDF can be filled from an array of values. * */ export default abstract class Form extends Fill { // Match the filename without extension when downloaded from IRS abstract tag: FormTag // Match the sequence number in the header of the PDF. abstract sequenceIndex: number public toString = (): string => ` Form ${this.tag}, at sequence ${this.sequenceIndex} ` } ================================================ FILE: src/core/irsForms/index.ts ================================================ import { PDFDocument } from 'pdf-lib' import _ from 'lodash' import { getPdfs, PDFDownloader } from '../pdfFiller/pdfHandler' import Form from './Form' export const insertFormDataToPdfs = async ( forms: Form[], downloader: PDFDownloader ): Promise => { const pdfs: PDFDocument[] = await Promise.all( forms.map(async (f) => await downloader(`/irs/${f.tag}.pdf`)) ) return getPdfs(_.zipWith(forms, pdfs, (a, b) => [a, b])) } ================================================ FILE: src/core/irsForms/util.ts ================================================ export const displayRound = (n: number | undefined): number | undefined => Math.round(n ?? 0) === 0 ? undefined : Math.round(n ?? 0) export const displayNegPos = (n: number | undefined): string => { const r = Math.round(n ?? 0) if (r === 0) return '' if (r < 0) return `(${Math.abs(r)})` return r.toString() } export const sumFields = (fs: Array): number => fs.reduce((l, r) => l + (r ?? 0), 0) ================================================ FILE: src/core/log.ts ================================================ import log from 'loglevel' const { TRACE, ERROR } = log.levels log.setDefaultLevel(process.env.NODE_ENV === 'development' ? TRACE : ERROR) export default log ================================================ FILE: src/core/pdfFiller/Fill.ts ================================================ import { Field, RenderedField } from '.' export const fieldIsNumber = (field: Field): field is number => typeof field === 'number' || (!Number.isNaN(field) && Number.isFinite(field)) export default abstract class Fill { abstract fields: () => Field[] renderedFields = (): RenderedField[] => this.fields().map((field) => { if (fieldIsNumber(field)) { if (Number.isInteger(field)) { return field.toString() } return field.toFixed(2).toString() } else { return field } }) } ================================================ FILE: src/core/pdfFiller/fillPdf.ts ================================================ import { PDFDocument, PDFCheckBox, PDFTextField, PDFName } from 'pdf-lib' import { Field } from '.' import { displayRound } from '../irsForms/util' import _ from 'lodash' /** * Attempt to fill fields in a PDF from a Form, * checking one by one that each pdf field and Form value * Make sense by type (checkbox => boolean, textField => string / number) * Side-effecting! Modifies the pdf document. */ export function fillPDF(pdf: PDFDocument, fieldValues: Field[]): PDFDocument { const formFields = pdf.getForm().getFields() formFields.forEach((pdfField, index) => { const value: Field = fieldValues[index] const error = (expected: string): Error => { return new Error( `Field ${index}, ${pdfField.getName()} expected ${expected}` ) } // First handle radio groups. If the value for this field // is a RadioSelect object, then assume the pdfField // has children, and check the correct box given the index value. // Idea taken from this comment: // https://github.com/Hopding/pdf-lib/issues/780#issuecomment-771453157 // Note, this is for cases such as the 2021 IL-1040 where the field // behaves as a radio group, but the pdfField is a PDFCheckbox // instead of a PDFRadioGroup. if (_.isObject(value)) { const children = pdfField.acroField.getWidgets() if (value.select >= children.length) { throw new Error( `Error in field ${index}, expected to select child at index ${value.select} but this node has only ${children.length} children.` ) } const setValue = children[value.select].getOnValue() if (setValue !== undefined) { pdfField.acroField.dict.set(PDFName.of('V'), setValue) children[value.select].setAppearanceState(setValue) } else { console.error(children) throw new Error( `Error handling RadioGroup, could not set index ${value.select}` ) } } else if (pdfField instanceof PDFCheckBox) { if (value === true) { pdfField.check() } else if (value !== false && value !== undefined) { throw error('boolean') } } else if (pdfField instanceof PDFTextField) { try { const showValue = !isNaN(value as number) ? displayRound(value as number | undefined)?.toString() : value?.toString() pdfField.setText(showValue) } catch (err) { throw error('text field') } } else if (value !== undefined) { throw error('unknown') } pdfField.enableReadOnly() }) return pdf } ================================================ FILE: src/core/pdfFiller/index.ts ================================================ export interface RadioSelect { select: number } export type Field = string | number | boolean | RadioSelect | undefined export type RenderedField = string | boolean | RadioSelect | undefined ================================================ FILE: src/core/pdfFiller/pdfHandler.ts ================================================ import { PDFDocument } from 'pdf-lib' import Fill from './Fill' import { fillPDF } from './fillPdf' export interface FileDownloader { (url: string): Promise } export type PDFDownloader = FileDownloader export const downloadPDF: PDFDownloader = async (url) => { const download = await fetch(url) const buffer = await download.arrayBuffer() return await PDFDocument.load(buffer) } export const combinePdfs = async ( pdfFiles: PDFDocument[] ): Promise => { const [head, ...rest] = await Promise.all( pdfFiles.map(async (pdf) => PDFDocument.load(await pdf.save())) ) // Make sure we combine the documents from left to right and preserve order return rest.reduce(async (l, r) => { const doc = await PDFDocument.load(await (await l).save()) return await doc.copyPages(r, r.getPageIndices()).then((pgs) => { pgs.forEach((p) => doc.addPage(p)) return doc }) }, Promise.resolve(head)) } export const getPdfs = async ( formData: Array<[Fill, PDFDocument]> ): Promise => { // Insert the values from each field into the PDF const pdfFiles: Array> = formData.map( async ([data, f]) => { fillPDF(f, data.renderedFields()) const pageBytes = await f.save() return await PDFDocument.load(pageBytes) } ) return await Promise.all(pdfFiles) } ================================================ FILE: src/core/stateForms/Form.ts ================================================ import Fill from '../pdfFiller/Fill' import { IncomeW2, State } from '../data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' /** * Represents a state's income tax form, or schedule */ export default abstract class Form extends Fill { abstract state: State abstract formName: string abstract formOrder: number abstract attachments: () => Form[] abstract info: ValidatedInformation } /** * Methods that would apply to any state tax form */ export class FormMethods { form: Form constructor(form: Form) { this.form = form } stateW2s = (): IncomeW2[] => this.form.info.w2s.filter((w2) => w2.state === this.form.state) stateWithholding = (): number => this.stateW2s().reduce( (withholding, w2) => withholding + (w2.stateWithholding ?? 0), 0 ) witholdingForState = (state: State) => this.stateW2s() .filter((w2) => w2.state === state) .reduce((withholding, w2) => withholding + (w2.stateWithholding ?? 0), 0) } ================================================ FILE: src/core/stateForms/index.ts ================================================ import _ from 'lodash' import { PDFDocument } from 'pdf-lib' import { fillPDF } from '../pdfFiller/fillPdf' import { combinePdfs, PDFDownloader } from '../pdfFiller/pdfHandler' import Form from './Form' export const createStatePDF = (forms: Form[]) => async (downloader: PDFDownloader): Promise => { const filenames = forms.map( (form) => `/states/${form.state}/${form.formName}.pdf` ) const pdfs = filenames.map(downloader) const filled: Array> = _.zipWith( pdfs, forms, async (pdf, form) => { fillPDF(await pdf, form.fields()) return pdf } ) return combinePdfs(await Promise.all(filled)) } ================================================ FILE: src/core/tests/LocalForms.ts ================================================ import { PDFDocument } from 'pdf-lib' import { FileDownloader, PDFDownloader } from 'ustaxes/core/pdfFiller/pdfHandler' import fs from 'fs/promises' import path from 'path' import { TaxYear } from 'ustaxes/core/data' const localPath = (url: string) => path.join(__dirname, '../../../public', url) export const localFiles: FileDownloader = ( url: string ): Promise => fs.readFile(localPath(url)) const compiledPDFs: { [key: string]: PDFDocument } = {} export const localPDFs = (y: TaxYear): PDFDownloader => async (url: string): Promise => { const fileUrl = `/forms/${y}/${url}` const lookedUpAt = localPath(fileUrl) if (lookedUpAt in compiledPDFs) { return compiledPDFs[lookedUpAt] } const bytes = await localFiles(fileUrl) const pdf = await PDFDocument.load(bytes.buffer) compiledPDFs[lookedUpAt] = pdf return pdf } ================================================ FILE: src/core/tests/arbitraries.ts ================================================ import * as fc from 'fast-check' import { Arbitrary } from 'fast-check' import locationPostalCodes from '../data/locationPostalCodes' import { QuestionTagName, questionTagNames, Responses } from '../data' import * as types from '../data' import * as util from '../util' import _ from 'lodash' import { ValidatedInformation, ValidatedTaxpayer } from 'ustaxes/forms/F1040Base' import { blankYearTaxesState, YearsTaxesState } from 'ustaxes/redux' const lower: Arbitrary = fc .integer({ min: 0x61, max: 0x7a }) .map((n) => String.fromCharCode(n)) const upper: Arbitrary = fc .integer({ min: 0x41, max: 0x5a }) .map((n) => String.fromCharCode(n)) export const word: Arbitrary = fc .array(fc.oneof(lower, upper)) .map((xs) => xs.join('')) export const words: Arbitrary = fc.array(word).map((xs) => xs.join(' ')) const maxWords = (max: number): Arbitrary => fc .integer({ min: 1, max }) .chain((maxLength) => fc.array(word, { minLength: 1, maxLength }).map((xs) => xs.join(' ')) ) const natStr: Arbitrary = fc.nat().map((n) => n.toString()) const numStr = (len: number): Arbitrary => fc .array(fc.nat({ max: 9 }), { minLength: len, maxLength: len }) .map((x) => x.join('')) export const state = fc.constantFrom( ...locationPostalCodes.map(([, code]) => code) ) const concat = ( as: Arbitrary, bs: Arbitrary, sep = ' ' ): Arbitrary => as.chain((a) => bs.map((b) => `${a}${sep}${b}`)) // Ideally these would be normally distributed... const wages: Arbitrary = fc.nat({ max: 10000000 }) const posCurrency = (max: number): Arbitrary => fc.nat({ max: max * 100 }).map((x) => x / 100) const posNegCurrency = (max: number): Arbitrary => fc .integer({ min: -max * 100, max: max * 100 }) .map((x) => x / 100) const investment = posCurrency(100000) const investmentResult = posNegCurrency(100000) const expense: Arbitrary = posCurrency(10000) const interest: Arbitrary = posCurrency(10000) const payment: Arbitrary = fc.nat({ max: 100000 }) const ssWitholding: Arbitrary = fc.nat({ max: 10000 }) const payerName: Arbitrary = maxWords(3) const ein: Arbitrary = numStr(9) const zip: Arbitrary = fc.oneof(numStr(5), numStr(9)) const routing: Arbitrary = numStr(9) const account: Arbitrary = fc .integer({ min: 4, max: 17 }) .chain((len) => numStr(len)) const email = fc .tuple(word, word, word) .map(([w1, w2, w3]) => `${w1}@${w2}.${w3}`) const phoneNumber = numStr(10) const address: Arbitrary = fc .tuple(concat(natStr, words), natStr, words, state, zip) .map(([address, aptNo, city, state, zip]) => ({ address, aptNo, city, state, zip })) const employer: Arbitrary = fc .tuple(ein, fc.string({ minLength: 1 }), address) .map(([EIN, employerName, address]) => ({ EIN, employerName, address })) const w2Box12Info = (max: number): Arbitrary => fc.dictionary( fc.constantFrom(...util.enumKeys(types.W2Box12Code)), fc.nat({ max }) ) const w2: Arbitrary = wages.chain((income) => fc .tuple( maxWords(2), fc.nat({ max: 2 * income }), fc.nat({ max: income }), fc.nat({ max: income }), ssWitholding, fc.nat({ max: income }), employer, w2Box12Info(income), state, fc.nat({ max: income }), fc.nat({ max: income }) ) .map( ([ occupation, medicareIncome, fedWithholding, ssWages, ssWithholding, medicareWithholding, employer, box12, state, stateWages, stateWithholding ]) => ({ occupation, income, medicareIncome, fedWithholding, employer, personRole: types.PersonRole.PRIMARY, ssWages, ssWithholding, medicareWithholding, state, stateWages, stateWithholding, box12 }) ) ) export const f1099IntData: Arbitrary = fc .nat() .map((income) => ({ income })) export const f1099DivData: Arbitrary = interest.chain( (dividends) => fc .tuple(fc.nat({ max: Math.round(dividends * 100) }), posCurrency(100000)) .map(([qdiv, totalCapitalGainsDistributions]) => ({ dividends, qualifiedDividends: qdiv / 100, totalCapitalGainsDistributions })) ) export const f1099BData: Arbitrary = fc .tuple(investmentResult, investment, investmentResult, investment) .map( ([ shortTermProceeds, shortTermCostBasis, longTermProceeds, longTermCostBasis ]) => ({ shortTermProceeds, shortTermCostBasis, longTermProceeds, longTermCostBasis }) ) export const f1099Int: Arbitrary = fc .tuple(payerName, f1099IntData) .map(([payer, form]) => ({ type: types.Income1099Type.INT, form, payer, personRole: types.PersonRole.PRIMARY })) export const f1099B: Arbitrary = fc .tuple(payerName, f1099BData) .map(([payer, form]) => ({ type: types.Income1099Type.B, form, payer, personRole: types.PersonRole.PRIMARY })) export const f1099Div: Arbitrary = fc .tuple(payerName, f1099DivData) .map(([payer, form]) => ({ type: types.Income1099Type.DIV, form, payer, personRole: types.PersonRole.PRIMARY })) export const f1099: Arbitrary = fc.oneof( f1099B, f1099Div, f1099Int ) const propExpenseTypeName: Arbitrary = fc.constantFrom(...util.enumKeys(types.PropertyExpenseType)) const propertyType: Arbitrary = fc.constantFrom( ...util.enumKeys(types.PropertyType) ) const propertyExpenses: Arbitrary< Partial<{ [K in types.PropertyExpenseTypeName]: number }> > = fc.set(propExpenseTypeName).chain((es) => fc .array(expense, { minLength: es.length, maxLength: es.length }) .map((nums) => _.chain(es) .zipWith(nums, (e, num) => [e, num]) .fromPairs() .value() ) ) const f1098e: Arbitrary = fc .tuple(maxWords(2), interest) .map(([lender, interest]) => ({ lender, interest })) const f3921: Arbitrary = fc .tuple(maxWords(2), posCurrency(100), fc.integer({ min: 1, max: 500 })) .map(([name, exercisePricePerShare, numShares]) => { const fmv = exercisePricePerShare + 1 return { name, personRole: types.PersonRole.PRIMARY, exercisePricePerShare, fmv, numShares } }) const scheduleK1Form1065: Arbitrary = fc .tuple( maxWords(2), ein, posCurrency(1000000), posCurrency(100000), posCurrency(100000), posCurrency(100000), posCurrency(100000), posCurrency(100000), posCurrency(100000), posCurrency(100000), posCurrency(100000) ) .map( ([ partnershipName, ein, ordinaryBusinessIncome, interestIncome, guaranteedPaymentsForServices, guaranteedPaymentsForCapital, selfEmploymentEarningsA, selfEmploymentEarningsB, selfEmploymentEarningsC, distributionsCodeAAmount, section199AQBI ]) => { return { personRole: types.PersonRole.PRIMARY, partnershipName, partnershipEin: ein, partnerOrSCorp: 'P', isForeign: false, isPassive: false, ordinaryBusinessIncome, interestIncome, guaranteedPaymentsForServices, guaranteedPaymentsForCapital, selfEmploymentEarningsA, selfEmploymentEarningsB, selfEmploymentEarningsC, distributionsCodeAAmount, section199AQBI } } ) const itemizedDeductions: Arbitrary = fc .tuple( posCurrency(2500), posCurrency(12500), fc.boolean(), posCurrency(10000), posCurrency(5000), posCurrency(10000), posCurrency(10000), posCurrency(10000), posCurrency(10000), posCurrency(10000), posCurrency(7500), posCurrency(2500), posCurrency(1000) ) .map( ([ medicalAndDental, stateAndLocalTaxes, isSalesTax, stateAndLocalRealEstateTaxes, stateAndLocalPropertyTaxes, interest8a, interest8b, interest8c, interest8d, investmentInterest, charityCashCheck, charityOther ]) => ({ medicalAndDental, stateAndLocalTaxes, isSalesTax, stateAndLocalRealEstateTaxes, stateAndLocalPropertyTaxes, interest8a, interest8b, interest8c, interest8d, investmentInterest, charityCashCheck, charityOther }) ) const estTax: Arbitrary = fc .tuple(maxWords(5), payment) .map(([label, payment]) => ({ label, payment })) export const accountType: Arbitrary = fc.constantFrom( types.AccountType.checking, types.AccountType.savings ) export const refund: Arbitrary = fc .tuple(routing, account, accountType) .map(([routingNumber, accountNumber, accountType]) => ({ routingNumber, accountNumber, accountType })) export const filingStatus: Arbitrary = fc.constantFrom( ...util.enumKeys(types.FilingStatus).map((x) => types.FilingStatus[x]) ) export const person: Arbitrary = fc .tuple( word, word, ein, fc.boolean(), fc.date({ min: new Date(1900, 0, 1), max: new Date() }) ) .map(([firstName, lastName, ssid, isBlind, dateOfBirth]) => ({ firstName, lastName, ssid, isBlind, dateOfBirth, role: types.PersonRole.PRIMARY })) export const primaryPerson: Arbitrary = fc .tuple(person, address, fc.boolean()) .map(([person, address, isTaxpayerDependent]) => ({ ...person, address, isTaxpayerDependent })) export const spouse: Arbitrary = fc .tuple(person, fc.boolean()) .map(([person, isTaxpayerDependent]) => ({ ...person, isTaxpayerDependent })) const questionTag: Arbitrary = fc.constantFrom( ...questionTagNames ) // make sure that the question tag maps to values of correct type. const questionTagArbs = { CRYPTO: fc.boolean(), FOREIGN_ACCOUNT_EXISTS: fc.boolean(), FINCEN_114: fc.boolean(), FINCEN_114_ACCOUNT_COUNTRY: words, FOREIGN_TRUST_RELATIONSHIP: fc.boolean(), LIVE_APART_FROM_SPOUSE: fc.boolean() } export const questions: Arbitrary = fc .uniqueArray(questionTag, { comparator: 'IsStrictlyEqual' }) .chain((tags) => fc .tuple(...tags.map((t) => questionTagArbs[t].map((v) => [t, v]))) .map((kvs) => Object.fromEntries(kvs) as Responses) ) // const iraPlan: Arbitrary = fc.constantFrom(...iraPlanNames) export class Arbitraries { currentYear: number constructor(currentYear: number) { this.currentYear = currentYear } daysInYear = (): Arbitrary => fc.nat({ max: util.daysInYear(this.currentYear) }) daysInYearPair = (): Arbitrary<[number, number]> => this.daysInYear().chain((d) => fc .nat({ max: util.daysInYear(this.currentYear) - d }) .map((d2) => [d, d2]) ) birthYear = (): Arbitrary => fc.integer({ min: 1900, max: this.currentYear }) property = (): Arbitrary => fc .tuple( address, this.daysInYearPair(), investment, propertyType, words, fc.boolean(), propertyExpenses ) .map( ([ address, [rentalDays, personalUseDays], rentReceived, propertyType, otherPropertyType, qualifiedJointVenture, expenses ]) => ({ address, rentalDays, personalUseDays, rentReceived, propertyType, otherPropertyType, qualifiedJointVenture, expenses }) ) qualifyingInformation = (): Arbitrary => fc .tuple(fc.nat({ max: 12 }), fc.boolean()) .map(([numberOfMonths, isStudent]) => ({ numberOfMonths, isStudent })) dependent = (): Arbitrary => fc .tuple(person, word, this.qualifyingInformation()) .map(([person, relationship, qualifyingInfo]) => ({ ...person, relationship, qualifyingInfo })) taxPayer = (): Arbitrary => fc .tuple( filingStatus, primaryPerson, fc.oneof(spouse, fc.constant(undefined)), fc.array(this.dependent()), email, phoneNumber ) .map( ([ filingStatus, primaryPerson, spouse, dependents, contactEmail, contactPhoneNumber ]) => ({ filingStatus, primaryPerson, spouse, dependents, contactEmail, contactPhoneNumber }) ) // Ensure we don't generate an invalid filing status. .chain((tp) => fc .constantFrom(...types.filingStatuses(tp)) .map((fs) => ({ ...tp, filingStatus: fs })) ) healthSavingsAccount = (): Arbitrary => fc .tuple( words, fc.constantFrom<'self-only' | 'family'>('self-only', 'family'), fc.nat({ max: 100000 }), fc.constantFrom( types.PersonRole.PRIMARY, types.PersonRole.SPOUSE ), fc.date({ min: new Date(this.currentYear, 0, 1), max: new Date(this.currentYear, 11, 31) }), fc.date({ min: new Date(this.currentYear, 0, 1), max: new Date(this.currentYear, 11, 31) }), fc.nat({ max: 100000 }), fc.nat({ max: 100000 }) ) .map( ([ label, coverageType, contributions, personRole, startDate, endDate, totalDistributions, qualifiedDistributions ]) => ({ label, coverageType, contributions, personRole, startDate, endDate, totalDistributions, qualifiedDistributions }) ) ira = (): Arbitrary => fc .tuple( words, //payer fc.constantFrom( types.PersonRole.PRIMARY, types.PersonRole.SPOUSE ), fc.nat({ max: 100000 }), // gross distribution fc.nat({ max: 100000 }), // taxable amount fc.boolean(), // taxable amount not determined fc.boolean(), // total distribution fc.nat({ max: 100000 }), // federal income tax withheld fc.constantFrom( types.IraPlanType.IRA, types.IraPlanType.SepIRA, types.IraPlanType.SimpleIRA, types.IraPlanType.RothIRA ), // plan type fc.nat({ max: 100000 }), // contributions fc.nat({ max: 100000 }), // rollover contributions fc.nat({ max: 100000 }), // roth IRA conversion fc.nat({ max: 100000 }), // recharacterized contributions fc.nat({ max: 100000 }), // required minimum distributions fc.nat({ max: 100000 }), // late contributions fc.nat({ max: 100000 }) ) .map( ([ payer, personRole, grossDistribution, taxableAmount, taxableAmountNotDetermined, totalDistribution, federalIncomeTaxWithheld, planType, contributions, rolloverContributions, rothIraConversion, recharacterizedContributions, requiredMinimumDistributions, lateContributions, repayments ]) => ({ payer, personRole, grossDistribution, taxableAmount, taxableAmountNotDetermined, totalDistribution, federalIncomeTaxWithheld, planType, contributions, rolloverContributions, rothIraConversion, recharacterizedContributions, requiredMinimumDistributions, lateContributions, repayments }) ) credit = (): Arbitrary => fc.tuple(fc.nat({ max: 100000 }).map((x) => x / 100)).map(([amount]) => ({ recipient: types.PersonRole.PRIMARY, amount, type: types.CreditType.AdvanceChildTaxCredit })) information = (): Arbitrary => fc .tuple( fc.array(f1099), fc.array(w2), fc.array(this.property()), fc.array(estTax), fc.array(f1098e), fc.array(f3921), fc.array(scheduleK1Form1065), itemizedDeductions, refund, this.taxPayer(), questions, state, fc.array(this.healthSavingsAccount()), fc.array(this.credit(), { maxLength: 2 }), fc.array(this.ira()) ) .map( ([ f1099s, w2s, realEstate, estimatedTaxes, f1098es, f3921s, scheduleK1Form1065s, itemizedDeductions, refund, taxPayer, questions, state, healthSavingsAccounts, credits, individualRetirementArrangements ]) => ({ f1099s, w2s, realEstate, estimatedTaxes, f1098es, f3921s, scheduleK1Form1065s, itemizedDeductions, refund, taxPayer, questions, stateResidencies: [{ state }], healthSavingsAccounts, credits, individualRetirementArrangements }) ) } export const forYear = (year: number): Arbitraries => new Arbitraries(year) export const asset: Arbitrary = fc .tuple( fc.string(), fc.constantFrom('Security', 'Real Estate'), fc.date(), fc.nat({ max: 100000 }), fc.nat({ max: 100 }), fc.nat({ max: 100 }) ) .map(([name, positionType, openDate, openPrice, openFee, quantity]) => ({ name, positionType, openPrice, openDate, openFee, quantity })) export const yearsTaxesState: Arbitrary = fc .tuple(forYear(2021).information(), fc.array(asset)) .map(([Y2021, assets]) => ({ ...blankYearTaxesState, Y2021, assets, activeYear: 'Y2021' })) ================================================ FILE: src/core/tests/validation.test.ts ================================================ import * as arbitraries from './arbitraries' import * as fc from 'fast-check' import { Address, Dependent, Information, PrimaryPerson } from '../data' import log from '../log' import * as validators from '../data/validate' import { dateToStringPerson, infoToStringInfo } from 'ustaxes/redux/data' /* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ beforeAll(() => { jest.spyOn(console, 'warn').mockImplementation(() => {}) jest.spyOn(console, 'error').mockImplementation(() => {}) log.setDefaultLevel(log.levels.SILENT) }) const primaryPerson: fc.Arbitrary> = arbitraries.primaryPerson.map((p) => dateToStringPerson(p)) const information: fc.Arbitrary> = arbitraries .forYear(2020) .information() .map((i) => infoToStringInfo(i)) const dependent: fc.Arbitrary> = arbitraries .forYear(2020) .dependent() .map((p) => ({ ...p, dateOfBirth: p.dateOfBirth.toISOString() })) describe('validation', () => { it('should validate some data', () => { fc.assert( fc.property(primaryPerson, (data) => { expect(validators.primaryPerson(data)).toEqual(true) }) ) }) it('checktype should throw', () => { fc.assert( fc.property(primaryPerson, (data) => { expect(() => validators.checkType( { ...data, address: '123 hi street' as unknown as Address }, validators.primaryPerson ) ).toThrow() }) ) }) it('checks dependent', () => { fc.assert( fc.property(dependent, (data) => { expect(validators.checkType(data, validators.dependent)).toEqual(data) }) ) }) it('checkType should not modify correct data', () => { fc.assert( fc.property(information, (info) => { expect(validators.checkType(info, validators.information)).toEqual(info) }) ) }) }) ================================================ FILE: src/core/util.ts ================================================ /** * Given a typescript enum, use this to get an array of the keys * to the enum * @param a Enumerator name * @returns tyepsafe array of keys */ import _ from 'lodash' import { DeepMap, DeepPartial } from 'react-hook-form' export const enumKeys = (a: A): Array => Object.keys(a).filter((k) => isNaN(Number(k))) as Array export const linear = (m: number, b: number) => (x: number): number => b + m * x // Lower bound, and function to apply above that bound. interface Piece { lowerBound: number f: (x: number) => number } export type Piecewise = Piece[] export const evaluatePiecewise = (f: Piecewise, x: number): number => { // Select the function segment to evaulate. // The function segment is the one before the segment with the lower bound above x. const selection: number = (() => { const idx = f.findIndex(({ lowerBound }) => lowerBound > x) if (idx < 0) { return f.length - 1 } return idx - 1 })() return f[selection].f(x) } export const isLeapYear = (year: number): boolean => (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 export const daysInYear = (year: number): number => isLeapYear(year) ? 366 : 365 export const ifNegative = ( n: number, orElse: A | number = 0 ): A | number => (n < 0 ? n : orElse) export const ifPositive = ( n: number, orElse: A | number = 0 ): A | number => (n > 0 ? n : orElse) // idea from https://github.com/gcanti/fp-ts/blob/3e2af038982cb4090ccc8c2912e4b22f907bdaea/src/Either.ts export interface Left { readonly _tag: 'left' left: E } export interface Right { readonly _tag: 'right' right: A } export type Either = Left | Right export const left = (left: E): Either => ({ _tag: 'left', left }) export const right = (right: A): Either => ({ _tag: 'right', right }) export const isLeft = (e: Either): e is Left => e._tag === 'left' export const isRight = (e: Either): e is Right => e._tag === 'right' // FP style Either type that also handles promises. export class EitherMethods { e: Either constructor(e: Either) { this.e = e } map = (f: (a: A) => B): EitherMethods => isLeft(this.e) ? pureLeft(this.e.left) : pure(f(this.e.right)) ap = (fab: Either B>): EitherMethods => isLeft(fab) ? new EitherMethods(left(fab.left)) : this.map(fab.right) chain = (f: (a: A) => Either): EitherMethods => new EitherMethods(isLeft(this.e) ? left(this.e.left) : f(this.e.right)) mapLeft = (f: (err: E) => E2): EitherMethods => isLeft(this.e) ? pureLeft(f(this.e.left)) : pure(this.e.right) fold = (f: (e: E) => B, g: (a: A) => C): B | C => isLeft(this.e) ? f(this.e.left) : g(this.e.right) orThrow = (): A => this.fold( (e) => { throw e }, (a) => a ) handle = (f: (e: E) => B): B | undefined => this.fold(f, () => undefined) mapAsync = async ( f: (a: A) => Promise ): Promise> => { if (isLeft(this.e)) { return Promise.resolve(pureLeft(this.e.left)) } else { return pure(await f(this.e.right)) } } value = (): Either => this.e } export const pureLeft = (e: E): EitherMethods => new EitherMethods(left(e)) export const pure = (a: A): EitherMethods => new EitherMethods(right(a)) export const run = (e: Either): EitherMethods => new EitherMethods(e) export const runAsync = ( e: Promise> ): Promise> => e.then(run) // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access export const isDesktop = (): boolean => (window as any).__TAURI__ !== undefined export const isWeb = (): boolean => !isDesktop() // Decimal precision const fixDecimals = (n: number) => (x: number): number => { const mul = Math.pow(10, n) return Math.round(x * mul) / mul } export const fix2 = fixDecimals(2) export const fix0 = (n: number): number => Math.round(n) export const fix5 = fixDecimals(5) export const fix10 = fixDecimals(10) export const parseFormNumber = (x: string | undefined): number | undefined => { if (x !== undefined && x.length > 0) { try { return parseFloat(x) } catch (e) { return undefined } } return undefined } export const parseFormNumberOrThrow = (x: string | undefined): number => { const res = parseFormNumber(x) if (res === undefined) { throw new Error(`${x ?? 'undefined'} does not parse to number`) } return res } export const numberOfDaysBetween = (d1: Date, d2: Date): number => { const [start, end] = [d1, d2].map((d) => // Ignore time part if it exists. new Date(d.toISOString().slice(0, 10)).getTime() ) return Math.abs(end - start) / 1000 / 60 / 60 / 24 } export const nextMultipleOf = (mul: number) => (value: number): number => { const v = Math.round(value) // Just return the highest possible value divisible by mul // if we're above this number (~9E15) // Above that mod cannot be expected to return correct results if (v > Number.MAX_SAFE_INTEGER - mul) { return Number.MAX_SAFE_INTEGER - (Number.MAX_SAFE_INTEGER % mul) } return Math.ceil(v / mul) * mul } export const nextMultipleOf1000 = nextMultipleOf(1000) /** * https://github.com/typescript-eslint/typescript-eslint/issues/4619#issuecomment-1055653098 * * Mark a promise as intentionally floating, not awaited. * * In react, we often want to do something asynchronous as a result * of a user action, such as when there is an onClick to handle. onClick * expects a function that returns void. But in these asynchronous cases * we have a promise with no one around to await it. So explicitly mark * this use as acceptable by wrapping the handler in this function. */ export const intentionallyFloat = ( fn: (...args: A) => Promise ): ((...args: A) => void) => { return (...args) => { void fn(...args) } } export const getNestedValue = ( obj: DeepMap, E>, path: string, defaultValue: V ): E | V => _.get(obj, path, defaultValue) as E | V ================================================ FILE: src/customTypes/persistStore.d.ts ================================================ /* eslint-disable @typescript-eslint/no-explicit-any */ /** * Provides type override for persistStore so that * custom state and action types can be easily used. * This is taken from a PR that has not been merged, * since the redux-persist project appears to be no * longer maintained since Sept 2019 * * https://github.com/rt2zz/redux-persist/pull/1085 */ declare module 'redux-persist/es/persistStore' { import { Store, Action, AnyAction } from 'redux' import { PersistorOptions, Persistor } from 'redux-persist/es/types' /** * @desc Creates a persistor for a given store. * @param store store to be persisted (or match an existent storage) * @param persistorOptions enhancers of the persistor * @param callback bootstrap callback of sort. */ // tslint:disable-next-line: strict-export-declare-modifiers export default function persistStore< S = any, A extends Action = AnyAction >( store: Store, persistorOptions?: PersistorOptions | null, callback?: () => any ): Persistor } declare module 'redux-persist/lib/persistStore' { // export * from 'redux-persist/es/persistStore' export { default } from 'redux-persist/es/persistStore' } ================================================ FILE: src/data/csvImport.ts ================================================ import { parse, ParseError } from 'papaparse' import { Either, left, right, run } from 'ustaxes/core/util' export type DateFormat = | 'YYYY-MM-DD' | 'DD-MM-YYYY' | 'MM-DD-YYYY' | 'DD-MM-YY' | 'MM-DD-YY' | 'YY-MM-DD' export interface CsvConfig { delimiter: string quote: string escape: string fields: Map dateFormat: string } export type Parsed = Either const parseEither = (contents: string): Parsed => { const res = parse(contents, { skipEmptyLines: true, delimiter: ',' }) if (res.errors.length > 0) { return left(res.errors) } return right(res.data) } export const preflightCsvAll = (contents: string): Parsed => parseEither(contents) export const preflightCsvAllOrThrow = (contents: string): string[][] => run(parseEither(contents)).orThrow() export const preflightCsv = (contents: string, sample = 5): Parsed => run(preflightCsvAll(contents)) .map((data) => data.slice(0, sample)) .value() export const preflightCsvOrThrow = (contents: string, sample = 5): string[][] => run(preflightCsv(contents, sample)).orThrow() export const parseCsv = ( contents: string, parseRow: (r: string[], rowNum: number) => A[] ): Parsed => run(preflightCsvAll(contents)) .map((data) => data.flatMap((row, i) => parseRow(row, i))) .value() export const parseCsvOrThrow = ( contents: string, parseRow: (r: string[], rowNum: number) => A[] ): A[] => run(parseCsv(contents, parseRow)).orThrow() ================================================ FILE: src/data/transactions.ts ================================================ import { Either, left, pure, right } from 'ustaxes/core/util' export interface Security { name: string } export type Side = 'BUY' | 'SELL' // A transaction represents a single trade in a security. export interface Transaction { security: Security date: string side: Side price: number fee?: number quantity: number } // A position represents a lot of a security. A lot may be split // by a transaction into a new lot. export interface Position { security: Security quantity: number price: number openDate: string openFee: number closeFee?: number closeDate?: string closePrice?: number } export interface Portfolio { positions: Position[] } const openPosition = (transaction: Transaction): Position => { const { security, date, side, price, quantity, fee } = transaction return { security, quantity: side === 'BUY' ? quantity : 0, price, openDate: date, openFee: fee ?? 0 } } // Apply a transaction to a portfolio. In the case of a buy, the portfolio simply gains a // new lot. In the case of a sell, lots are consumed in first-in-first-out-order. Any left // over quantity in a lot is added to a new position. Note positions are never removed // from the portfolio. The closeDate field is set to indicate the position is closed. // TODO: Handle wash sales, flag adjustments caused. export const processTransaction = ( portfolio: Portfolio, transaction: Transaction ): Portfolio => { const { positions } = portfolio if (transaction.side === 'BUY') { // In the case of a buy, we just open a new position. Simplest case. return { ...portfolio, positions: [...positions, openPosition(transaction)] } } else { // For a sale, we have to handle a few cases. // 1. The security is not in the portfolio. This is a short position. For now, this is an exception // 2. The security is in the portfolio. // a. We have to consume the quantity from the first matching lot in the portfolio. // b, If there is a remaining quantity, we have to open a new position. let remainingQuantity = transaction.quantity const newPositions = positions.flatMap((position) => { if ( position.closeDate !== undefined || transaction.security.name !== position.security.name || remainingQuantity <= 0 ) { return [position] } if (remainingQuantity >= position.quantity) { // Entire lot is consumed by the sale remainingQuantity -= position.quantity return [ { ...position, closePrice: transaction.price, closeDate: transaction.date, closeFee: transaction.fee } ] } else { // Lot is only partially consumed. We have to open a new position const closedQuantity = remainingQuantity const newQuantity = position.quantity - closedQuantity remainingQuantity = 0 const closeRatio = closedQuantity / position.quantity return [ { ...position, // Since we're tracking the fee at opening and closing of // the position, we have to apportion the fee to split lots // so that the cost basis and proceeds for each will calculate // correctly. openFee: position.openFee * closeRatio, closeFee: (position.closeFee ?? 0) * closeRatio, closeDate: transaction.date, closePrice: transaction.price, quantity: closedQuantity }, { ...position, openFee: position.openFee * (1 - closeRatio), closeFee: (position.closeFee ?? 0) * (1 - closeRatio), quantity: newQuantity } ] } }) if (remainingQuantity > 0) { // This is an error. We hit a sale which tries to consume more // of a security than we have. console.error('Transaction list failed for transaction') console.error(transaction) console.error(newPositions) console.error( `${remainingQuantity} remaining for security ${transaction.security.name}` ) throw new Error('Transaction list failed') } return { ...portfolio, positions: newPositions } } } export interface TransactionError { messages: string[] previousPortfolio?: Portfolio errorTransaction?: Transaction errorIndex: number } export const processTransactions = ( initialPortfolio: Portfolio, transactions: Transaction[] ): Either => transactions .reduce( (portfolio, transaction, i) => portfolio.chain((p: Portfolio) => { try { return right(processTransaction(p, transaction)) } catch (e) { return left({ messages: [(e as Error).message], previousPortfolio: p, errorTransaction: transaction, errorIndex: i }) } }), pure(initialPortfolio) ) .value() ================================================ FILE: src/data/urls.ts ================================================ const Urls = { usTaxes: { start: '/start' }, taxPayer: { root: '/taxpayer', info: '/info', spouseAndDependent: '/spouseanddependent' }, refund: '/refundinfo', questions: '/questions', income: { w2s: '/income/w2jobinfo', f1099s: '/income/f1099s', realEstate: '/income/realestate', otherInvestments: '/income/otherinvestments', stockOptions: '/income/stockoptions', partnershipIncome: '/income/partnershipincome' }, payments: { estimatedTaxes: '/payments/estimatedtaxes' }, savingsAccounts: { healthSavingsAccounts: '/savingsaccounts/hsa', ira: '/savingsaccounts/ira' }, deductions: { f1098es: '/deductions/studentloaninterest', itemized: '/deductions/itemized' }, credits: { main: '/credits', eic: '/credits/eic' }, createPdf: '/createpdf', settings: '/settings', help: '/help', Y2021: { credits: `/Y2021/credits` }, default: '' } Urls.default = Urls.usTaxes.start export default Urls ================================================ FILE: src/forms/F1040Base.ts ================================================ import { FilingStatus, Income1099B, Income1099Div, Income1099Int, Income1099R, Income1099SSA, Income1099Type, Information, Person, PrimaryPerson, ScheduleK1Form1065, Supported1099, TaxPayer } from 'ustaxes/core/data' import Form from 'ustaxes/core/irsForms/Form' import { Either, left, right } from 'ustaxes/core/util' import { F1040Error } from './errors' export type ValidatedTaxpayer = TaxPayer & { filingStatus: FilingStatus primaryPerson: PrimaryPerson } export interface ValidatedInformation extends Information { taxPayer: ValidatedTaxpayer } const isValidTaxpayer = (tp: TaxPayer): tp is ValidatedTaxpayer => tp.filingStatus !== undefined && tp.primaryPerson !== undefined export const isValidInformation = ( info: Information ): info is ValidatedInformation => isValidTaxpayer(info.taxPayer) export const validate = ( info: Information ): Either => { const result: F1040Error[] = [] if (info.taxPayer.filingStatus === undefined) { result.push(F1040Error.filingStatusUndefined) } const fs = info.taxPayer.filingStatus const numDependents = info.taxPayer.dependents.length const hasSpouse = info.taxPayer.spouse !== undefined const hasDependents = numDependents > 0 if ( fs === undefined || ([FilingStatus.S, FilingStatus.HOH].some((x) => x === fs) && hasSpouse) || (fs === FilingStatus.HOH && !hasDependents) ) { result.push(F1040Error.filingStatusRequirementsNotMet) } if (result.length > 0) { return left(result) } if (!isValidInformation(info)) { result.push(F1040Error.incompletePrimaryTaxpayer) } if (result.length > 0) { return left(result) } // Calling this a second time for typechecking. if (isValidInformation(info)) { return right(info) } // Should not get here. Above logic should be exhaustive throw new Error('Invalid information.') } export default abstract class F1040Base extends Form { info: ValidatedInformation constructor(info: ValidatedInformation) { super() this.info = info } namesString = (): string => { const ps: Person[] = [ this.info.taxPayer.primaryPerson, this.info.taxPayer.spouse ] .filter((p: Person | undefined) => p !== undefined) .map((p: Person | undefined) => p as Person) return ps.map((p: Person) => `${p.firstName} ${p.lastName}`).join(', ') } k1sWithInterest = (): ScheduleK1Form1065[] => this.info.scheduleK1Form1065s.filter((k1) => k1.interestIncome > 0) f1099sByType = (ft: Income1099Type): Supported1099[] => this.info.f1099s.filter((f1099) => f1099.type === ft) f1099Ints = (): Income1099Int[] => this.f1099sByType(Income1099Type.INT) as Income1099Int[] f1099Divs = (): Income1099Div[] => this.f1099sByType(Income1099Type.DIV) as Income1099Div[] f1099Bs = (): Income1099B[] => this.f1099sByType(Income1099Type.B) as Income1099B[] f1099rs = (): Income1099R[] => this.f1099sByType(Income1099Type.R) as Income1099R[] f1099ssas = (): Income1099SSA[] => this.f1099sByType(Income1099Type.SSA) as Income1099SSA[] fullName = (person: Person): string => `${person.firstName} ${person.lastName}` primaryFullName = (): string | undefined => this.fullName(this.info.taxPayer.primaryPerson) spouseFullName = (): string | undefined => this.info.taxPayer.spouse !== undefined ? this.fullName(this.info.taxPayer.spouse) : undefined } ================================================ FILE: src/forms/StateForms.ts ================================================ import { State } from 'ustaxes/core/data' import StateForm from 'ustaxes/core/stateForms/Form' import { Either, left, right } from 'ustaxes/core/util' import F1040Base from './F1040Base' export enum StateFormError { unsupportedTaxYear = 'Tax year not supported', NoResidency = 'No residency defined', StateFormsNotAvailable = 'No state forms available', NoFilingRequirement = 'No filing requirement' } type StateForms = { [K in State]?: (f1040: F) => StateForm } export const createStateReturn = (noFilingStates: State[], stateForms: StateForms) => (f1040: F): Either => { if (f1040.info.stateResidencies.length < 1) { return left([StateFormError.NoResidency]) } else if (noFilingStates.includes(f1040.info.stateResidencies[0].state)) { return left([StateFormError.NoFilingRequirement]) } const residency = f1040.info.stateResidencies[0] const form = stateForms[residency.state]?.call(undefined, f1040) if (form !== undefined) { return right( [form, ...form.attachments()].sort((a, b) => a.formOrder - b.formOrder) ) } return left([StateFormError.StateFormsNotAvailable]) } ================================================ FILE: src/forms/Y2020/data/federal.ts ================================================ import { FilingStatus } from 'ustaxes/core/data' import { linear, Piecewise } from 'ustaxes/core/util' export const CURRENT_YEAR = 2020 interface TaggedAmount { name: string amount: number } interface Brackets { brackets: number[] } interface Deductions { deductions: TaggedAmount[] exemptions: TaggedAmount[] } interface Rates { rates: number[] } interface FederalBrackets { ordinary: Rates & { status: { [key in FilingStatus]: Brackets & Deductions } } longTermCapGains: Rates & { status: { [key in FilingStatus]: Brackets } } } const federalBrackets: FederalBrackets = { ordinary: { rates: [10, 12, 22, 24, 32, 35, 37], status: { [FilingStatus.S]: { brackets: [9875, 40125, 85525, 163300, 207350, 510300], deductions: [ { name: 'Standard Deduction (Single)', amount: 12400 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] }, [FilingStatus.MFJ]: { brackets: [19750, 80250, 171050, 326600, 414700, 622050], deductions: [ { name: 'Standard Deduction (Married)', amount: 24800 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] }, [FilingStatus.W]: { brackets: [19750, 80250, 171050, 326600, 414700, 622050], deductions: [ { name: 'Standard Deduction (Married)', amount: 24800 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] }, [FilingStatus.MFS]: { brackets: [9875, 40125, 85525, 163300, 207350, 510300], deductions: [ { name: 'Standard Deduction (Married Filing Separately)', amount: 12400 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] }, [FilingStatus.HOH]: { brackets: [19750, 80250, 171050, 326600, 414700, 622050], deductions: [ { name: 'Standard Deduction (Head of Household)', amount: 18650 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] } } }, longTermCapGains: { rates: [0, 15, 20], status: { [FilingStatus.S]: { brackets: [40000, 441450] }, [FilingStatus.MFJ]: { brackets: [80000, 496600] }, [FilingStatus.W]: { brackets: [80000, 496600] }, [FilingStatus.MFS]: { brackets: [40000, 248300] }, [FilingStatus.HOH]: { brackets: [53600, 469050] } } } } export const fica = { maxSSTax: 8537.4, maxIncomeSSTaxApplies: 137700, regularMedicareTaxRate: 1.45 / 100, additionalMedicareTaxRate: 0.9 / 100, additionalMedicareTaxThreshold: (filingStatus: FilingStatus): number => { switch (filingStatus) { case FilingStatus.MFJ: { return 250000 } case FilingStatus.MFS: { return 125000 } default: { return 200000 // Single, Head of Household, Windower } } } } // Net Investment Income Tax calculated on form 8960 export const netInvestmentIncomeTax = { taxRate: 0.038, // 3.8% taxThreshold: (filingStatus: FilingStatus): number => { switch (filingStatus) { case FilingStatus.MFJ: { return 250000 } case FilingStatus.W: { return 250000 } case FilingStatus.MFS: { return 125000 } default: { return 200000 // Single, Head of Household } } } } export const healthSavingsAccounts = { contributionLimit: { 'self-only': 3550, family: 7100 } } // line 11 caps based on step one in instructions const line11Caps = [15820, 41756, 47440, 50954] const line11MfjCaps = [21710, 47646, 53330, 56844] type Point = [number, number] // Provided a list of points, create a piecewise function // that makes linear segments through the list of points. const toPieceWise = (points: Point[]): Piecewise => points .slice(0, points.length - 1) .map((point, idx) => [point, points[idx + 1]]) .map(([[x1, y1], [x2, y2]]) => ({ // starting point slope intercept lowerBound: x1, f: linear((y2 - y1) / (x2 - x1), y1 - (x1 * (y2 - y1)) / (x2 - x1)) })) // These points are taken directly from IRS publication // IRS Rev. Proc. 2019-44 for tax year 2020 // https://www.irs.gov/pub/irs-drop/rp-19-44.pdf const unmarriedFormulas: Piecewise[] = (() => { const points: Point[][] = [ [ [0, 0], [7030, 538], [8790, 3584], [15820, 0] ], // 0 [ [0, 0], [10540, 3584], [19330, 3584], [41756, 0] ], // 1 [ [0, 0], [14800, 5920], [19330, 5920], [47440, 0] ], // 2 [ [0, 0], [14800, 6660], [19330, 6660], [50954, 0] ] // 3 or more ] return points.map((ps: Point[]) => toPieceWise(ps)) })() const marriedFormulas: Piecewise[] = (() => { const points: Point[][] = [ [ [0, 0], [7030, 538], [14680, 3584], [21710, 0] ], // 0 [ [0, 0], [10540, 3584], [25220, 3584], [47646, 0] ], // 1 [ [0, 0], [14800, 5920], [25220, 5920], [53330, 0] ], // 2 [ [0, 0], [14800, 6660], [25220, 6660], [56844, 0] ] // 3 or more ] return points.map((ps) => toPieceWise(ps)) })() interface EICDef { caps: { [k in FilingStatus]: number[] | undefined } maxInvestmentIncome: number formulas: { [k in FilingStatus]: Piecewise[] | undefined } } export const QualifyingDependents = { childMaxAge: 17, qualifyingDependentMaxAge: 19, qualifyingStudentMaxAge: 24 } export const EIC: EICDef = { // credit caps for number of children (0, 1, 2, 3 or more): // Step 1 caps: { [FilingStatus.S]: line11Caps, [FilingStatus.W]: line11Caps, [FilingStatus.HOH]: line11Caps, [FilingStatus.MFS]: undefined, [FilingStatus.MFJ]: line11MfjCaps }, maxInvestmentIncome: 3650, formulas: { [FilingStatus.S]: unmarriedFormulas, [FilingStatus.W]: unmarriedFormulas, [FilingStatus.HOH]: unmarriedFormulas, [FilingStatus.MFS]: undefined, [FilingStatus.MFJ]: marriedFormulas } } export default federalBrackets // Constants used in the social security benefits worksheet interface SocialSecurityBenefitsDef { caps: { [k in FilingStatus]: { l8: number; l10: number } } } export const SSBenefits: SocialSecurityBenefitsDef = { caps: { [FilingStatus.S]: { l8: 25000, l10: 9000 }, [FilingStatus.W]: { l8: 25000, l10: 9000 }, [FilingStatus.HOH]: { l8: 25000, l10: 9000 }, [FilingStatus.MFS]: { l8: 25000, l10: 9000 }, [FilingStatus.MFJ]: { l8: 32000, l10: 12000 } } } ================================================ FILE: src/forms/Y2020/irsForms/F1040.ts ================================================ import { AccountType, Dependent, FilingStatus, IncomeW2, PersonRole, PlanType1099, Asset } from 'ustaxes/core/data' import federalBrackets, { CURRENT_YEAR } from '../data/federal' import F1040V from './F1040v' import F2441 from './F2441' import F2555 from './F2555' import F4136 from './F4136' import F4563 from './F4563' import F4797 from './F4797' import F4952 from './F4952' import F4972 from './F4972' import F5695 from './F5695' import F8814 from './F8814' import F8863 from './F8863' import F8888 from './F8888' import F8889 from './F8889' import F8910 from './F8910' import F8936 from './F8936' import F8959 from './F8959' import F8960 from './F8960' import F8962 from './F8962' import F8995 from './F8995' import F8995A from './F8995A' import Schedule1 from './Schedule1' import Schedule2 from './Schedule2' import Schedule3 from './Schedule3' import Schedule8812 from './Schedule8812' import ScheduleA from './ScheduleA' import ScheduleB from './ScheduleB' import ScheduleC from './ScheduleC' import ScheduleD from './ScheduleD' import ScheduleE from './ScheduleE' import ScheduleEIC from './ScheduleEIC' import ScheduleR from './ScheduleR' import Form, { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import { computeOrdinaryTax } from './TaxTable' import SDQualifiedAndCapGains from './worksheets/SDQualifiedAndCapGains' import ChildTaxCreditWorksheet from './worksheets/ChildTaxCreditWorksheet' import SocialSecurityBenefitsWorksheet from './worksheets/SocialSecurityBenefits' import StudentLoanInterestWorksheet from './worksheets/StudentLoanInterestWorksheet' import _ from 'lodash' import F8949 from './F8949' import F4137 from './F4137' import F8919 from './F8919' import F8582 from './F8582' import { Field } from 'ustaxes/core/pdfFiller' import Form8853 from './F8853' import F1040Base, { ValidatedInformation } from 'ustaxes/forms/F1040Base' import F1040Attachment from './F1040Attachment' export default class F1040 extends F1040Base { tag: FormTag = 'f1040' sequenceIndex = 0 assets: Asset[] schedule1: Schedule1 schedule2: Schedule2 schedule3: Schedule3 scheduleA?: ScheduleA scheduleB: ScheduleB scheduleC?: ScheduleC scheduleD: ScheduleD scheduleE: ScheduleE scheduleEIC: ScheduleEIC scheduleR?: ScheduleR schedule8812: Schedule8812 f2441?: F2441 f2555?: F2555 f4136?: F4136 f4137?: F4137 f4563?: F4563 f4797?: F4797 f4952?: F4952 f4972?: F4972 f5695?: F5695 f8582?: F8582 f8814?: F8814 f8853?: Form8853 f8863?: F8863 f8888?: F8888 f8889: F8889 f8889Spouse?: F8889 f8910?: F8910 f8919?: F8919 f8936?: F8936 f8949: F8949 _f8949s?: F8949[] f8959: F8959 f8960: F8960 f8962?: F8962 f8995?: F8995 | F8995A studentLoanInterestWorksheet?: StudentLoanInterestWorksheet socialSecurityBenefitsWorksheet?: SocialSecurityBenefitsWorksheet childTaxCreditWorksheet: ChildTaxCreditWorksheet constructor(info: ValidatedInformation, assets: Asset[] = []) { super(info) this.assets = assets this.childTaxCreditWorksheet = new ChildTaxCreditWorksheet(this) this.scheduleB = new ScheduleB(this) this.scheduleD = new ScheduleD(this) this.scheduleE = new ScheduleE(this) this.scheduleEIC = new ScheduleEIC(this) this.schedule1 = new Schedule1(this) this.schedule2 = new Schedule2(this) this.schedule3 = new Schedule3(this) this.schedule8812 = new Schedule8812(this) this.f8889 = new F8889(this, this.info.taxPayer.primaryPerson) if (this.info.taxPayer.spouse !== undefined) { // add in separate form 8889 for the spouse this.f8889Spouse = new F8889(this, this.info.taxPayer.spouse) } this.f8949 = new F8949(this) this.f8959 = new F8959(this) this.f8960 = new F8960(this) if (this.f1099ssas().length > 0) { const ssws = new SocialSecurityBenefitsWorksheet(this.info, this) this.socialSecurityBenefitsWorksheet = ssws } if (this.info.f1098es.length > 0) { this.studentLoanInterestWorksheet = new StudentLoanInterestWorksheet( this, this.info.f1098es ) } this.childTaxCreditWorksheet = new ChildTaxCreditWorksheet(this) } get f8949s(): F8949[] { if (this._f8949s === undefined) { this._f8949s = [this.f8949, ...this.f8949.copies()] } return this._f8949s } toString = (): string => ` Form 1040 generated from information: Information: ${JSON.stringify(this.info)} ` schedules = (): Form[] => { const res1: (F1040Attachment | undefined)[] = [ this.scheduleA, this.scheduleB, this.scheduleD, this.scheduleE, this.scheduleR, this.scheduleEIC, this.schedule8812, this.f4797, this.f4952, this.f4972, this.f5695, this.f8814, this.f8888, this.f8889, ...(this.f8889Spouse === undefined ? [] : [this.f8889Spouse]), this.f8910, this.f8936, this.f8949, this.f8959, this.f8960, this.f8995, this.schedule1, this.schedule2, this.schedule3 ] const res = _.compact(res1) .filter((f) => f.isNeeded()) .flatMap((f) => [f, ...f.copies()]) // Attach payment voucher to front if there is a payment due if (this.l37() > 0) { res.push(new F1040V(this)) } return [this, ...res].sort((a, b) => a.sequenceIndex - b.sequenceIndex) } bornBeforeDate = (): boolean => this.info.taxPayer.primaryPerson.dateOfBirth < new Date(CURRENT_YEAR - 64, 0, 2) blind = (): boolean => this.info.taxPayer.primaryPerson.isBlind spouseBeforeDate = (): boolean => (this.info.taxPayer.spouse?.dateOfBirth ?? new Date()) < new Date(CURRENT_YEAR - 64, 0, 2) spouseBlind = (): boolean => this.info.taxPayer.spouse?.isBlind ?? false validW2s = (): IncomeW2[] => { if (this.info.taxPayer.filingStatus === FilingStatus.MFS) { return this.info.w2s.filter((w2) => w2.personRole === PersonRole.PRIMARY) } return this.info.w2s } wages = (): number => this.validW2s().reduce((res, w2) => res + w2.income, 0) medicareWages = (): number => this.validW2s().reduce((res, w2) => res + w2.medicareIncome, 0) occupation = (r: PersonRole): string | undefined => this.info.w2s.find((w2) => w2.personRole === r && w2.occupation !== '') ?.occupation standardDeduction = (): number | undefined => { const filingStatus = this.info.taxPayer.filingStatus if ( this.info.taxPayer.primaryPerson.isTaxpayerDependent || (this.info.taxPayer.spouse?.isTaxpayerDependent ?? false) ) { return Math.min( federalBrackets.ordinary.status[filingStatus].deductions[0].amount, this.wages() > 750 ? this.wages() + 350 : 1100 ) } return federalBrackets.ordinary.status[filingStatus].deductions[0].amount } totalQualifiedDividends = (): number => this.f1099Divs().reduce((sum, f) => sum + f.form.qualifiedDividends, 0) totalGrossDistributionsFromIra = (): number => this.info.individualRetirementArrangements.reduce( (res, i) => res + i.grossDistribution, 0 ) totalTaxableFromIra = (): number => this.info.individualRetirementArrangements.reduce( (r, i) => r + i.taxableAmount, 0 ) totalGrossDistributionsFrom1099R = (planType: PlanType1099): number => this.f1099rs() .filter((element) => element.form.planType == planType) .reduce((res, f1099) => res + f1099.form.grossDistribution, 0) totalTaxableFrom1099R = (planType: PlanType1099): number => this.f1099rs() .filter((element) => element.form.planType == planType) .reduce((res, f1099) => res + f1099.form.taxableAmount, 0) l1 = (): number => this.wages() l2a = (): number | undefined => this.scheduleB.l3() l2b = (): number | undefined => this.scheduleB.to1040l2b() l3a = (): number | undefined => this.totalQualifiedDividends() l3b = (): number | undefined => this.scheduleB.to1040l3b() // This is the value of box 1 in 1099-R forms coming from IRAs l4a = (): number | undefined => this.totalGrossDistributionsFromIra() // This should be the value of box 2a in 1099-R coming from IRAs l4b = (): number | undefined => this.totalTaxableFromIra() // This is the value of box 1 in 1099-R forms coming from pensions/annuities l5a = (): number | undefined => this.totalGrossDistributionsFrom1099R(PlanType1099.Pension) // this is the value of box 2a in 1099-R forms coming from pensions/annuities l5b = (): number | undefined => this.totalTaxableFrom1099R(PlanType1099.Pension) // The sum of box 5 from SSA-1099 l6a = (): number | undefined => this.socialSecurityBenefitsWorksheet?.l1() // calculation of the taxable amount of line 6a based on other income l6b = (): number | undefined => this.socialSecurityBenefitsWorksheet?.taxableAmount() l7 = (): number | undefined => this.scheduleD.to1040() l8 = (): number | undefined => this.schedule1.l9() l9 = (): number => sumFields([ this.l1(), this.l2b(), this.l3b(), this.l4b(), this.l5b(), this.l6b(), this.l7(), this.l8() ]) l10a = (): number | undefined => this.schedule1.l22() l10b = (): number | undefined => undefined l10c = (): number => sumFields([this.l10a(), this.l10b()]) l11 = (): number => Math.max(0, this.l9() - this.l10c()) l12 = (): number | undefined => { if (this.scheduleA !== undefined) { return this.scheduleA.deductions() } return this.standardDeduction() } l13 = (): number | undefined => this.f8995?.deductions() l14 = (): number => sumFields([this.l12(), this.l13()]) l15 = (): number => Math.max(0, this.l11() - this.l14()) computeTax = (): number | undefined => { if ( this.scheduleD.computeTaxOnQDWorksheet() || this.totalQualifiedDividends() > 0 ) { const wksht = new SDQualifiedAndCapGains(this) return wksht.tax() } return computeOrdinaryTax(this.info.taxPayer.filingStatus, this.l15()) } l16 = (): number | undefined => sumFields([this.f8814?.tax(), this.f4972?.tax(), this.computeTax()]) l17 = (): number | undefined => this.schedule2.l3() l18 = (): number => sumFields([this.l16(), this.l17()]) // TODO l19 = (): number | undefined => this.childTaxCreditWorksheet.l12() l20 = (): number | undefined => this.schedule3.l7() l21 = (): number => sumFields([this.l19(), this.l20()]) l22 = (): number => Math.max(0, this.l18() - this.l21()) l23 = (): number | undefined => this.schedule2.l10() l24 = (): number => sumFields([this.l22(), this.l23()]) l25a = (): number => this.validW2s().reduce((res, w2) => res + w2.fedWithholding, 0) // tax withheld from 1099s l25b = (): number => this.f1099rs().reduce( (res, f1099) => res + f1099.form.federalIncomeTaxWithheld, 0 ) + this.f1099ssas().reduce( (res, f1099) => res + f1099.form.federalIncomeTaxWithheld, 0 ) // TODO: form(s) W-2G box 4, schedule K-1, form 1042-S, form 8805, form 8288-A l25c = (): number | undefined => this.f8959.l24() l25d = (): number => sumFields([this.l25a(), this.l25b(), this.l25c()]) l26 = (): number => this.info.estimatedTaxes.reduce((res, et) => res + et.payment, 0) l27 = (): number | undefined => this.scheduleEIC.credit() l28 = (): number | undefined => this.schedule8812.l15() l29 = (): number | undefined => this.f8863?.l8() // TODO: recovery rebate credit? l30 = (): number | undefined => undefined l31 = (): number | undefined => this.schedule3.l13() l32 = (): number => sumFields([this.l27(), this.l28(), this.l29(), this.l30(), this.l31()]) l33 = (): number => sumFields([this.l25d(), this.l26(), this.l32()]) l34 = (): number => Math.max(0, this.l33() - this.l24()) // TODO: assuming user wants amount refunded // rather than applied to estimated tax l35a = (): number => this.l34() l36 = (): number => Math.max(0, this.l34() - this.l35a()) l37 = (): number => Math.max(0, this.l24() - this.l33()) // TODO - estimated tax penalty l38 = (): number | undefined => undefined _depField = (idx: number): string | boolean => { const deps: Dependent[] = this.info.taxPayer.dependents // Based on the PDF row we are on, select correct dependent const depIdx = Math.floor(idx / 5) const depFieldIdx = idx % 5 let fieldArr = ['', '', '', false, false] if (depIdx < deps.length) { const dep = deps[depIdx] // Based on the PDF column, select the correct field fieldArr = [ `${dep.firstName} ${dep.lastName}`, dep.ssid, dep.relationship, this.childTaxCreditWorksheet.qualifiesChild(dep), this.childTaxCreditWorksheet.qualifiesOther(dep) ] } return fieldArr[depFieldIdx] } // 1040 allows 4 dependents listed without a supplemental schedule, // so create field mappings for 4x5 grid of fields _depFieldMappings = (): Array => Array.from(Array(20)).map((u, n: number) => this._depField(n)) fields = (): Field[] => [ this.info.taxPayer.filingStatus === FilingStatus.S, this.info.taxPayer.filingStatus === FilingStatus.MFJ, this.info.taxPayer.filingStatus === FilingStatus.MFS, this.info.taxPayer.filingStatus === FilingStatus.HOH, this.info.taxPayer.filingStatus === FilingStatus.W, // TODO: implement non dependent child for HOH and QW this.info.taxPayer.filingStatus === 'MFS' ? this.spouseFullName() : '', this.info.taxPayer.primaryPerson.firstName, this.info.taxPayer.primaryPerson.lastName, this.info.taxPayer.primaryPerson.ssid, this.info.taxPayer.filingStatus === FilingStatus.MFJ ? this.info.taxPayer.spouse?.firstName : '', this.info.taxPayer.filingStatus === FilingStatus.MFJ ? this.info.taxPayer.spouse?.lastName ?? '' : '', this.info.taxPayer.spouse?.ssid, this.info.taxPayer.primaryPerson.address.address, this.info.taxPayer.primaryPerson.address.aptNo, this.info.taxPayer.primaryPerson.address.city, this.info.taxPayer.primaryPerson.address.state, this.info.taxPayer.primaryPerson.address.zip, this.info.taxPayer.primaryPerson.address.foreignCountry, this.info.taxPayer.primaryPerson.address.province, this.info.taxPayer.primaryPerson.address.postalCode, false, // election campaign boxes false, this.info.questions.CRYPTO ?? false, !(this.info.questions.CRYPTO ?? false), this.info.taxPayer.primaryPerson.isTaxpayerDependent, this.info.taxPayer.spouse?.isTaxpayerDependent ?? false, false, // TODO: spouse itemizes separately, this.bornBeforeDate(), this.blind(), this.spouseBeforeDate(), this.spouseBlind(), this.info.taxPayer.dependents.length > 4, ...this._depFieldMappings(), this.l1(), this.l2a(), this.l2b(), this.l3a(), this.l3b(), this.l4a(), this.l4b(), this.l5a(), this.l5b(), this.l6a(), this.l6b(), !this.scheduleD.isNeeded(), this.l7(), this.l8(), this.l9(), this.l10a(), this.l10b(), this.l10c(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.f8814 !== undefined, this.f4972 !== undefined, false, // TODO: other tax form '', // TODO: other tax form this.l16(), this.l17(), this.l18(), this.l19(), this.l20(), this.l21(), this.l22(), this.l23(), this.l24(), this.l25a(), this.l25b(), this.l25c(), this.l25d(), this.l26(), this.l27(), this.l28(), this.l29(), this.l30(), this.l31(), this.l32(), this.l33(), this.l34(), this.f8888 !== undefined, this.l35a(), this.info.refund?.routingNumber, this.info.refund?.accountType === AccountType.checking, this.info.refund?.accountType === AccountType.savings, this.info.refund?.accountNumber, this.l36(), this.l37(), this.l38(), // TODO: 3rd party false, false, '', '', '', this.occupation(PersonRole.PRIMARY), // TODO: pin numbers '', this.occupation(PersonRole.SPOUSE), '', this.info.taxPayer.contactPhoneNumber, this.info.taxPayer.contactEmail, // Paid preparer fields: '', '', false, '', '', '', '' ].map((x) => (x === undefined ? '' : x)) } ================================================ FILE: src/forms/Y2020/irsForms/F1040Attachment.ts ================================================ import Form from 'ustaxes/core/irsForms/Form' import F1040 from './F1040' abstract class F1040Attachment extends Form { f1040: F1040 constructor(f1040: F1040) { super() this.f1040 = f1040 } isNeeded = (): boolean => true copies = (): F1040Attachment[] => [] } export abstract class Worksheet { f1040: F1040 constructor(f1040: F1040) { this.f1040 = f1040 } } export default F1040Attachment ================================================ FILE: src/forms/Y2020/irsForms/F1040v.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Field } from 'ustaxes/core/pdfFiller' export default class F1040V extends F1040Attachment { tag: FormTag = 'f1040v' sequenceIndex = -1 fields = (): Field[] => { const tp = this.f1040.info.taxPayer const taxOwed = this.f1040.l37() const result = [ tp.primaryPerson.ssid, tp.spouse?.ssid, Math.trunc(taxOwed), // dollars Math.round((taxOwed - Math.trunc(taxOwed)) * 100), // cents tp.primaryPerson.firstName, tp.primaryPerson.lastName, tp.spouse?.firstName, tp.spouse?.lastName, tp.primaryPerson.address.address, tp.primaryPerson.address.aptNo, tp.primaryPerson.address.city, tp.primaryPerson.address.state, tp.primaryPerson.address.zip, tp.primaryPerson.address.foreignCountry, tp.primaryPerson.address.province, tp.primaryPerson.address.postalCode ] return result } } ================================================ FILE: src/forms/Y2020/irsForms/F2441.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * TODO: Credit for child and dependent care expenses */ export default class F2441 extends F1040Attachment { tag: FormTag = 'f2441' sequenceIndex = 999 credit = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F2555.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Impacts EIC, 1040 instructions L27 step 1 question 4 */ export default class F2555 extends F1040Attachment { tag: FormTag = 'f2555' sequenceIndex = 34 // TODO - required from 8812 l45 = (): number => 0 // TODO - required from 8812 l50 = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F4136.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * TODO: not implemented * Credit for federal tax on fuels */ export default class F4136 extends F1040Attachment { tag: FormTag = 'f4136' sequenceIndex = 999 credit = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F4137.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' // TODO export default class F4137 extends F1040Attachment { tag = 'f4137' sequenceIndex = 999 l6 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F4563.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Exclusion of income for residents of American Somoa * Impacts 8812, */ export default class F4563 extends F1040Attachment { sequenceIndex = 563 tag: FormTag = 'f4563' // TODO - required from 8812 l15 = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F4797.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Impacts EIC, 1040 instructions L27 step 2 question 3 * Not implemented yet */ export default class F4797 extends F1040Attachment { tag: FormTag = 'f4797' sequenceIndex = 999 // TODO, required from schedule EIC, PUB 596, worksheet 1 l7 = (): number | undefined => undefined l8 = (): number | undefined => undefined l9 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F4952.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Impacts Schedule D, capital gains and taxes worksheet, * Not implemented yet */ export default class F4952 extends F1040Attachment { tag: FormTag = 'f4952' sequenceIndex = 999 l4e = (): number | undefined => undefined l4g = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F4972.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented yet */ export default class F4972 extends F1040Attachment { tag: FormTag = 'f4972' sequenceIndex = 999 tax = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F5695.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented yet */ export default class F5695 extends F1040Attachment { tag: FormTag = 'f5695' sequenceIndex = 999 l30 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F6168.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' /** * Referenced from line 21 of Schedule E */ export default class F6168 extends F1040Attachment { tag = 'f6168' sequenceIndex = 999 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8582.ts ================================================ import F1040Attachment from './F1040Attachment' import { MatrixRow } from './ScheduleE' import { Field } from 'ustaxes/core/pdfFiller' /** * Referenced from line 22 of Schedule E */ export default class F8582 extends F1040Attachment { tag = 'f8582' sequenceIndex = 999 // TODO: 'Deducible rental estate loss after limitation, assuming all allowed' deductibleRealEstateLossAfterLimitation = (): MatrixRow => this.f1040.scheduleE.rentalNet().map((v) => { if (v === undefined || v >= 0) { return undefined } return v }) as MatrixRow fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8814.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Field } from 'ustaxes/core/pdfFiller' export default class F8814 extends F1040Attachment { tag: FormTag = 'f8814' sequenceIndex = 999 // TODO: required from schedule EIC, pub596, worksheet 1 l1b = (): number | undefined => undefined tax = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8853.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' // Not yet implemented export default class F8853 extends F1040Attachment { tag: FormTag = 'f8853' sequenceIndex = 999 l1 = (): number | undefined => undefined l2 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8863.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' // Not yet implemented export default class F8863 extends F1040Attachment { tag: FormTag = 'f8863' sequenceIndex = 999 l8 = (): number | undefined => undefined l19 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8888.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented yet */ export default class F8888 extends F1040Attachment { tag: FormTag = 'f8888' sequenceIndex = 999 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8889.ts ================================================ import { Person, HealthSavingsAccount } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import { CURRENT_YEAR, healthSavingsAccounts } from '../data/federal' import F1040Attachment from './F1040Attachment' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' type ContributionType = 'self-only' | 'family' type PerMonthContributionType = { amount: number[] type: ContributionType[] } export default class F8889 extends F1040Attachment { tag: FormTag = 'f8889' sequenceIndex = 52 // these should only be the HSAs that belong to this person // the person can be either the primary person or the spouse hsas: HealthSavingsAccount[] person: Person calculatedCoverageType: 'self-only' | 'family' perMonthContributions: PerMonthContributionType readonly firstDayOfLastMonth: Date constructor(f1040: F1040, person: Person) { super(f1040) this.person = person // The relevant HSAs are the ones either for this person or any that // have family coverage. this.hsas = this.f1040.info.healthSavingsAccounts .filter((h) => { if (h.personRole == person.role || h.coverageType == 'family') { return true } return false }) .map((h) => { return { ...h, startDate: new Date(h.startDate), endDate: new Date(h.endDate) } }) this.calculatedCoverageType = 'self-only' this.firstDayOfLastMonth = new Date(CURRENT_YEAR, 11, 1) this.perMonthContributions = { amount: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], type: Array(12) } } isNeeded = (): boolean => this.f1040.info.healthSavingsAccounts.some( (h) => h.personRole === this.person.role || h.coverageType === 'family' ) calculatePerMonthLimits = (): void => { for ( let index = 0; index < this.perMonthContributions.amount.length; index++ ) { // for each month check each HSA to see if we are covered. this.hsas.forEach((h) => { const firstDayOfThisMonth = new Date(CURRENT_YEAR, index, 1) if ( h.startDate <= firstDayOfThisMonth && h.endDate >= firstDayOfThisMonth ) { // the coverage limit for that month is based on the type of coverage of the // HSA. If you have both types of HSA coverage for that month, then the family // coverage limit wins out. Since family coverage limit is higher we can just // take the max of the coverage limit for this month. if ( this.perMonthContributions.amount[index] < healthSavingsAccounts.contributionLimit[h.coverageType] ) { this.perMonthContributions.amount[index] = healthSavingsAccounts.contributionLimit[h.coverageType] this.perMonthContributions.type[index] = h.coverageType } } }) } // The calculated coverage type is whichever one was in effect for longer let familyMonthCount = 0 let singleMonthCount = 0 this.perMonthContributions.amount.forEach((m) => { if (m == healthSavingsAccounts.contributionLimit.family) { familyMonthCount += 1 } else if (m == healthSavingsAccounts.contributionLimit['self-only']) { singleMonthCount += 1 } }) if (familyMonthCount >= singleMonthCount) { this.calculatedCoverageType = 'family' } else { this.calculatedCoverageType = 'self-only' } } /* If you are an eligible individual on the first day of the last month of your tax year * (December 1 for most taxpayers), you are considered to be an eligible individual * for the entire year. */ lastMonthRule = (): boolean => { return this.hsas.some((hsa) => hsa.endDate >= this.firstDayOfLastMonth) } /*If, on the first day of the last month of your tax year (December 1 for most taxpayers), you had family coverage, check the "family" box. */ lastMonthCoverage = (): string | undefined => { let coverage = undefined for (const hsa of this.hsas) { if (hsa.endDate >= this.firstDayOfLastMonth) { if (hsa.coverageType == 'family') { coverage = 'family' break } coverage = 'self-only' } } return coverage } fullYearHsa = (): boolean => { return this.hsas.some( (hsa) => hsa.startDate <= new Date(CURRENT_YEAR, 0, 1) && hsa.endDate >= this.firstDayOfLastMonth ) } contributionLimit = (): number => { /*If you were under age 55 at the end of 2020 and, on the first day of every month during 2020, you were, or were considered, an eligible individual with the same coverage, enter $3,550 ($7,100 for family coverage). All others, see the instructions for the amount to enter. */ /*If the last-month rule (see Last-month rule, earlier) applies, you are considered an eligible individual for the entire year. You are treated as having the same HDHP coverage for the entire year as you had on the first day of the last month of your tax year. */ if (this.lastMonthRule()) { // If, on the first day of the last month of your tax year (December 1 for most taxpayers), // you had family coverage, check the "family" box. const lastMonthCoverage = this.lastMonthCoverage() if (lastMonthCoverage !== undefined) { if (lastMonthCoverage === 'family') { this.calculatedCoverageType = 'family' return healthSavingsAccounts.contributionLimit.family } else if (lastMonthCoverage === 'self-only') { this.calculatedCoverageType = 'self-only' return healthSavingsAccounts.contributionLimit['self-only'] } } } /* If you don't have coverage in the last month, then you need to figure out your contribution limit. If you don't have coverage for that month then your contribution limit is 0. So let's initialize our per-month contribution limit based on that. */ this.calculatePerMonthLimits() return Math.round( this.perMonthContributions.amount.reduce((a, b) => a + b) / 12 ) } splitFamilyContributionLimit = (): number | undefined => { /* if you and your spouse each have separate HSAs and had family coverage under an HDHP at any time during 2020*/ /* If you are treated as having family coverage for each month, divide the amount on line 5 equally between you and your spouse, unless you both agree on a different allocation (such as allocating nothing to one spouse). Enter your allocable share on line 6.*/ /* Example. In 2020, you are an eligible individual and have self-only HDHP coverage. In March you marry and as of April 1 you have family HDHP coverage. Neither you nor your spouse qualify for the additional contribution amount. Your spouse has a separate HSA and is an eligible individual from April 1 to December 31, 2020. Because you and your spouse are considered to have family coverage on December 1, your contribution limit is $7,100 (the family coverage maximum). You and your spouse can divide this amount in any allocation to which you agree (such as allocating nothing to one spouse).*/ if (!this.hsas.some((h) => h.coverageType === 'family')) { return this.l5() } if (this.lastMonthCoverage() === 'family') { // TODO: This hard codes the allocation at 50% for each spouse but the // rules say any contribution allowcation is allowed return Math.round(this.l5() / 2) } else { // get the number of months of family coverage const familyMonths: number = this.perMonthContributions.type.filter( (t) => t === 'family' ).length // TODO: This hard codes the allocation at 50% for each spouse but the // rules say any contribution allowcation is allowed const familyContribution: number = (familyMonths * healthSavingsAccounts.contributionLimit['family']) / 12 / 2 // Add this to the contributions of the self-only portion of the year const selfMonths: number = 12 - familyMonths const selfContribution: number = (selfMonths * healthSavingsAccounts.contributionLimit['self-only']) / 12 return familyContribution + selfContribution } } /*Include on line 2 only those amounts you, or others on your behalf, contributed to your HSA in 2020. Also, include those contributions made from January 1, 2021, through April 15, 2021, that were for 2020. Do not include employer contributions (see line 9) or amounts rolled over from another HSA or Archer MSA. See Rollovers, earlier. Also, do not include any qualified HSA funding distributions (see line 10). Contributions to an employee's account through a cafeteria plan are treated as employer contributions and are not included on line 2. */ l2 = (): number => this.hsas.reduce((total, hsa) => hsa.contributions + total, 0) l3 = (): number => this.contributionLimit() l4 = (): number => sumFields([this.f1040.f8853?.l1(), this.f1040.f8853?.l2()]) l5 = (): number => this.l3() - this.l4() l6 = (): number | undefined => this.splitFamilyContributionLimit() // TODO: Additional contirbution amount. Need to know the age of the user l7 = (): number | undefined => undefined l8 = (): number => sumFields([this.l6(), this.l7()]) // Employer contributions are listed in W2 box 12 with code W l9 = (): number => this.f1040.info.w2s .filter((w2) => w2.personRole == this.person.role) .reduce((res, w2) => res + (w2.box12?.W ?? 0), 0) l10 = (): number | undefined => undefined l11 = (): number => sumFields([this.l9(), this.l10()]) l12 = (): number => { const tmp = this.l8() - this.l11() return tmp < 0 ? 0 : tmp } l13 = (): number | undefined => this.l2() < this.l12() ? this.l2() : this.l12() l14a = (): number => this.hsas.reduce((total, hsa) => hsa.totalDistributions + total, 0) l14b = (): number | undefined => undefined l14c = (): number => this.l14a() - (this.l14b() ?? 0) l15 = (): number => this.hsas.reduce((total, hsa) => hsa.qualifiedDistributions + total, 0) l16 = (): number => Math.max(0, this.l14c() - this.l15()) l17a = (): boolean => false // TODO: add in logic for when line 17a is true l17b = (): number | undefined => Math.round(this.l16() * 0.2) l18 = (): number | undefined => undefined l19 = (): number | undefined => undefined l20 = (): number => sumFields([this.l18(), this.l19()]) l21 = (): number => Math.round(this.l20() * 0.1) fields = (): Field[] => [ `${this.person.firstName} ${this.person.lastName}`, this.person.ssid, this.calculatedCoverageType === 'self-only', // line 1: self-only check box this.calculatedCoverageType === 'family', // line 1: family checkbox this.l2(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14a(), this.l14b(), this.l14c(), this.l15(), this.l16(), this.l17a(), this.l17b(), this.l18(), this.l19(), this.l20(), this.l21() ] } ================================================ FILE: src/forms/Y2020/irsForms/F8910.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented */ export default class F8910 extends F1040Attachment { sequenceIndex = 999 tag: FormTag = 'f8910' l15 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8919.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' // TODO export default class F8919 extends F1040Attachment { tag = 'f8919' sequenceIndex = 999 l6 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8936.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' export default class F8936 extends F1040Attachment { tag: FormTag = 'f8936' sequenceIndex = 999 l15 = (): number | undefined => undefined l23 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8949.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Asset, SoldAsset } from 'ustaxes/core/data' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' type EmptyLine = [ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined ] type Line = | [string, string, string, number, number, undefined, undefined, number] | EmptyLine const emptyLine: EmptyLine = [ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined ] const showDate = (date: Date): string => `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` const toLine = (position: SoldAsset): Line => [ position.name, showDate(position.openDate), showDate(position.closeDate), position.closePrice * position.quantity, position.openPrice * position.quantity, undefined, undefined, (position.closePrice - position.openPrice) * position.quantity ] const NUM_SHORT_LINES = 14 const NUM_LONG_LINES = 14 const padUntil = (xs: A[], v: B, n: number): (A | B)[] => { if (xs.length >= n) { return xs } return [...xs, ...Array.from(Array(n - xs.length)).map(() => v)] } export default class F8949 extends F1040Attachment { tag: FormTag = 'f8949' sequenceIndex = 12.1 index = 0 constructor(f1040: F1040, index = 0) { super(f1040) this.index = index } copies = (): F8949[] => { if (this.index === 0) { const extraCopiesNeeded = Math.round( Math.max( this.thisYearShortTermSales().length / NUM_SHORT_LINES, this.thisYearLongTermSales().length / NUM_LONG_LINES ) ) return Array.from(Array(extraCopiesNeeded)).map( (_, i) => new F8949(this.f1040, i + 1) ) } return [] } isNeeded = (): boolean => this.thisYearSales().length > 0 // Assuming we're only handling non-reported transactions part1BoxA = (): boolean => false part1BoxB = (): boolean => false part1BoxC = (): boolean => true part2BoxD = (): boolean => false part2BoxE = (): boolean => false part2BoxF = (): boolean => true thisYearSales = (): SoldAsset[] => this.f1040.assets.filter( (p) => p.closeDate !== undefined && p.closeDate.getFullYear() === 2020 ) as SoldAsset[] thisYearLongTermSales = (): SoldAsset[] => this.thisYearSales().filter((p) => this.isLongTerm(p)) thisYearShortTermSales = (): SoldAsset[] => this.thisYearSales().filter((p) => !this.isLongTerm(p)) // in milliseconds oneDay = 1000 * 60 * 60 * 24 isLongTerm = (p: Asset): boolean => { if (p.closeDate === undefined) return false const milliInterval = p.closeDate.getTime() - p.openDate.getTime() return milliInterval / this.oneDay > 366 } /** * Take the short term transactions that fit on this copy of the 8949 */ shortTermSales = (): SoldAsset[] => this.thisYearShortTermSales().slice( this.index * NUM_SHORT_LINES, (this.index + 1) * NUM_SHORT_LINES ) /** * Take the long term transactions that fit on this copy of the 8949 */ longTermSales = (): SoldAsset[] => this.thisYearLongTermSales().slice( this.index * NUM_LONG_LINES, (this.index + 1) * NUM_LONG_LINES ) shortTermLines = (): Line[] => padUntil( this.shortTermSales().map((p) => toLine(p)), emptyLine, NUM_SHORT_LINES ) longTermLines = (): Line[] => padUntil( this.longTermSales().map((p) => toLine(p)), emptyLine, NUM_LONG_LINES ) shortTermTotalProceeds = (): number => this.shortTermSales().reduce( (acc, p) => acc + p.closePrice * p.quantity - (p.closeFee ?? 0), 0 ) shortTermTotalCost = (): number => this.shortTermSales().reduce( (acc, p) => acc + p.openPrice * p.quantity + p.openFee, 0 ) shortTermTotalGain = (): number => this.shortTermTotalProceeds() - this.shortTermTotalCost() // TODO: handle adjustments column. shortTermTotalAdjustments = (): number | undefined => undefined longTermTotalProceeds = (): number => this.longTermSales().reduce( (acc, p) => acc + p.closePrice * p.quantity - (p.closeFee ?? 0), 0 ) longTermTotalCost = (): number => this.longTermSales().reduce( (acc, p) => acc + p.openPrice * p.quantity + p.openFee, 0 ) longTermTotalGain = (): number => this.longTermTotalProceeds() - this.longTermTotalCost() // TODO: handle adjustments column. longTermTotalAdjustments = (): number | undefined => undefined fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.part1BoxA(), this.part1BoxB(), this.part1BoxC(), ...this.shortTermLines().flat(), this.shortTermTotalProceeds(), this.shortTermTotalCost(), undefined, // greyed out field this.shortTermTotalAdjustments(), this.shortTermTotalGain(), this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.part2BoxD(), this.part2BoxE(), this.part2BoxF(), ...this.longTermLines().flat(), this.longTermTotalProceeds(), this.longTermTotalCost(), undefined, // greyed out field this.longTermTotalAdjustments(), this.longTermTotalGain() ] } ================================================ FILE: src/forms/Y2020/irsForms/F8959.ts ================================================ import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import ScheduleSE from './ScheduleSE' import { fica } from '../data/federal' import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' export default class F8959 extends F1040Attachment { tag: FormTag = 'f8959' sequenceIndex = 71 scheduleSE?: ScheduleSE isNeeded = (): boolean => { const filingStatus = this.f1040.info.taxPayer.filingStatus const totalW2Income = this.f1040.info.w2s.reduce( (s, w2) => s + w2.medicareIncome, 0 ) return fica.additionalMedicareTaxThreshold(filingStatus) < totalW2Income } thresholdFromFilingStatus = (): number => fica.additionalMedicareTaxThreshold(this.f1040.info.taxPayer.filingStatus) computeAdditionalMedicareTax = (compensation: number): number => fica.additionalMedicareTaxRate * compensation // Part I: Additional Medicare Tax on Medicare Wages l1 = (): number => this.f1040.info.w2s.reduce((sum, w2) => sum + w2.medicareIncome, 0) l2 = (): number | undefined => this.f1040.f4137?.l6() l3 = (): number | undefined => this.f1040.f8919?.l6() l4 = (): number => sumFields([this.l1(), this.l2(), this.l3()]) l5 = (): number => this.thresholdFromFilingStatus() l6 = (): number => Math.max(0, this.l4() - this.l5()) l7 = (): number | undefined => this.computeAdditionalMedicareTax(this.l6()) // Part II: Additional Medicare Tax on Self-Employment Income l8 = (): number | undefined => this.scheduleSE?.l6() l9 = (): number => this.thresholdFromFilingStatus() l10 = (): number => this.l4() l11 = (): number => Math.max(0, this.l9() - this.l10()) l12 = (): number => Math.max(0, (this.l8() ?? 0) - this.l11()) l13 = (): number | undefined => this.computeAdditionalMedicareTax(this.l12()) // Part III: Additional Medicare Tax on Railroad Retirement Tax Act // (RRTA) Compensation l14 = (): number | undefined => undefined // TODO: RRTA in W2 l15 = (): number => this.thresholdFromFilingStatus() l16 = (): number => Math.max(0, (this.l14() ?? 0) - this.l15()) l17 = (): number => this.computeAdditionalMedicareTax(this.l12()) // Part IV: Total Medicare Tax l18 = (): number => sumFields([this.l7(), this.l3(), this.l17()]) // Part V: Withholding Reconciliation l19 = (): number => this.f1040.info.w2s.reduce((sum, w2) => sum + w2.medicareWithholding, 0) l20 = (): number => this.l1() l21 = (): number => fica.regularMedicareTaxRate * this.l20() l22 = (): number => Math.max(0, this.l19() - this.l21()) l23 = (): number | undefined => 0 // TODO: RRTA l24 = (): number => sumFields([this.l22(), this.l23()]) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18(), this.l19(), this.l20(), this.l21(), this.l22(), this.l23(), this.l24() ] } ================================================ FILE: src/forms/Y2020/irsForms/F8960.ts ================================================ import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import { netInvestmentIncomeTax } from '../data/federal' import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' export default class F8960 extends F1040Attachment { tag: FormTag = 'f8960' sequenceIndex = 72 isNeeded = (): boolean => { const filingStatus = this.f1040.info.taxPayer.filingStatus const totalW2Income = this.f1040.info.w2s.reduce( (sum, w2) => sum + w2.income, 0 ) return netInvestmentIncomeTax.taxThreshold(filingStatus) < totalW2Income } //Taxable Interest l1 = (): number | undefined => this.f1040.l2b() // Ordinary Dividends l2 = (): number | undefined => this.f1040.l3b() /* Enter the gross income from all annuities, except annuities paid from the following. - Section 401—Qualified pension, profit-sharing, and stock bonus plans. - Section 403(a)—Qualified annuity plans purchased by an employer for an employee. - Section 403(b)—Annuities purchased by public schools or section 501(c)(3) tax-exempt organizations. - Section 408—Individual Retirement Accounts (IRAs) or Annuities. - Section 408A—Roth IRAs. - Section 457(b)—Deferred compensation plans of a state and local government and tax-exempt organization. - Amounts paid in consideration for services (for example, distributions from a foreign retirement plan that are paid in the form of an annuity and include investment income that was earned by the retirement plan). How your annuities are reported to you. Net investment income from annuities is reported to a recipient on Form 1099-R, Distributions From Pensions, Annuities, Retirement or Profit-Sharing Plans, IRAs, Insurance Contracts, etc. However, the amount reported on Form 1099-R may also include annuity payments from retirement plans that are exempt from NIIT. Amounts subject to NIIT should be identified with code "D" in box 7. If code "D" is shown in box 7 of Form 1099-R, include on Form 8960, line 3, the taxable amount reported on Form 1099-R, box 2a. However, if the payor checks box 2b indicating the taxable amount can’t be determined, you may need to calculate the taxable portion of your distribution. See Pub. 939, General Rule for Pensions and Annuities, and Pub. 575, Pension and Annuity Income, for details. */ l3 = (): number | undefined => undefined /* Rental Real Estate, Royalties, Partnerships, S Corporations, and Trusts Enter the following amount from your properly completed return. - Schedule 1 (Form 1040), line 5. - Form 1041, line 5. - Form 1041-QFT, the portion of line 4 that’s income and loss that properly would be reported by a trust filing Form 1041 on Form 1041, line 5. - Form 1040-NR, the amount properly reported on the attachment to your Form 1040-NR representing the amount that you would properly include on Schedule 1 (Form 1040), line 5, if you were filing Form 1040 or 1040‐SR and including income and loss only for your period of U.S. residency. */ l4a = (): number | undefined => this.f1040.schedule1.l5() l4b = (): number | undefined => undefined l4c = (): number => sumFields([this.l4a(), this.l4b()]) // Line 5a-5d: Gains and Losses on the Disassets of Property l5a = (): number => sumFields([this.f1040.l7(), this.f1040.schedule1.l4()]) // TODO: implement line 5b and 5c from worksheet. l5b = (): number | undefined => undefined l5c = (): number | undefined => undefined l5d = (): number => sumFields([this.l5a(), this.l5b(), this.l5c()]) // TODO: Line 6: Adjustments to Investment Income for Certain CFCs and PFICs l6 = (): number | undefined => undefined // TODO: Line 7: Other Modifications to Investment Income l7 = (): number | undefined => undefined l8 = (): number => sumFields([ this.l1(), this.l2(), this.l3(), this.l4c(), this.l5d(), this.l6(), this.l7() ]) // Todo: Implement Schedule A l9a = (): number | undefined => this.f1040.scheduleA?.l9() l9b = (): number | undefined => undefined l9c = (): number | undefined => undefined l9d = (): number => sumFields([this.l9a(), this.l9b(), this.l9c()]) l10 = (): number | undefined => undefined l11 = (): number => sumFields([this.l9d(), this.l10()]) l12 = (): number => Math.max(0, this.l8() - this.l11()) // TODO: This should also take into account values on form 2555 and adjustments for Certain CFCs and Certain PFICs l13 = (): number => this.f1040.l11() l14 = (): number => netInvestmentIncomeTax.taxThreshold(this.f1040.info.taxPayer.filingStatus) l15 = (): number => Math.max(0, this.l13() - this.l14()) l16 = (): number => (this.l12() < this.l15() ? this.l12() : this.l15()) l17 = (): number => Math.round(this.l16() * netInvestmentIncomeTax.taxRate) // TODO: Estates and Trusts // leave all of the following undefined until we support estates and trusts // these lines are to be left blank for individuals l18a = (): number | undefined => undefined l18b = (): number | undefined => undefined l18c = (): number | undefined => undefined l19a = (): number | undefined => undefined l19b = (): number | undefined => undefined l19c = (): number | undefined => undefined l20 = (): number | undefined => undefined // this.l19c() < this.l18c()? this.l19c() : this.l18c() l21 = (): number | undefined => undefined // Math.round(this.l20() * netInvestmentIncomeTax.taxRate) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, undefined, // Section 6013(g) election checkbox undefined, // Section 6013(h) election checkbox undefined, // Regulations section 1.1411-10(g) election checkbox this.l1(), this.l2(), this.l3(), this.l4a(), this.l4b(), this.l4c(), this.l5a(), this.l5b(), this.l5c(), this.l5d(), this.l6(), this.l7(), this.l8(), this.l9a(), this.l9b(), this.l9c(), this.l9d(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18a(), this.l18b(), this.l18c(), this.l19a(), this.l19b(), this.l19c(), this.l20(), this.l21() ] } ================================================ FILE: src/forms/Y2020/irsForms/F8962.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * TODO: Not yet implemented * Net premium tax credit */ export default class F8962 extends F1040Attachment { tag: FormTag = 'f8962' sequenceIndex = 999 credit = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8995.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented */ export default class F8995 extends F1040Attachment { tag: FormTag = 'f8995' sequenceIndex = 999 deductions = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/F8995A.ts ================================================ import F8995 from './F8995' export default class F8995A extends F8995 {} ================================================ FILE: src/forms/Y2020/irsForms/Main.ts ================================================ import { Asset, Information } from 'ustaxes/core/data' import { Either, run } from 'ustaxes/core/util' import F1040 from './F1040' import Form from 'ustaxes/core/irsForms/Form' import { F1040Error } from 'ustaxes/forms/errors' import { validate } from 'ustaxes/forms/F1040Base' export const create1040 = ( info: Information, assets: Asset[] ): Either => run(validate(info)) .map<[F1040, Form[]]>((info) => { const f1040 = new F1040(info, assets) return [f1040, f1040.schedules()] }) .value() ================================================ FILE: src/forms/Y2020/irsForms/Schedule1.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import ScheduleE from './ScheduleE' import { sumFields } from 'ustaxes/core/irsForms/util' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' export default class Schedule1 extends F1040Attachment { tag: FormTag = 'f1040s1' sequenceIndex = 1 scheduleE?: ScheduleE otherIncomeStrings: Set constructor(f1040: F1040) { super(f1040) this.otherIncomeStrings = new Set() } isNeeded = (): boolean => this.f1040.studentLoanInterestWorksheet !== undefined && this.f1040.studentLoanInterestWorksheet.notMFS() && this.f1040.studentLoanInterestWorksheet.isNotDependent() l1 = (): number | undefined => undefined l2a = (): number | undefined => undefined l2b = (): number | undefined => undefined l3 = (): number | undefined => undefined l4 = (): number | undefined => undefined l5 = (): number | undefined => this.scheduleE?.l41() l6 = (): number | undefined => undefined l7 = (): number | undefined => undefined l8 = (): number => { if ( this.f1040.f8889.isNeeded() || (this.f1040.f8889Spouse?.isNeeded() ?? false) ) { this.otherIncomeStrings.add('HSA') } return sumFields([ this.f1040.f8889.l16(), this.f1040.f8889.l20(), this.f1040.f8889Spouse?.l16(), this.f1040.f8889Spouse?.l20() ]) } l9 = (): number => sumFields([ this.l1(), this.l2a(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8() ]) l10 = (): number | undefined => undefined l11 = (): number | undefined => undefined l12 = (): number | undefined => sumFields([this.f1040.f8889.l13(), this.f1040.f8889Spouse?.l13()]) l13 = (): number | undefined => undefined l14 = (): number | undefined => undefined l15 = (): number | undefined => undefined l16 = (): number | undefined => undefined l17 = (): number | undefined => undefined l18 = (): number | undefined => undefined l19 = (): number | undefined => undefined l20 = (): number | undefined => this.f1040.studentLoanInterestWorksheet?.l9() l21 = (): number | undefined => undefined // TO DO: Write in Deductions l22writeIn = (): number | undefined => undefined // TODO: adjustments to income l22 = (): number | undefined => sumFields([ this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18(), this.l19(), this.l20(), this.l21(), this.l22writeIn() ]) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2a(), this.l2b(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), Array.from(this.otherIncomeStrings).join(' '), // Other income type textbox undefined, // Other income type 2 this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18(), undefined, // Alimony Recipient SSN undefined, // Date of Divorce/Seperation this.l19(), this.l20(), this.l21(), this.l22() ] } ================================================ FILE: src/forms/Y2020/irsForms/Schedule2.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' export default class Schedule2 extends F1040Attachment { tag: FormTag = 'f1040s2' sequenceIndex = 2 otherIncomeStrings: Set constructor(f1040: F1040) { super(f1040) this.otherIncomeStrings = new Set() } isNeeded = (): boolean => this.f1040.f8959.isNeeded() || this.f1040.f8960.isNeeded() // Part I: Tax l1 = (): number | undefined => undefined // TODO: Alternative Minimum Tax (form 6251) l2 = (): number | undefined => undefined // TODO: excess advance premium tax credit repayment (form 8962) l3 = (): number => sumFields([this.l1(), this.l2()]) // Part II: Other Tax l4 = (): number | undefined => undefined // TODO: self-employment tax (schedule SE) l5 = (): number | undefined => undefined // TODO: unreported FICA tax l6 = (): number | undefined => undefined // TODO: additional tax on retirement accounts l7a = (): number | undefined => undefined // TODO: household employment taxes l7b = (): number | undefined => undefined // TODO: repayment of first-time homebuyer credit l8 = (): number | undefined => { if ( this.f1040.f8889.l17b() !== undefined || this.f1040.f8889Spouse?.l17b() !== undefined ) { this.otherIncomeStrings.add('HSA') } if (this.f1040.f8889.l21() > 0) { this.otherIncomeStrings.add('HDHP') } if ( this.f1040.f8889Spouse?.l21() !== undefined && this.f1040.f8889Spouse.l21() > 0 ) { this.otherIncomeStrings.add('HDHP') } return sumFields([ this.f1040.f8959.l18(), this.f1040.f8960.l17(), this.f1040.f8889.l17b(), this.f1040.f8889.l21(), this.f1040.f8889Spouse?.l17b(), this.f1040.f8889Spouse?.l21() ]) } l9 = (): number | undefined => undefined // TODO: section 965 net tax liability l10 = (): number | undefined => sumFields([ this.l4(), this.l5(), this.l6(), this.l7a(), this.l7b(), this.l8() ]) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2(), this.l3(), this.l4(), undefined, undefined /* checkboxes */, this.l5(), this.l6(), this.l7a(), this.l7b(), this.f1040.f8959.isNeeded(), // Form 8959 checkbox this.f1040.f8960.isNeeded(), // Form 8960 checkbox undefined, //others checkbox Array.from(this.otherIncomeStrings).join(' '), // others textbox this.l8(), this.l9(), this.l10() ] } ================================================ FILE: src/forms/Y2020/irsForms/Schedule3.ts ================================================ import { IncomeW2, PersonRole } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import { fica } from '../data/federal' import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' export const claimableExcessSSTaxWithholding = (w2s: IncomeW2[]): number => { /* Excess FICA taxes are calculated per person. If an individual person has greater than the applicable amount then they are entitled to a refund of that amount */ let claimableExcessFica = 0 const primaryFica = w2s .filter((w2) => w2.personRole == PersonRole.PRIMARY) .map((w2) => w2.ssWithholding) .reduce((l, r) => l + r, 0) const spouseFica = w2s .filter((w2) => w2.personRole == PersonRole.SPOUSE) .map((w2) => w2.ssWithholding) .reduce((l, r) => l + r, 0) if ( primaryFica > fica.maxSSTax && w2s .filter((w2) => w2.personRole == PersonRole.PRIMARY) .every((w2) => w2.ssWithholding <= fica.maxSSTax) ) { claimableExcessFica += primaryFica - fica.maxSSTax } if ( spouseFica > fica.maxSSTax && w2s .filter((w2) => w2.personRole == PersonRole.SPOUSE) .every((w2) => w2.ssWithholding <= fica.maxSSTax) ) { claimableExcessFica += spouseFica - fica.maxSSTax } return claimableExcessFica } export default class Schedule3 extends F1040Attachment { tag: FormTag = 'f1040s3' sequenceIndex = 3 isNeeded = (): boolean => claimableExcessSSTaxWithholding(this.f1040.info.w2s) > 0 deductions = (): number => 0 // Part I: Nonrefundable credits l1 = (): number | undefined => undefined l2 = (): number | undefined => undefined l3 = (): number | undefined => undefined l4 = (): number | undefined => undefined l5 = (): number | undefined => undefined l6 = (): number | undefined => undefined // TODO: checkboxes l7 = (): number | undefined => sumFields([this.l1(), this.l2(), this.l3(), this.l4(), this.l5()]) // Part II: Other payments and refundable credits l8 = (): number | undefined => undefined l9 = (): number | undefined => undefined l10 = (): number => // TODO: also applies to RRTA tax claimableExcessSSTaxWithholding(this.f1040.validW2s()) l11 = (): number | undefined => undefined l12a = (): number | undefined => undefined l12b = (): number | undefined => undefined l12c = (): number | undefined => undefined l12d = (): number | undefined => undefined // TODO: 'other' box l12e = (): number | undefined => undefined l12f = (): number | undefined => sumFields([this.l12a(), this.l12b(), this.l12c(), this.l12d(), this.l12e()]) l13 = (): number | undefined => sumFields([this.l8(), this.l9(), this.l10(), this.l11(), this.l12f()]) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2(), this.l3(), this.l4(), this.l5(), ...Array(4).fill(undefined), // TODO: checkboxes this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12a(), this.l12b(), this.l12c(), undefined /* TODO: 'other' box */, this.l12d(), this.l12e(), this.l12f(), this.l13() ] } ================================================ FILE: src/forms/Y2020/irsForms/Schedule8812.ts ================================================ import F1040Attachment from './F1040Attachment' import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Field } from 'ustaxes/core/pdfFiller' export default class Schedule8812 extends F1040Attachment { tag: FormTag = 'f1040s8' sequenceIndex = 47 isNeeded = (): boolean => this.f1040.info.taxPayer.dependents.some( (dep) => this.f1040.childTaxCreditWorksheet.qualifiesChild(dep) || this.f1040.childTaxCreditWorksheet.qualifiesOther(dep) ) // This can be calculated with either pub 972 or the child tax credit worksheet, but for now we're only supporting the worksheet // TODO: Add pub 972 support l1 = (): number => this.f1040.childTaxCreditWorksheet.l8() ?? 0 l2 = (): number => this.f1040.l19() ?? 0 l3 = (): number => Math.max(0, this.l1() - this.l2()) l4 = (): number | undefined => this.f1040.childTaxCreditWorksheet.numberQualifyingChildren() * 1400 l5 = (): number => Math.min(this.l3(), this.l4() ?? 0) // This is a horrible, horrible line // have net earnings from self-employment and used optional methods => report pub972 Earned Income Worksheet (even if taking EIC) // Taking EIC => completed worksheet B? => worksheet B line 4b, plus combat pay, minus clergy housing and meals, else EIC step 5 plus combat pay // Not taking EIC? => pub 972 Earned Income Worksheet if self employed, or filing Schedule SE, or filing Schedule C as a "statutory employee" , // else Form 1040 l1, minus fellowships, income as an inmate, deferred compensation, and Medicaid waiver, unless you chose to inclue the Medicaid waivers, // and add combat pay in as well // So for now, it's just line 1 or EIC step 5 (line 9) // TODO: Add other earned income definitions l6 = (): number => this.f1040.scheduleEIC.isNeeded() ? this.f1040.scheduleEIC.earnedIncome() : this.f1040.l1() l7checkBox = (): boolean => this.l6() > 2500 l7 = (): number | undefined => this.l7checkBox() ? this.l6() - 2500 : undefined l8 = (): number | undefined => (this.l7() ?? 0) * 0.15 ssWithholding(): number { if (this.f1040.validW2s().length > 0) { return this.f1040 .validW2s() .reduce((res, w2) => res + w2.ssWithholding, 0) } return 0 } medicareWithholding = (): number => this.f1040.validW2s().reduce((res, w2) => res + w2.medicareWithholding, 0) l9checkBox = (): boolean => (this.l4() ?? 0) > 4200 l9 = (): number | undefined => this.l9checkBox() ? this.ssWithholding() + this.medicareWithholding() : undefined l10 = (): number | undefined => this.l9checkBox() ? sumFields([ this.f1040.schedule1.l14(), this.f1040.schedule2.l5(), this.f1040.schedule2.l8() ]) : undefined l11 = (): number | undefined => this.l9checkBox() ? (this.l9() ?? 0) + (this.l10() ?? 0) : undefined // TODO: Add 1040-NR l12 = (): number | undefined => this.l9checkBox() ? sumFields([this.f1040.l27(), this.f1040.schedule3.l10()]) : undefined l13 = (): number | undefined => this.l9checkBox() ? Math.max(0, (this.l11() ?? 0) - (this.l12() ?? 0)) : undefined l14 = (): number | undefined => this.l9checkBox() ? Math.max(this.l8() ?? 0, this.l13() ?? 0) : undefined l15 = (): number | undefined => this.l9checkBox() ? Math.min(this.l14() ?? 0, this.l5()) : this.l5() fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2(), this.l3(), this.f1040.childTaxCreditWorksheet.numberQualifyingChildren(), this.l4(), this.l5(), this.l6(), undefined, // Nontaxable combat pay not supported, !this.l7checkBox(), this.l7checkBox(), this.l7(), this.l8(), !this.l9checkBox(), this.l9checkBox(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15() ] } ================================================ FILE: src/forms/Y2020/irsForms/ScheduleA.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' // Not yet implemented export default class ScheduleA extends F1040Attachment { tag: FormTag = 'f1040sa' sequenceIndex = 999 deductions(): number { return 0 } // Used in Form 8960 l9 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/ScheduleB.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import { Field } from 'ustaxes/core/pdfFiller' import F1040 from './F1040' interface PayerAmount { payer?: string amount?: number } export default class ScheduleB extends F1040Attachment { tag: FormTag = 'f1040sb' sequenceIndex = 8 readonly interestPayersLimit = 14 readonly dividendPayersLimit = 16 index = 0 constructor(f1040: F1040, index = 0) { super(f1040) this.index = index } copies = (): ScheduleB[] => { if (this.index === 0) { const numInterestPayers = this.l1Fields().length const numDivPayers = this.l5Fields().length const extraCopiesNeeded = Math.floor( Math.max( numInterestPayers / this.interestPayersLimit, numDivPayers / this.dividendPayersLimit ) ) return Array.from(Array(extraCopiesNeeded)).map( (_, i) => new ScheduleB(this.f1040, i + 1) ) } return [] } isNeeded = (): boolean => this.l1Fields().length > 0 || this.l5Fields().length > 0 l1Fields = (): PayerAmount[] => this.f1040.f1099Ints().map((v) => ({ payer: v.payer, amount: v.form.income })) l1 = (): Array => { const payerValues = this.l1Fields().slice( this.index * this.interestPayersLimit, (this.index + 1) * this.interestPayersLimit ) const rightPad = 2 * (this.interestPayersLimit - payerValues.length) // ensure we return an array of length interestPayersLimit * 2. return payerValues .flatMap(({ payer, amount }) => [payer, amount?.toString()]) .concat(Array(rightPad).fill(undefined)) } l2 = (): number => sumFields(this.f1040.f1099Ints().map((f) => f.form.income)) // TODO: Interest from tax exempt savings bonds l3 = (): number | undefined => undefined l4 = (): number => this.l2() - (this.l3() ?? 0) /** * Total interest on all schedule Bs. */ to1040l2b = (): number => [this, ...this.copies()].reduce((acc, f) => acc + f.l4(), 0) l5Fields = (): PayerAmount[] => this.f1040.f1099Divs().map((v) => ({ payer: v.payer, amount: v.form.dividends })) l5 = (): Array => { const payerValues = this.l5Fields().slice( this.index * this.dividendPayersLimit, (this.index + 1) * this.dividendPayersLimit ) const rightPad = 2 * (this.dividendPayersLimit - payerValues.length) return payerValues .flatMap(({ payer, amount }) => [payer, amount]) .concat(Array(rightPad).fill(undefined)) } l6 = (): number => sumFields(this.l5Fields().map(({ amount }) => amount)) /** * Total dividends on all schedule Bs. */ to1040l3b = (): number => [this, ...this.copies()].reduce((acc, f) => acc + f.l6(), 0) foreignAccount = (): boolean => this.f1040.info.questions.FOREIGN_ACCOUNT_EXISTS ?? false fincenForm = (): boolean => this.f1040.info.questions.FINCEN_114 ?? false fincenCountry = (): string | undefined => this.f1040.info.questions.FINCEN_114_ACCOUNT_COUNTRY foreignTrust = (): boolean => this.f1040.info.questions.FOREIGN_TRUST_RELATIONSHIP ?? false l7a = (): [boolean, boolean] => [ this.foreignAccount(), !this.foreignAccount() ] l7a2 = (): [boolean, boolean] => [this.fincenForm(), !this.fincenForm()] l7b = (): string | undefined => this.fincenCountry() l8 = (): [boolean, boolean] => [this.foreignTrust(), !this.foreignTrust()] fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, ...this.l1(), this.l2(), this.l3(), this.l4(), ...this.l5(), this.l6(), ...this.l7a(), ...this.l7a2(), this.l7b(), ...this.l8() ] } ================================================ FILE: src/forms/Y2020/irsForms/ScheduleC.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented */ export default class ScheduleC extends F1040Attachment { tag: FormTag = 'f1040sc' sequenceIndex = 999 // TODO: statutory employee income // shown on Schedule 8812, earned income l1 = (): number | undefined => undefined // TODO: net profit or loss // shown on Schedule 8812, earned income l31 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/ScheduleD.ts ================================================ import F1040Attachment from './F1040Attachment' import { F1099BData, FilingStatus } from 'ustaxes/core/data' import { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import SDRateGainWorksheet from './worksheets/SDRateGainWorksheet' import SDUnrecaptured1250 from './worksheets/SDUnrecaptured1250' import F8949 from './F8949' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' export default class ScheduleD extends F1040Attachment { tag: FormTag = 'f1040sd' sequenceIndex = 12 aggregated: F1099BData rateGainWorksheet: SDRateGainWorksheet unrecaptured1250: SDUnrecaptured1250 readonly l21MinMFS = 1500 readonly l21MinDefault = 3000 constructor(f1040: F1040) { super(f1040) const bs: F1099BData[] = f1040.f1099Bs().map((f) => f.form) this.aggregated = { shortTermProceeds: bs.reduce((l, r) => l + r.shortTermProceeds, 0), shortTermCostBasis: bs.reduce((l, r) => l + r.shortTermCostBasis, 0), longTermProceeds: bs.reduce((l, r) => l + r.longTermProceeds, 0), longTermCostBasis: bs.reduce((l, r) => l + r.longTermCostBasis, 0) } this.rateGainWorksheet = new SDRateGainWorksheet() this.unrecaptured1250 = new SDUnrecaptured1250() } isNeeded = (): boolean => this.f1040.f1099Bs().length > 0 || this.f1040.f8949.isNeeded() l21Min = (): number => { if (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS) { return this.l21MinMFS } return this.l21MinDefault } l1ad = (): number | undefined => this.aggregated.shortTermProceeds l1ae = (): number | undefined => this.aggregated.shortTermCostBasis // This field is greyed out, but fillable l1ag = (): number | undefined => undefined l1ah = (): number => sumFields([this.l1ad(), 0 - (this.l1ae() ?? 0)]) l1f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxA()) l1bd = (): number => sumFields(this.l1f8949s().map((f) => f.shortTermTotalProceeds())) l1be = (): number => sumFields(this.l1f8949s().map((f) => f.shortTermTotalCost())) l1bg = (): number => sumFields(this.l1f8949s().map((f) => f.shortTermTotalAdjustments())) l1bh = (): number => sumFields(this.l1f8949s().map((f) => f.shortTermTotalGain())) l2f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxB()) l2d = (): number => sumFields(this.l2f8949s().map((f) => f.shortTermTotalProceeds())) l2e = (): number => sumFields(this.l2f8949s().map((f) => f.shortTermTotalCost())) l2g = (): number => sumFields(this.l2f8949s().map((f) => f.shortTermTotalAdjustments())) l2h = (): number => sumFields(this.l2f8949s().map((f) => f.shortTermTotalGain())) l3f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxC()) l3d = (): number => sumFields(this.l3f8949s().map((f) => f.shortTermTotalProceeds())) l3e = (): number => sumFields(this.l3f8949s().map((f) => f.shortTermTotalCost())) l3g = (): number => sumFields(this.l3f8949s().map((f) => f.shortTermTotalAdjustments())) l3h = (): number => sumFields(this.l3f8949s().map((f) => f.shortTermTotalGain())) l4 = (): number | undefined => undefined l5 = (): number | undefined => undefined l6 = (): number | undefined => undefined l7 = (): number => sumFields([ this.l1ah(), this.l1bh(), this.l2h(), this.l3h(), this.l4(), this.l5(), this.l6() ]) l8ad = (): number | undefined => this.aggregated.longTermProceeds l8ae = (): number | undefined => this.aggregated.longTermCostBasis // This field is greyed out, but fillable l8ag = (): number | undefined => undefined l8ah = (): number | undefined => sumFields([this.l8ad(), 0 - (this.l8ae() ?? 0)]) l8f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxD()) l8bd = (): number => sumFields(this.l8f8949s().map((f) => f.longTermTotalProceeds())) l8be = (): number => sumFields(this.l8f8949s().map((f) => f.longTermTotalCost())) l8bg = (): number => sumFields(this.l8f8949s().map((f) => f.longTermTotalAdjustments())) l8bh = (): number => sumFields(this.l8f8949s().map((f) => f.longTermTotalGain())) l9f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxE()) l9d = (): number => sumFields(this.l9f8949s().map((f) => f.longTermTotalProceeds())) l9e = (): number => sumFields(this.l9f8949s().map((f) => f.longTermTotalCost())) l9g = (): number => sumFields(this.l9f8949s().map((f) => f.longTermTotalAdjustments())) l9h = (): number => sumFields(this.l9f8949s().map((f) => f.longTermTotalGain())) l10f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxF()) l10d = (): number => sumFields(this.l10f8949s().map((f) => f.longTermTotalProceeds())) l10e = (): number => sumFields(this.l10f8949s().map((f) => f.longTermTotalCost())) l10g = (): number => sumFields(this.l10f8949s().map((f) => f.longTermTotalAdjustments())) l10h = (): number => sumFields(this.l10f8949s().map((f) => f.longTermTotalGain())) l11 = (): number | undefined => undefined l12 = (): number | undefined => undefined l13 = (): number | undefined => this.f1040 .f1099Divs() .reduce((s, f) => s + f.form.totalCapitalGainsDistributions, 0) l14 = (): number | undefined => undefined l15 = (): number => sumFields([ this.l8ah(), this.l8bh(), this.l9h(), this.l10h(), this.l11(), this.l12(), this.l13(), this.l14() ]) // L7 + L15 // If +, enter on L16 of F1040 // If -, go to L21 // If 0, go to L22 l16 = (): number => sumFields([this.l7(), this.l15()]) // Are L15 and L16 both gains? l17 = (): boolean => this.l15() > 0 && this.l16() > 0 l18 = (): number | undefined => { if (!this.l17()) { return undefined } return this.rateGainWorksheet.l7() } l19 = (): number | undefined => { if (!this.l17()) { return undefined } return this.unrecaptured1250.l18() } l20 = (): boolean | undefined => { if (!this.l17()) { return undefined } return (this.l18() ?? 0) === 0 && (this.l19() ?? 0) === 0 } fillL21 = (): boolean => !this.l20() && ((this.l16() > 0 && this.l17()) || this.l16() < 0) l21 = (): number | undefined => { if (this.fillL21()) { return Math.max(-this.l21Min(), this.l16()) } } haveQualifiedDividends = (): boolean => this.f1040.f1099Divs().some((f) => f.form.qualifiedDividends > 0) // TODO: Schedule D tax worksheet // neither box should be checked if this question was not required to be answered by l20. l22 = (): boolean | undefined => { if (this.l20() !== false) { return this.haveQualifiedDividends() } } lossCarryForward = (): number => { const amount = this.l16() + this.l21Min() if (amount < 0) { return -amount } return 0 } to1040 = (): number => this.l21() ?? this.l16() computeTaxOnQDWorksheet = (): boolean => (this.l20() ?? false) || (this.l22() ?? false) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, false, false, this.l1ad(), this.l1ae(), this.l1ag(), this.l1ah(), this.l1bd(), this.l1be(), this.l1bg(), this.l1bh(), this.l2d(), this.l2e(), this.l2g(), this.l2h(), this.l3d(), this.l3e(), this.l3g(), this.l3h(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8ad(), this.l8ae(), this.l8ag(), this.l8ah(), this.l8bd(), this.l8be(), this.l8bg(), this.l8bh(), this.l9d(), this.l9e(), this.l9g(), this.l9h(), this.l10d(), this.l10e(), this.l10g(), this.l10h(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), !this.l17(), this.l18(), this.l19(), this.l20() === true, this.l20() === false, this.l21(), this.l22() === true, this.l22() === false ] } ================================================ FILE: src/forms/Y2020/irsForms/ScheduleE.ts ================================================ import { Address, Property, PropertyType, PropertyExpenseTypeName } from 'ustaxes/core/data' import { FormTag } from 'ustaxes/core/irsForms/Form' import { displayNegPos, sumFields } from 'ustaxes/core/irsForms/util' import _ from 'lodash' import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' type Cell = number | undefined export type MatrixRow = [Cell, Cell, Cell] const fill = (values: number[]): MatrixRow => { const realValues = (values as Cell[]).slice(0, 3).map((v) => { if (v === 0) { return undefined } return v }) return [ ...realValues, ...Array.from(Array(3 - realValues.length)).map(() => undefined) ] as MatrixRow } const propTypeIndex = { [PropertyType.singleFamily]: 1, [PropertyType.multiFamily]: 2, [PropertyType.vacation]: 3, [PropertyType.commercial]: 4, [PropertyType.land]: 5, [PropertyType.selfRental]: 7, [PropertyType.other]: 8 } export default class ScheduleE extends F1040Attachment { tag: FormTag = 'f1040se' sequenceIndex = 13 isNeeded = (): boolean => this.f1040.info.realEstate.length > 0 addressString = (address: Address): string => [ address.address, address.city, address.state ?? address.province ?? '', address.zip ?? address.postalCode ?? '' ].join(', ') propForRow = (row: number): Property | undefined => { if (row < this.f1040.info.realEstate.length) { return this.f1040.info.realEstate[row] } } /** * Whether or not you can deduct expenses for the unit depends on whether or not you used * the unit as a home in 2020. You used the unit as a home if your personal use of the unit * was more than the greater of: * 14 days, or * 10% of the total days it was rented to others at a fair rental price. * @param p */ propertyUseTest = (p: Property): boolean => p.personalUseDays <= Math.max(14, 0.1 * p.rentalDays) l3 = (): MatrixRow => { const properties = this.f1040.info.realEstate return fill(properties.map((a) => a.rentReceived)) } // TODO: 'Line 4: Royalties l4 = (): MatrixRow => [undefined, undefined, undefined] getExpensesRow = (expType: PropertyExpenseTypeName): MatrixRow => fill( this.f1040.info.realEstate.map((p) => { if (this.propertyUseTest(p)) { return p.expenses[expType] ?? 0 } return 0 }) ) // Matching order of expenses in rows of form expenses: PropertyExpenseTypeName[] = [ 'advertising', 'auto', 'cleaning', 'commissions', 'insurance', 'legal', 'management', 'mortgage', 'otherInterest', 'repairs', 'supplies', 'taxes', 'utilities', 'depreciation' ] l19 = (): [string | undefined, MatrixRow] => { const expenseRow = this.getExpensesRow('other') const otherText = this.f1040.info.realEstate .flatMap((p) => p.otherExpenseType !== undefined ? [p.otherExpenseType] : [] ) .join(',') return [otherText, expenseRow] } allExpenses = (): MatrixRow[] => this.expenses.map((e) => this.getExpensesRow(e)) l12 = (): MatrixRow => this.getExpensesRow('mortgage') l18 = (): MatrixRow => this.getExpensesRow('depreciation') // TODO - required from pub 596 worksheet 1 royaltyExpenses = (): number | undefined => undefined l20 = (): MatrixRow => fill(_.unzip(this.allExpenses()).map((column) => sumFields(column))) l21 = (): MatrixRow => _.zipWith( this.l3(), this.l4(), this.l20(), (x, y, z) => (x ?? 0) + (y ?? 0) - (z ?? 0) ) as MatrixRow // Deductible real estate loss from 8582, as positive number l22 = (): MatrixRow => this.f1040.f8582?.deductibleRealEstateLossAfterLimitation() ?? [ undefined, undefined, undefined ] l23a = (): number => sumFields(this.l3()) l23b = (): number => sumFields(this.l4()) l23c = (): number => sumFields(this.l12()) l23d = (): number => sumFields(this.l18()) l23e = (): number => sumFields(this.l20()) rentalNet = (): MatrixRow => _.zipWith(this.l3(), this.l20(), (x, y) => (x ?? 0) - (y ?? 0)) as MatrixRow l24 = (): number => sumFields(this.l21().filter((x) => x !== undefined && x > 0)) // TODO: 'Ignoring royalty losses on L25 l25 = (): number => { return sumFields(this.l22()) } l26 = (): number => sumFields([this.l24(), this.l25()]) // TODO: required from Pub 596 l29ah = (): number | undefined => undefined l29ak = (): number | undefined => undefined l29bg = (): number | undefined => undefined l29bi = (): number | undefined => undefined l29bj = (): number | undefined => undefined // TODO: 'Partnership and S corporation income or loss l32 = (): number | undefined => { return undefined } l34ad = (): number | undefined => undefined l34af = (): number | undefined => undefined l34bc = (): number | undefined => undefined l34be = (): number | undefined => undefined // TODO: 'Real estate trust income or loss l37 = (): number | undefined => { return undefined } // TODO: 'REMICS income or loss l39 = (): number | undefined => { return undefined } // TODO: 'Farm rental income or loss l40 = (): number | undefined => { return undefined } l41 = (): number => sumFields([this.l26(), this.l32(), this.l37(), this.l39(), this.l40()]) fields = (): Field[] => { const [p0, p1, p2] = [0, 1, 2].map((i) => this.propForRow(i)) return [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, false, false, false, false, ...[p0, p1, p2].map((p) => p === undefined ? undefined : this.addressString(p.address) ), p0 === undefined ? undefined : propTypeIndex[PropertyType[p0.propertyType]], p1 === undefined ? undefined : propTypeIndex[PropertyType[p1.propertyType]], p2 === undefined ? undefined : propTypeIndex[PropertyType[p2.propertyType]], p0?.rentalDays, p0?.personalUseDays, p0?.qualifiedJointVenture, p1?.rentalDays, p1?.personalUseDays, p1?.qualifiedJointVenture, p2?.rentalDays, p2?.personalUseDays, p2?.qualifiedJointVenture, [p0, p1, p2].find((p) => p?.propertyType === 'other') ?.otherPropertyType ?? undefined, ...this.l3(), ...this.l4(), ...this.allExpenses().flat(), ...this.l19().flat(), ...this.l20(), ...this.l21(), ...this.l22(), this.l23a(), this.l23b(), this.l23c(), this.l23d(), this.l23e(), this.l24(), Math.abs(this.l25()), displayNegPos(this.l26()), // Page 2 - TODO: completely unimplemented this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, ...[false, false], // l27 ...Array(6 * 4 + 5 * 4).fill(undefined), // l28 undefined, // grey this.l29ah(), undefined, // grey undefined, // grey this.l29ak(), this.l29bg(), undefined, // grey this.l29bi(), this.l29bj(), undefined, // grey undefined, // l30 undefined, // l31 this.l32(), // l32 ...Array(2 * 4).fill(undefined), // l33 undefined, this.l34ad(), undefined, this.l34af(), ...Array(4).fill(undefined), this.l34bc(), undefined, // grey this.l34be(), undefined, // grey // l34b undefined, // l35 undefined, // l36 this.l37(), // l37 ...Array(5 + 4).fill(undefined), // l38 this.l39(), // l39 this.l40(), // l40 this.l41(), // l41 undefined, undefined ] } } ================================================ FILE: src/forms/Y2020/irsForms/ScheduleEIC.ts ================================================ import _ from 'lodash' import { Dependent, FilingStatus } from 'ustaxes/core/data' import { FormTag } from 'ustaxes/core/irsForms/Form' import { evaluatePiecewise, Piecewise } from 'ustaxes/core/util' import { sumFields } from 'ustaxes/core/irsForms/util' import * as federal from '../data/federal' import F1040 from './F1040' import F2555 from './F2555' import F4797 from './F4797' import F8814 from './F8814' import Pub596Worksheet1 from './worksheets/Pub596Worksheet1' import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' type PrecludesEIC = (f: F) => boolean // TODO: check F2555 const checks2555: PrecludesEIC = (): boolean => { return false } // TODO: check F4797 const checks4797: PrecludesEIC = (): boolean => { return false } // TODO: check F8814 const checks8814: PrecludesEIC = (): boolean => { return false } const checksPub596: PrecludesEIC = (f): boolean => f.precludesEIC() const precludesEIC = (p: PrecludesEIC) => (f: F | undefined): boolean => { if (f === undefined) { return false } return p(f) } export default class ScheduleEIC extends F1040Attachment { tag: FormTag = 'f1040sei' sequenceIndex = 43 f2555?: F2555 f4797?: F4797 f8814?: F8814 pub596Worksheet1: Pub596Worksheet1 qualifyingStudentCutoffYear = 1996 qualifyingCutoffYear = 2001 investmentIncomeLimit = 3650 constructor(f1040: F1040) { super(f1040) this.pub596Worksheet1 = new Pub596Worksheet1(f1040) } isNeeded = (): boolean => this.allowed() // instructions step 1.1 passIncomeLimit = (): boolean => { const filingStatus = this.f1040.info.taxPayer.filingStatus const incomeLimits = federal.EIC.caps[filingStatus] if (incomeLimits !== undefined) { const limit = incomeLimits[ Math.min(this.qualifyingDependents().length, incomeLimits.length - 1) ] return this.f1040.l11() < limit } return false } // Step 1.2, todo, both spouses must have a SSN issued before 2020 due date // // TODO: Step 1.2 (valid SSNs) unchecked and without work restriction and valid for eic purposes validSSNs = (): boolean => { return true } // Step 1.3 allowedFilingStatus = (): boolean => this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFS // Step 1.4 allowedFilling2555 = (): boolean => !precludesEIC(checks2555)(this.f2555) // // TODO: Step 1.5, Not checking non-resident alien Step 1.5 nonResidentAlien allowedNonresidentAlien = (): boolean => { return true } // step 2, question 1 investmentIncome = (): number => sumFields([ this.f1040.l2a(), this.f1040.l2b(), this.f1040.l3b(), Math.max(this.f1040.l7() ?? 0, 0) ]) passInvestmentIncomeLimit = (): boolean => this.investmentIncome() < federal.EIC.maxInvestmentIncome // Todo, step 2, question 3 f4797AllowsEIC = (): boolean => !precludesEIC(checks4797)(this.f4797) // Todo, instruction 2.4.1 filingScheduleE = (): boolean => this.f1040.scheduleE.isNeeded() // // TODO: Not checking personal property income 2.4.2 passIncomeFromPersonalProperty = (): boolean => { return true } // 2.4.3 passForm8814 = (): boolean => !precludesEIC(checks8814)(this.f8814) // // TODO: Not checking passive activity 2.4.4 incomeOrLossFromPassiveActivity = (): boolean => { return false } // 2.4.5 passPub596 = (): boolean => !precludesEIC(checksPub596)(this.pub596Worksheet1) // 3.1 atLeastOneChild = (): boolean => this.qualifyingDependents().length > 0 // 3.2, 4.4 jointReturn = (): boolean => this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ // // TODO: 3.3: Not checking qualifying child of another 3.3, 4.5 qualifyingChildOfAnother = (): boolean => { return false } // 4.1 - covered by income limit check // // TODO: 4.2: Not checking taxpayer age 4.2 over25Under65 = (): boolean => { return true } // // TODO: 4.3: Not checking residency 4.3 mainHomeInsideUsBothPeople = (): boolean => { return true } // 4.4 covered above // 4.5 covered above // 4.6 dependent of another dependentOfAnother = (): boolean => this.f1040.info.taxPayer.primaryPerson.isTaxpayerDependent || (this.f1040.info.taxPayer.spouse?.isTaxpayerDependent ?? false) // // TODO: 5.1: Not checking church self-employment income 5.1 - Filing schedule SE for church filingSEChurchIncome = (): boolean => { return false } // // TODO: 5.1.2: Not checking scholarship, grants 5.1.2 taxableScholarshipIncome = (): number => { return 0 } // // TODO: 5.1.3: Not checking prison income 5.1.3 prisonIncome = (): number => { return 0 } // // TODO: 5.1.4: Not checking pension income 5.1.4 pensionPlanIncome = (): number => { return 0 } // // TODO: 5.1.5: Not checking medicaid waiver 5.1.5 medicaidWaiverPayment = (): number => { return 0 } // // TODO: 5.1.8: Not checking nontaxable combat pay 5.1.8 nontaxableCombatPay = (): number => { return 0 } // 5.1 - Earned income earnedIncome = (): number => { const l1 = this.f1040.l1() const l2 = this.taxableScholarshipIncome() const l3 = this.prisonIncome() const l4 = this.pensionPlanIncome() const l5 = this.medicaidWaiverPayment() const l6 = l2 + l3 + l4 + l5 const l7 = l1 - l6 const l8 = this.nontaxableCombatPay() const l9 = l7 + l8 return l9 } /** * The credit table in Publication 596 provides an * amount for each interval of $50, calculated from the * midpoint of the interval. * * @param income The earned income * @returns the earned income rounded to the nearest 25 */ roundIncome = (income: number): number => { if (income < 1) { return 0 } return Math.round(Math.round(income) / 50) * 50 + 25 } /** * Based on the earned income and filing status, calculate the * allowed EITC. * * For tax year 2020, IRS Rev. Proc. 2019-44 outlines the required * calculation for the EITC based on number of qualifying children * and filing status. * * https://www.irs.gov/pub/irs-drop/rp-19-44.pdf * * IRS publication 596 provides a table that can be used * to figure the EITC, and is the basis of online calculators published * by IRS. This table uses the formulas outlined in Rev Proc 2019-44 * but applies them to incomes lying in $50 intervals, with the midpoint * of those intervals used to calculate the credit for the entire window. * For example, if the taxpayer has an earned income of $5000, the amount * that is found in the table is calculated based on an income of $5025 and * comes out ahead. Conversely, someone with an earned income of $5049 finds * a credit in the table calculated off the same $5,025 and loses out. * * https://www.irs.gov/pub/irs-pdf/p596.pdf * * @param income The earned income * @returns */ calculateEICForIncome = (income: number): number => { const f: Piecewise[] | undefined = federal.EIC.formulas[this.f1040.info.taxPayer.filingStatus] if (f === undefined) { return 0 } return Math.max( 0, evaluatePiecewise( f[this.qualifyingDependents().length], this.roundIncome(income) ) ) } // // TODO: 5.2: Not checking selfemployment 5.2 selfEmployed = (): boolean => { return false } // 5.3 - covered by income check // 6.1 - We will figure the credit. // EIC worksheet A calculation credit = (): number => Math.min( this.calculateEICForIncome(this.earnedIncome()), this.calculateEICForIncome(this.f1040.l11()) ) allowed = (): boolean => { return ( // Step 1 this.passIncomeLimit() && this.validSSNs() && this.allowedFilingStatus() && this.allowedFilling2555() && this.allowedNonresidentAlien() && // Step 2 (this.passInvestmentIncomeLimit() || this.f4797AllowsEIC()) && (!( // Step 3 ( this.filingScheduleE() || !this.passIncomeFromPersonalProperty() || !this.passForm8814() || this.incomeOrLossFromPassiveActivity() ) ) || this.passPub596()) && !( // Step 4 ( this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFJ && this.dependentOfAnother() ) ) && this.credit() > 0 ) } qualifyingDependents = (): Dependent[] => this.f1040.info.taxPayer.dependents .filter( (d) => d.dateOfBirth.getFullYear() >= this.qualifyingCutoffYear || ((d.qualifyingInfo?.isStudent ?? false) && d.dateOfBirth.getFullYear() >= this.qualifyingStudentCutoffYear) ) .sort((d) => d.dateOfBirth.getFullYear()) .slice(0, 3) qualifyingDependentsFilled = (): Array => { const res = this.qualifyingDependents() return _.fill([...res], undefined, res.length, 3) } // EIC line 1 nameFields = (): Array => this.qualifyingDependentsFilled().map( (d) => `${d?.firstName ?? ''} ${d?.lastName ?? ''}` ) // EIC line 2 ssnFields = (): Array => this.qualifyingDependentsFilled().map((d) => d?.ssid) years = (): Array => this.qualifyingDependentsFilled().map((d) => d?.dateOfBirth.getFullYear()) // EIC line 3 birthYearFields = (): Array => this.years().flatMap((year) => { if (year !== undefined) { return String(year).split('') } return [undefined, undefined, undefined, undefined] }) // EIC line 4a: Not handling case of child older than taxpayer ageFields = (): Array => this.years().flatMap((year) => { if (year !== undefined) { const qualifies = year > 1996 return [qualifies, !qualifies] } return [undefined, undefined] }) // TODO: disability disabledFields = (): Array => this.years().flatMap((year) => { if (year === undefined || year < this.qualifyingCutoffYear) { return [undefined, undefined] } return [undefined, undefined] }) // Line 5 // TODO: Address eic relationships relationships = (): Array => this.qualifyingDependentsFilled().map((d) => d?.relationship) // Line 6 numberMonths = (): Array => this.qualifyingDependents().map((d) => d.qualifyingInfo?.numberOfMonths) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, ...this.nameFields(), // 6 ...this.ssnFields(), // 3 ...this.birthYearFields(), // 12 ...this.ageFields(), // 6 ...this.disabledFields(), // 6 ...this.relationships(), ...this.numberMonths() ] } ================================================ FILE: src/forms/Y2020/irsForms/ScheduleR.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' export default class ScheduleR extends F1040Attachment { tag: FormTag = 'f1040sr' sequenceIndex = 999 l22 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/ScheduleSE.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' // TODO export default class ScheduleSE extends F1040Attachment { tag = 'f1040se' sequenceIndex = 999 l6 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2020/irsForms/TaxTable.ts ================================================ import federalBrackets from '../data/federal' import { FilingStatus } from 'ustaxes/core/data' import _ from 'lodash' const computeTax = (brackets: (status: FilingStatus) => number[], rates: number[]) => (filingStatus: FilingStatus, income: number): number => _.chain([0, ...brackets(filingStatus)]) // Low end of each bracket .zipWith( [...brackets(filingStatus), undefined], // top end of each bracket rates.map((r) => r / 100), // rate for each bracket (low, high, rate) => { if (income < low) { // this bracket is above income, no tax here return 0 } else if (high === undefined) { // This is the top bracket return Math.max(0, income - low) * rate } else if (income > high) { // Taxable income is above the top of this bracket // so add the max tax for this bracket return (high - low) * rate } // Otherwise max income is inside this bracket, // add the tax on the amount falling in this bracket return (income - low) * rate } ) .sum() .value() export const computeOrdinaryTax = computeTax( (status) => federalBrackets.ordinary.status[status].brackets, federalBrackets.ordinary.rates ) export const computeLongTermCapGainsTax = computeTax( (status) => federalBrackets.longTermCapGains.status[status].brackets, federalBrackets.longTermCapGains.rates ) ================================================ FILE: src/forms/Y2020/irsForms/index.ts ================================================ import { PDFDocument } from 'pdf-lib' import { create1040 } from '../irsForms/Main' import { Either, isLeft, isRight, right } from 'ustaxes/core/util' import log from 'ustaxes/core/log' import { combinePdfs, PDFDownloader } from 'ustaxes/core/pdfFiller/pdfHandler' import { Information, Asset } from 'ustaxes/core/data' import { F1040Error } from 'ustaxes/forms/errors' import { insertFormDataToPdfs } from 'ustaxes/core/irsForms' export { create1040 } export const create1040PDFs = (state: Information, assets: Asset[]) => async ( downloader: PDFDownloader ): Promise> => { if (state.taxPayer.primaryPerson !== undefined) { const f1040Result = create1040(state, assets) // Get data and pdf links applicable to the model state if (isLeft(f1040Result)) { throw new Error(f1040Result.left.join('\n')) } const [, forms] = f1040Result.right const inserted = await insertFormDataToPdfs(forms, downloader) return right(inserted) } log.error('Attempt to create pdf with no data, will be empty') return right([]) } export const create1040PDF = (state: Information, assets: Asset[]) => async ( downloader: PDFDownloader ): Promise> => { const pdfResult = await create1040PDFs(state, assets)(downloader) if (isRight(pdfResult)) { const pdf = await combinePdfs(pdfResult.right) const bytes = await pdf.save() return right(bytes) } throw new Error(pdfResult.left.join('\n')) } ================================================ FILE: src/forms/Y2020/irsForms/worksheets/ChildTaxCreditWorksheet.ts ================================================ import F1040 from '../../irsForms/F1040' import { Dependent, FilingStatus } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import { QualifyingDependents } from '../../data/federal' export default class ChildTaxCreditWorksheet { f1040: F1040 year = 2020 constructor(f1040: F1040) { this.f1040 = f1040 } qualifiesChild = (d: Dependent): boolean => this.year - d.dateOfBirth.getFullYear() < QualifyingDependents.childMaxAge qualifiesOther = (d: Dependent): boolean => d.qualifyingInfo !== undefined && !this.qualifiesChild(d) && this.year - d.dateOfBirth.getFullYear() < (d.qualifyingInfo.isStudent ? QualifyingDependents.qualifyingDependentMaxAge : QualifyingDependents.qualifyingStudentMaxAge) // worksheet line 1 numberQualifyingChildren = (): number => this.f1040.info.taxPayer.dependents.reduce( (total, dependent) => this.qualifiesChild(dependent) ? total + 1 : total, 0 ) l1 = (): number => this.numberQualifyingChildren() * 2000 // worksheet line 2 numberQualifyingOtherDependents = (): number => this.f1040.info.taxPayer.dependents.reduce( (total, dependent) => this.qualifiesOther(dependent) ? total : total + 1, 0 ) l2 = (): number => this.numberQualifyingOtherDependents() * 500 // worksheet line 3 l3 = (): number => this.l1() + this.l2() // worksheet line 4 l4 = (): number => this.f1040.l11() // worksheet line 5 l5 = (): number => this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ ? 400000 : 200000 // worksheet line 6 l6 = (): number => Math.max(0, this.l4() - this.l5()) // worksheet line 7 l7 = (): number => this.l6() * 0.05 // worksheet line 8 l8 = (): number | undefined => this.l3() > this.l7() ? this.l3() - this.l7() : undefined // worksheet line 9 l9 = (): number => this.f1040.l18() // worksheet line 10 l10 = (): number => sumFields([ this.f1040.schedule3.l1(), this.f1040.schedule3.l2(), this.f1040.schedule3.l3(), this.f1040.schedule3.l4(), this.f1040.f5695?.l30(), this.f1040.f8910?.l15(), this.f1040.f8936?.l23(), this.f1040.scheduleR?.l22() ]) // worksheet line 11 // This maybe should be >= l9 l11 = (): number | undefined => this.l10() === this.l9() ? undefined : this.l9() - this.l10() // worksheet line 13 // if l11 is undefined, returns undefined since they can't take the deduction // if l8 > l11, returns l11 following instructions // Otherwise, returns l8, either because l8 is the deduction or because l8 is undefined and they can't take the deduction l12 = (): number | undefined => this.l11() !== undefined ? Math.min(this.l8() ?? 0, this.l11() ?? 0) : undefined // alias credit = (): number | undefined => this.l12() isAllowed = (): boolean => this.credit() !== undefined } ================================================ FILE: src/forms/Y2020/irsForms/worksheets/Pub596Worksheet1.ts ================================================ import { EIC } from '../../data/federal' import { ifNegative, ifPositive } from 'ustaxes/core/util' import F1040 from '../../irsForms/F1040' import { sumFields } from 'ustaxes/core/irsForms/util' export default class Pub596Worksheet1 { f1040: F1040 constructor(f1040: F1040) { this.f1040 = f1040 } l1 = (): number | undefined => this.f1040.l2b() l2 = (): number | undefined => sumFields([this.f1040.l2a(), this.f1040.f8814?.l1b()]) l3 = (): number | undefined => this.f1040.l3b() // TODO: Unchecked child's Alaska permanent fund dividend l4 = (): number | undefined => this.f1040.schedule1.l8() l5 = (): number => ((this.f1040.l7() ?? 0) < 0 ? 0 : this.f1040.l7() ?? 0) l6 = (): number => { const l7 = this.f1040.f4797?.l7() const l9 = this.f1040.f4797?.l9() if (l7 !== undefined && l7 < 0) { return l9 ?? 0 } return l7 ?? 0 } l7 = (): number => { const diff = this.l5() - this.l6() return diff < 0 ? 0 : diff } l8 = (): number | undefined => sumFields([this.f1040.scheduleE.l23b(), this.f1040.schedule1.l8()]) l9 = (): number | undefined => sumFields([ this.f1040.scheduleE.royaltyExpenses(), this.f1040.schedule1.l22() ]) l10 = (): number => { const diff = (this.l9() ?? 0) - (this.l8() ?? 0) return diff < 0 ? 0 : diff } l11 = (): number | undefined => sumFields([ ifPositive(this.f1040.scheduleE.l26()), this.f1040.scheduleE.l29ah(), this.f1040.scheduleE.l34ad(), this.f1040.scheduleE.l40() // todo: FPA form 4797 line 10 ]) l12 = (): number | undefined => sumFields( [ this.f1040.scheduleE.l26(), this.f1040.scheduleE.l29bg() ?? 0, this.f1040.scheduleE.l34bc() ?? 0, this.f1040.scheduleE.l40() ?? 0 // TODO: PAL Loss form 4797 ].map((x) => ifNegative(x)) ) l13 = (): number | undefined => ifPositive(sumFields([this.l11(), this.l12()])) l14 = (): number | undefined => sumFields([ this.l1(), this.l2(), this.l3(), this.l4(), this.l7(), this.l10(), this.l13() ]) l15 = (): boolean => (this.l14() ?? 0) > EIC.maxInvestmentIncome precludesEIC = (): boolean => this.l15() } ================================================ FILE: src/forms/Y2020/irsForms/worksheets/SDQualifiedAndCapGains.ts ================================================ // Reference implementation for ltcg and cap gains worksheet import { FilingStatus } from 'ustaxes/core/data' import federalBrackets from '../../data/federal' import { computeOrdinaryTax } from '../../irsForms/TaxTable' import F1040 from '../F1040' export interface TestData { qualDiv: number taxableIncome: number f1040l7: number | undefined sdl15: number | undefined sdl16: number | undefined sdl18: number | undefined sdl19: number | undefined filingStatus: FilingStatus } type Bracket = [number, number] type Cutoffs = { [key in FilingStatus]: Bracket } const cutoffAmounts: Cutoffs = { [FilingStatus.S]: [40000, 441450], [FilingStatus.MFJ]: [80000, 496600], [FilingStatus.MFS]: [40000, 441450], [FilingStatus.W]: [80000, 496600], [FilingStatus.HOH]: [53600, 469050] } export default class QualDivAndCGWorksheetReference { [k: string]: TestData | (() => number) data: TestData constructor(f1040: F1040) { const filingStatus = f1040.info.taxPayer.filingStatus this.data = { qualDiv: f1040.l3a() ?? 0, taxableIncome: f1040.l15(), f1040l7: f1040.l7(), sdl15: f1040.scheduleD.l15(), sdl16: f1040.scheduleD.l16(), sdl18: f1040.scheduleD.l18(), sdl19: f1040.scheduleD.l19(), filingStatus } } // 1. Enter the amount from Form 1040 or 1040-SR, line 15. However, if you are filing Form 2555 (relating to foreign earned income), enter the amount from line 3 of the Foreign Earned Income Tax Worksheet l1 = (): number => this.data.taxableIncome // 2. Enter the amount from Form 1040 or 1040-SR, line 3a* l2 = (): number => this.data.qualDiv // 3. Are you filing Schedule D?* // Yes. Enter the smaller of line 15 or 16 of Schedule D. If either line 15 or 16 is blank or a loss, enter -0-. 3. // No. Enter the amount from Form 1040 or 1040-SR, line 7. // Either way, it's the smaller of LTCG or total capital gain. l3 = (): number => Math.min( Math.max(this.data.sdl15 ?? 0, 0), Math.max(this.data.sdl16 ?? 0, 0) ) // 4. Add lines 2 and 3: LTCG + QDIV l4 = (): number => this.l2() + this.l3() // 5. Subtract line 4 from line 1. If zero or less, enter -0- l5 = (): number => Math.max(this.l1() - this.l4(), 0) // 6. Enter: // $40,000 if single or married filing separately, // $80,000 if married filing jointly or qualifying widow(er),$53,600 if head of household. l6 = (): number => cutoffAmounts[this.data.filingStatus][0] // 7. Enter the smaller of line 1 or line 6 l7 = (): number => Math.min(this.l1(), this.l6()) // 8. Enter the smaller of line 5 or line 7 l8 = (): number => Math.min(this.l5(), this.l7()) // 9. Subtract line 8 from line 7. This amount is taxed at 0% l9 = (): number => this.l7() - this.l8() // 10. Enter the smaller of line 1 or line 4 l10 = (): number => Math.min(this.l1(), this.l4()) // 11. Enter the amount from line 9 l11 = (): number => this.l9() // 12. Subtract line 11 from line 10 l12 = (): number => this.l10() - this.l11() // 13. Enter: // $441,450 if single,$248,300 if married filing separately,$496,600 if married filing jointly or qualifying widow(er),$469,050 if head of household. // l13 = (): number => cutoffAmounts[this.data.filingStatus][1] // 14. Enter the smaller of line 1 or line 13 l14 = (): number => Math.min(this.l1(), this.l13()) // 15. Add lines 5 and 9 l15 = (): number => this.l5() + this.l9() // 16. Subtract line 15 from line 14. If zero or less, enter -0- l16 = (): number => Math.max(this.l14() - this.l15(), 0) // 17. Enter the smaller of line 12 or line 16 l17 = (): number => Math.min(this.l12(), this.l16()) // 18. Multiply line 17 by 15% (0.15) l18 = (): number => (this.l17() * federalBrackets.longTermCapGains.rates[1]) / 100 // 19. Add lines 9 and 17 l19 = (): number => this.l9() + this.l17() // 20. Subtract line 19 from line 10 l20 = (): number => this.l10() - this.l19() // 21. Multiply line 20 by 20% (0.20) l21 = (): number => (this.l20() * federalBrackets.longTermCapGains.rates[2]) / 100 // 22. Figure the tax on the amount on line 5. If the amount on line 5 is less than $100,000, use the Tax Table to figure the tax. If the amount on line 5 is $100,000 or more, use the Tax Computation Worksheet l22 = (): number => computeOrdinaryTax(this.data.filingStatus, this.l5()) // 23. Add lines 18, 21, and 22 l23 = (): number => this.l18() + this.l21() + this.l22() // 24. Figure the tax on the amount on line 1. If the amount on line 1 is less than $100,000, use the Tax Table to figure the tax. If the amount on line 1 is $100,000 or more, use the Tax Computation Worksheet l24 = (): number => computeOrdinaryTax(this.data.filingStatus, this.l1()) // 25. Tax on all taxable income. Enter the smaller of line 23 or 24. Also include this amount on the entry space on Form 1040 or 1040-SR, line 16. If you are filing Form 2555, don’t enter this amount on the entry space on Form 1040 or 1040-SR, line 16. Instead, enter it on line 4 of the Foreign Earned Income Tax Worksheet l25 = (): number => Math.min(this.l23(), this.l24()) tax = (): number => this.l25() } ================================================ FILE: src/forms/Y2020/irsForms/worksheets/SDRateGainWorksheet.ts ================================================ export default class SDRateGainWorksheet { l7 = (): number | undefined => undefined } ================================================ FILE: src/forms/Y2020/irsForms/worksheets/SDUnrecaptured1250.ts ================================================ export default class SDUnrecaptured1250 { l18 = (): number | undefined => undefined } ================================================ FILE: src/forms/Y2020/irsForms/worksheets/ScheduleDTaxWorksheet.ts ================================================ // Reference implementation for Schedule D Tax Worksheet import { FilingStatus } from 'ustaxes/core/data' import { computeOrdinaryTax } from '../../irsForms/TaxTable' export interface TestData { qualDiv: number taxableIncome: number f4952l4g: number f4952l4e: number sdl15: number sdl16: number sdl18: number sdl19: number filingStatus: FilingStatus } type Bracket = [number, number, number] type Cutoffs = { [key in FilingStatus]: Bracket } const cutoffAmounts: Cutoffs = { [FilingStatus.S]: [40000, 163300, 441450], [FilingStatus.MFJ]: [80000, 326600, 496600], [FilingStatus.MFS]: [40000, 163300, 441450], [FilingStatus.W]: [80000, 326600, 496600], [FilingStatus.HOH]: [53600, 163300, 469050] } export default class LTCGQualDivReference { [k: string]: TestData | (() => number) data: TestData constructor(data: TestData) { this.data = data } // 1. Enter your taxable income from Form 1040, 1040-SR, or 1040-NR, line 15. (However, if you are filing Form 2555 (relating to foreign earned income), enter instead the amount from line 3 of the Foreign Earned Income Tax Worksheet in the instructions for Forms 1040 and 1040-SR, line 16) l1 = (): number => this.data.taxableIncome // 2. Enter your qualified dividends from Form 1040, 1040-SR, or 1040-NR, line 3a l2 = (): number => this.data.qualDiv // 3. Enter the amount from Form 4952 (used to figure investment interest expense deduction), line 4g l3 = (): number => this.data.f4952l4g // 4. Enter the amount from Form 4952, line 4e* l4 = (): number => this.data.f4952l4e // 5. Subtract line 4 from line 3. If zero or less, enter -0- l5 = (): number => Math.max(0, this.l3() - this.l4()) // 6. Subtract line 5 from line 2. If zero or less, enter -0-** l6 = (): number => Math.max(0, this.l2() - this.l5()) // 7. Enter the smaller of line 15 or line 16 of Schedule D l7 = (): number => Math.min(this.data.sdl15, this.data.sdl16) // 8. Enter the smaller of line 3 or line 4 l8 = (): number => Math.min(this.l3(), this.l4()) // 9. Subtract line 8 from line 7. If zero or less, enter -0-** l9 = (): number => Math.max(0, this.l7() - this.l8()) // 10. Add lines 6 and 9 l10 = (): number => this.l6() + this.l9() // 11. Add lines 18 and 19 of Schedule D** l11 = (): number => this.data.sdl18 + this.data.sdl19 // 12. Enter the smaller of line 9 or line 11 l12 = (): number => Math.min(this.l9(), this.l11()) // 13. Subtract line 12 from line 10 l13 = (): number => this.l10() - this.l12() // 14. Subtract line 13 from line 1. If zero or less, enter -0- l14 = (): number => Math.max(0, this.l1() - this.l13()) // 15. Enter: l15 = (): number => cutoffAmounts[this.data.filingStatus][0] // 16. Enter the smaller of line 1 or line 15 l16 = (): number => Math.min(this.l1(), this.l15()) // 17. Enter the smaller of line 14 or line 16 l17 = (): number => Math.min(this.l14(), this.l16()) // 18. Subtract line 10 from line 1. If zero or less, enter -0- l18 = (): number => Math.max(0, this.l1() - this.l10()) // 19. Enter the smaller of line 1 or [ltcg bracket 2] l19 = (): number => Math.min(this.l1(), cutoffAmounts[this.data.filingStatus][1]) // 20. Enter the smaller of line 14 or line 19 l20 = (): number => Math.min(this.l14(), this.l19()) // 21. Enter the larger of line 18 or line 20 l21 = (): number => Math.max(this.l18(), this.l20()) // 22. Subtract line 17 from line 16. This amount is taxed at 0%. l22 = (): number => Math.max(0, this.l16() - this.l17()) // If lines 1 and 16 are the same, skip lines 23 through 43 and go to line 44. Otherwise, go to line 23. // 23. Enter the smaller of line 1 or line 13 l23 = (): number => Math.min(this.l1(), this.l13()) // 24. Enter the amount from line 22. (If line 22 is blank, enter -0-.) l24 = (): number => this.l22() // 25. Subtract line 24 from line 23. If zero or less, enter -0- l25 = (): number => Math.max(0, this.l23() - this.l24()) // 26. Enter top bracket amount l26 = (): number => cutoffAmounts[this.data.filingStatus][1] // 27. Enter the smaller of line 1 or line 26 l27 = (): number => Math.min(this.l1(), this.l26()) // 28. Add lines 21 and 22 l28 = (): number => this.l21() + this.l22() // 29. Subtract line 28 from line 27. If zero or less, enter -0- l29 = (): number => Math.max(0, this.l27() - this.l28()) // 30. Enter the smaller of line 25 or line 29 l30 = (): number => Math.min(this.l25(), this.l29()) // 31. Multiply line 30 by 15% (0.15) l31 = (): number => this.l30() * 0.15 // 32. Add lines 24 and 30 l32 = (): number => this.l24() + this.l30() // If lines 1 and 32 are the same, skip lines 33 through 43 and go to line 44. Otherwise, go to line 33. // 33. Subtract line 32 from line 23 l33 = (): number => Math.max(0, this.l23() - this.l32()) // 34. Multiply line 33 by 20% (0.20) l34 = (): number => this.l33() * 0.2 // If Schedule D, line 19, is zero or blank, skip lines 35 through 40 and go to line 41. Otherwise, go to line 35. // 35. Enter the smaller of line 9 above or Schedule D, line 19 l35 = (): number => Math.min(this.l9(), this.data.sdl19) // 36. Add lines 10 and 21 l36 = (): number => this.l10() + this.l21() // 37. Enter the amount from line 1 above l37 = (): number => this.l1() // 38. Subtract line 37 from line 36. If zero or less, enter -0- l38 = (): number => Math.max(0, this.l36() - this.l37()) // 39. Subtract line 38 from line 35. If zero or less, enter -0- l39 = (): number => Math.max(0, this.l35() - this.l38()) // 40. Multiply line 39 by 25% (0.25) l40 = (): number => this.l39() * 0.25 // If Schedule D, line 18, is zero or blank, skip lines 41 through 43 and go to line 44. Otherwise, go to line 41. // 41. Add lines 21, 22, 30, 33, and 39 l41 = (): number => this.l21() + this.l22() + this.l30() + this.l33() + this.l39() // 42. Subtract line 41 from line 1 l42 = (): number => Math.max(0, this.l1() - this.l41()) // 43. Multiply line 42 by 28% (0.28) l43 = (): number => this.l42() * 0.28 // 44. Figure the tax on the amount on line 21. If the amount on line 21 is less than $100,000, use the Tax Table to l44 = (): number => computeOrdinaryTax(this.data.filingStatus, this.l21()) // 45. Add lines 31, 34, 40, 43, and 44 l45 = (): number => this.l31() + this.l34() + this.l40() + this.l43() + this.l44() // 46. Figure the tax on the amount on line 1. If the amount on line 1 is less than $100,000, use the Tax Table to l46 = (): number => computeOrdinaryTax(this.data.filingStatus, this.l1()) // figure the tax. If the amount on line 1 is $100,000 or more, use the Tax Computation Worksheet // 47. Tax on all taxable income (including capital gains and qualified dividends). Enter the smaller of line 45 // or line 46. Also, include this amount on Form 1040, 1040-SR, or 1040-NR, line 16. (If you are filing Form // 2555, don't enter this amount on Form 1040 or 1040-SR, line 16. Instead, enter it on line 4 of the Foreign // Earned Income Tax Worksheet in the Instructions for Forms 1040 and 1040-SR) l47 = (): number => Math.round(Math.min(this.l45(), this.l46())) } export const showReference = (r: LTCGQualDivReference): string => Array.from(Array(47)) .map((_, i) => `l${i + 1}`) .map((x) => [x, (r[x] as () => number)()]) .map(([l, v]) => `${l}: ${v}`) .join('\n') ================================================ FILE: src/forms/Y2020/irsForms/worksheets/SocialSecurityBenefits.ts ================================================ import { FilingStatus, TaxPayer, Information } from 'ustaxes/core/data' import F1040 from '../F1040' import { sumFields } from 'ustaxes/core/irsForms/util' import { SSBenefits } from '../../data/federal' export default class SocialSecurityBenefitsWorksheet { tp: TaxPayer f1040: F1040 constructor(state: Information, f1040: F1040) { this.f1040 = f1040 this.tp = state.taxPayer } totalNetBenefits = (): number => this.f1040.f1099ssas().reduce((sum, f) => sum + f.form.netBenefits, 0) /* Enter the total amount from box 5 of all your Forms SSA-1099 and RRB-1099. Also enter this amount on Form 1040 or 1040-SR, line 6a */ l1 = (): number => this.totalNetBenefits() // Multiply line 1 by 50% (0.50) l2 = (): number => this.l1() / 2 /* If you are not excluding unemployment compensation from income, combine the amounts from Form 1040 or 1040-SR, lines 1, 2b, 3b, 4b, 5b, 7, and 8. If you are excluding unemployment compensation from income, combine the amounts from Form 1040 or 1040-SR , lines 1, 2b, 3b, 4b, 5b, 7, Schedule 1, lines 1 through 7, and line 3 of the Unemployment Compensation Exclusion Worksheet */ l3 = (): number => sumFields([ this.f1040.l1(), this.f1040.l2b(), this.f1040.l3b(), this.f1040.l4b(), this.f1040.l5b(), this.f1040.l7(), this.f1040.l8() ]) // Enter the amount, if any, from Form 1040 or 1040-SR, line 2a l4 = (): number | undefined => this.f1040.l2a() // Combine lines 2, 3, and 4 l5 = (): number => sumFields([this.l2(), this.l3(), this.l4()]) /* Enter the total of the amounts from Form 1040 or 1040-SR, line 10b, Schedule 1, lines 10 through 19, plus any write-in adjustments you entered on the dotted line next to Schedule 1, line 22 */ l6 = (): number => sumFields([ this.f1040.l10b(), this.f1040.schedule1.l10(), this.f1040.schedule1.l11(), this.f1040.schedule1.l12(), this.f1040.schedule1.l13(), this.f1040.schedule1.l14(), this.f1040.schedule1.l15(), this.f1040.schedule1.l16(), this.f1040.schedule1.l17(), this.f1040.schedule1.l18(), this.f1040.schedule1.l19(), this.f1040.schedule1.l22writeIn() ]) /* Line 7: Is the amount on line 6 less than the amount on line 5? If No, None of your social security benefits are taxable. Enter -0- on Form 1040 or 1040-SR, line 6b. If Yes, Subtract line 6 from line 5 */ l7 = (): number => { if (this.l6() < this.l5()) { return this.l5() - this.l6() } else { return 0 } } /* If you are: Married filing jointly, enter $32,000 Single, head of household, qualifying widow(er), or married filing separately and you lived apart from your spouse for all of the year, enter $25,000 Married filing separately and you lived with your spouse at any time in the year, skip lines 8 through 15; multiply line 7 by 85% (0.85) and enter the result on line 16. Then, go to line 17 */ l8 = (): number => { if (this.tp.filingStatus == undefined) { return 0 } else if (this.tp.filingStatus == FilingStatus.MFS) { // treat Married filing separately specially due to the extra question below // and resulting logic in the worksheet if (this.f1040.info.questions.LIVE_APART_FROM_SPOUSE) { return SSBenefits.caps[this.tp.filingStatus].l8 } else { // Note that this value won't be taken into account. Instead, // the line 16 function will also check for this and perform // the right math. return 0 } } else { return SSBenefits.caps[this.tp.filingStatus].l8 } } /* Is the amount on line 8 less than the amount on line 7? If No, None of your social security benefits are taxable. Enter -0- on Form 1040 or 1040-SR, line 6b. If you are married filing separately and you lived apart from your spouse for all of 2020, be sure you entered "D" to the right of the word "benefits" on line 6a. If Yes, Subtract line 8 from line 7. */ l9 = (): number => { if (this.l8() < this.l7()) { return this.l7() - this.l8() } else { return 0 } } /* Enter: $12,000 if married filing jointly; $9,000 if single, head of household, qualifying widow(er), or married filing separately and you lived apart from your spouse for all of 2020 */ l10 = (): number => { if (this.tp.filingStatus == undefined) { return 0 } return SSBenefits.caps[this.tp.filingStatus].l10 } // Subtract line 10 from line 9. If zero or less, enter -0- l11 = (): number => { const tmp = this.l9() - this.l10() if (tmp < 0) { return 0 } else { return tmp } } // Enter the smaller of line 9 or line 10 l12 = (): number => Math.min(this.l9(), this.l10()) // Enter one-half of line 12 l13 = (): number => this.l12() / 2 // Enter the smaller of line 2 or line 13 l14 = (): number => Math.min(this.l13(), this.l2()) // Multiply line 11 by 85% (0.85). If line 11 is zero, enter -0- l15 = (): number => { if (this.l11() == 0) { return 0 } else { return this.l11() * 0.85 } } // Add lines 14 and 15 l16 = (): number => { // From line 7 instructions: // Married filing separately and you lived with your spouse at any time // in 2020, skip lines 8 through 15; multiply line 7 by 85% (0.85) and // enter the result on line 16. Then, go to line 17 if ( this.tp.filingStatus == FilingStatus.MFS && !this.f1040.info.questions.LIVE_APART_FROM_SPOUSE ) { return this.l7() * 0.85 } else { return sumFields([this.l14(), this.l15()]) } } // Multiply line 1 by 85% (0.85) l17 = (): number => this.l1() * 0.85 // Taxable social security benefits. Enter the smaller of line 16 or line 17. // Also enter this amount on Form 1040 or 1040-SR, line 6b l18 = (): number => Math.min(this.l16(), this.l17()) // This is the function used to return the taxable amount of the social security // benefits to be entered in line 6b of 1040. It takes into account the various // stopping points in the worksheet. taxableAmount = (): number => { const line7 = this.l7() if (line7 == 0) { return line7 } const line9 = this.l9() if (line9 == 0) { return line9 } return this.l18() } } ================================================ FILE: src/forms/Y2020/irsForms/worksheets/StudentLoanInterestWorksheet.ts ================================================ import { F1098e, FilingStatus } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import F1040 from '../../irsForms/F1040' export default class StudentLoanInterestWorksheet { f1040: F1040 f1098es: F1098e[] constructor(f1040: F1040, f1098es: F1098e[]) { this.f1040 = f1040 this.f1098es = f1098es } // Can't take deduction if filling Married Filling Seperate notMFS = (): boolean => this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFS // Can't take deduction if MFJ and spouse is a dependent isNotDependentSpouse = (): boolean => this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFJ || this.f1040.info.taxPayer.spouse === undefined || !this.f1040.info.taxPayer.spouse.isTaxpayerDependent // Can't take deduction if someone else claims you as a dependent isNotDependentSelf = (): boolean => !this.f1040.info.taxPayer.primaryPerson.isTaxpayerDependent isNotDependent = (): boolean => this.isNotDependentSpouse() && this.isNotDependentSelf() // Sum interest, but maximum of 2500 can be deducted l1 = (): number => Math.min( this.f1098es.reduce((sum, f) => sum + f.interest, 0), 2500 ) // Currently do not support unemployment compensation exclusion // TO DO: add unemployment compensation exclusion l2 = (): number => this.f1040.l9() // Schedule 1 deductions l3 = (): number => sumFields([ this.f1040.schedule1.l10(), this.f1040.schedule1.l11(), this.f1040.schedule1.l12(), this.f1040.schedule1.l13(), this.f1040.schedule1.l14(), this.f1040.schedule1.l15(), this.f1040.schedule1.l16(), this.f1040.schedule1.l17(), this.f1040.schedule1.l18(), this.f1040.schedule1.l19(), this.f1040.schedule1.l22writeIn() ]) l4 = (): number => Math.max(0, this.l2() - this.l3()) l5 = (): number => this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ ? 140000 : 70000 l6 = (): number => Math.max(0, this.l4() - this.l5()) l7 = (): number => Math.min(this.l6() / 15000, 1) l8 = (): number => this.l1() * this.l7() l9 = (): number | undefined => this.notMFS() && this.isNotDependent() ? Math.max(0, this.l1() - this.l8()) : undefined } ================================================ FILE: src/forms/Y2020/stateForms/AK/Form.ts ================================================ export default class AKForm {} ================================================ FILE: src/forms/Y2020/stateForms/AL/Form.ts ================================================ export default class ALForm {} ================================================ FILE: src/forms/Y2020/stateForms/AR/Form.ts ================================================ export default class ARForm {} ================================================ FILE: src/forms/Y2020/stateForms/AZ/Form.ts ================================================ export default class AZForm {} ================================================ FILE: src/forms/Y2020/stateForms/CA/Form.ts ================================================ export default class CAForm {} ================================================ FILE: src/forms/Y2020/stateForms/CO/Form.ts ================================================ export default class COForm {} ================================================ FILE: src/forms/Y2020/stateForms/CT/Form.ts ================================================ export default class CTForm {} ================================================ FILE: src/forms/Y2020/stateForms/DC/Form.ts ================================================ export default class DCForm {} ================================================ FILE: src/forms/Y2020/stateForms/DE/Form.ts ================================================ export default class DEForm {} ================================================ FILE: src/forms/Y2020/stateForms/FL/Form.ts ================================================ export default class FLForm {} ================================================ FILE: src/forms/Y2020/stateForms/GA/Form.ts ================================================ export default class GAForm {} ================================================ FILE: src/forms/Y2020/stateForms/HI/Form.ts ================================================ export default class HIForm {} ================================================ FILE: src/forms/Y2020/stateForms/IA/Form.ts ================================================ export default class IAForm {} ================================================ FILE: src/forms/Y2020/stateForms/ID/Form.ts ================================================ export default class IDForm {} ================================================ FILE: src/forms/Y2020/stateForms/IL/IL1040.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field, RadioSelect } from 'ustaxes/core/pdfFiller' import { sumFields } from 'ustaxes/core/irsForms/util' import { AccountType, FilingStatus, State } from 'ustaxes/core/data' import parameters from './Parameters' import { IL1040scheduleileeic } from './IL1040ScheduleILEIC' import IL1040V from './IL1040V' import { ILWIT } from './ILWit' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class IL1040 extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State scheduleEIC: IL1040scheduleileeic il1040V: IL1040V formOrder = 0 methods: FormMethods constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'IL-1040' this.state = 'IL' this.scheduleEIC = new IL1040scheduleileeic(this.info, f1040) this.il1040V = new IL1040V(this.info, f1040, this) this.methods = new FormMethods(this) } attachments = (): Form[] => { const pmt = this.payment() const result: Form[] = [] if ((pmt ?? 0) > 0) { result.push(this.il1040V) } if (this.scheduleEIC.isRequired()) { result.push(this.scheduleEIC) } if (this.methods.stateWithholding() > 0) { const ilwit = new ILWIT(this.f1040) result.push(ilwit) ilwit.attachments().forEach((f) => result.push(f)) } return result } /** * Index 0: Help */ Help = (): string | undefined => { return undefined } f0 = (): string | undefined => this.Help() /** * Index 1: month */ month = (): string | undefined => { return undefined } f1 = (): string | undefined => this.month() /** * Index 2: year */ year = (): string | undefined => { return undefined } f2 = (): string | undefined => this.year() /** * Index 3: name1 */ name1 = (): string | undefined => this.info.taxPayer.primaryPerson.firstName f3 = (): string | undefined => this.name1() /** * Index 4: name2 */ name2 = (): string | undefined => this.info.taxPayer.primaryPerson.lastName f4 = (): string | undefined => this.name2() /** * Index 5: YoB * TODO - year of birth */ YoB = (): string | undefined => undefined f5 = (): string | undefined => this.YoB() /** * Index 6: ssn1 */ ssn1 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(0, 3) f6 = (): string | undefined => this.ssn1() /** * Index 7: ssn2 */ ssn2 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(3, 5) f7 = (): string | undefined => this.ssn2() /** * Index 8: ssn3 */ ssn3 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(5) f8 = (): string | undefined => this.ssn3() /** * Index 9: name3 */ name3 = (): string | undefined => this.info.taxPayer.spouse?.firstName f9 = (): string | undefined => this.name3() /** * Index 10: name4 */ name4 = (): string | undefined => this.info.taxPayer.spouse?.lastName f10 = (): string | undefined => this.name4() /** * Index 11: SpYoB * TODO: spouse birth year */ SpYoB = (): string | undefined => undefined f11 = (): string | undefined => this.SpYoB() /** * Index 12: ssn4 */ ssn4 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(0, 3) f12 = (): string | undefined => this.ssn4() /** * Index 13: ssn5 */ ssn5 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(3, 5) f13 = (): string | undefined => this.ssn5() /** * Index 14: ssn6 */ ssn6 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(5) f14 = (): string | undefined => this.ssn6() /** * Index 15: address */ address = (): string | undefined => this.info.taxPayer.primaryPerson.address.address f15 = (): string | undefined => this.address() /** * Index 16: apt */ apt = (): string | undefined => this.info.taxPayer.primaryPerson.address.aptNo f16 = (): string | undefined => this.apt() /** * Index 17: County * TODO - county */ County = (): string | undefined => undefined f17 = (): string | undefined => this.County() /** * Index 18: city */ city = (): string | undefined => this.info.taxPayer.primaryPerson.address.city f18 = (): string | undefined => this.city() /** * Index 19: st */ st = (): string | undefined => this.info.taxPayer.primaryPerson.address.state ?? this.info.taxPayer.primaryPerson.address.province f19 = (): string | undefined => this.st() /** * Index 20: zip */ zip = (): string | undefined => this.info.taxPayer.primaryPerson.address.zip f20 = (): string | undefined => this.zip() /** * Index 21: foreign */ foreign = (): string | undefined => this.info.taxPayer.primaryPerson.address.foreignCountry f21 = (): string | undefined => this.foreign() /** * Index 22: Check Box1 * This is actually a radio group, so indicate the correct selection * by index. */ CheckBox1 = (): RadioSelect | undefined => ({ select: [ FilingStatus.S, FilingStatus.MFJ, FilingStatus.MFS, FilingStatus.W, FilingStatus.HOH ].findIndex((x) => x === this.info.taxPayer.filingStatus) }) f22 = (): RadioSelect | undefined => this.CheckBox1() /** * Index 23: Check Box1c */ CheckBox1c = (): boolean | undefined => this.info.taxPayer.primaryPerson.isTaxpayerDependent f23 = (): boolean | undefined => this.CheckBox1c() /** * Index 24: Check Box1cc */ CheckBox1cc = (): boolean | undefined => this.info.taxPayer.spouse?.isTaxpayerDependent f24 = (): boolean | undefined => this.CheckBox1cc() /** * Index 25: Check Box7 * TODO - nonresident, part year */ CheckBox7 = (): boolean | undefined => undefined f25 = (): boolean | undefined => this.CheckBox7() /** * Index 26: 1 */ l1 = (): number | undefined => this.f1040.l11() /** * Index 27: 2 */ l2 = (): number | undefined => this.f1040.l2a() /** * Index 28: 3 * TODO: Schedule M, other additions */ l3 = (): number | undefined => undefined /** * Index 29: 4 */ l4 = (): number => sumFields([this.l1(), this.l2(), this.l3()]) /** * Index 30: 5 * TODO - ss benefits and certain retirement plan income received if included in line 1, attach p1 of federal return */ l5 = (): number | undefined => undefined /** * Index 31: 6 * TODO IL income tax overpayment included in federal form 1040 S1 L1 */ l6 = (): number | undefined => undefined /** * Index 32: 7 * TODO: other subtractions, attach Schedule M */ l7 = (): number | undefined => undefined /** * Index 33: Check Box2 * Check if L7 includes any amount from Schedule 1299-C */ CheckBox2 = (): boolean | undefined => undefined f33 = (): boolean | undefined => this.CheckBox2() /** * Index 34: 8 */ l8 = (): number => sumFields([this.l5(), this.l6(), this.l7()]) /** * Index 35: 9 * IL base income */ l9 = (): number => Math.max(0, this.l4() - this.l8()) /** * Index 36: 10a */ l10a = (): number => { if (this.info.taxPayer.filingStatus === FilingStatus.MFJ) return parameters.exemptions.MFJ.exemptionAmount return parameters.exemptions.S.exemptionAmount } /** * Index 37: Check Box3 * Check if TP senior */ primarySenior = (): boolean | undefined => undefined f37 = (): boolean | undefined => this.primarySenior() /** * Index 38: Check Box4 * Check if spouse senior */ spouseSenior = (): boolean | undefined => undefined f38 = (): boolean | undefined => this.spouseSenior() /** * Index 39: 10b */ l10b = (): number => [this.primarySenior() ?? false, this.spouseSenior() ?? false].filter( (x) => x ).length * parameters.seniorExemption /** * Index 40: Check Box5 * TODO: TP blind */ CheckBox5 = (): boolean | undefined => undefined f40 = (): boolean | undefined => this.CheckBox5() /** * Index 41: Check Box6 * TODO: Spouse blind */ CheckBox6 = (): boolean | undefined => undefined f41 = (): boolean | undefined => this.CheckBox6() /** * Index 42: 10c */ l10c = (): number | undefined => [this.CheckBox5(), this.CheckBox6()].filter((x) => x).length * parameters.blindExemption /** * Index 43: 10d * TODO: Schedule EC step 2, line 1 */ l10d = (): number | undefined => undefined /** * Index 44: 10 * Net income */ l10 = (): number => sumFields([this.l10a(), this.l10b(), this.l10c(), this.l10d()]) /** * Index 45: 11 * TODO: handle non-residents, part year residents */ l11 = (): number => Math.max(0, this.l9() - this.l10()) /** * Index 46: 12 */ l12 = (): number => this.l11() * parameters.taxRate /** * Index 47: 13 * TODO: recapture investment tax credits, schedule 4255 */ l13 = (): number | undefined => undefined /** * Index 48: 14 * Income tax */ l14 = (): number => sumFields([this.l12(), this.l13()]) /** * Index 49: 15 * TODO: income tax paid to another state while IL resident */ l15 = (): number | undefined => undefined /** * Index 50: 16 * Property tax and K12 education expense credit amount from Schedule ICR */ l16 = (): number | undefined => undefined /** * Index 51: 17 * TODO: Credit amount from Schedule 1299-C Attach 1299-C */ l17 = (): number | undefined => undefined /** * Index 52: 18 * Total credits */ l18 = (): number => sumFields([this.l15(), this.l16(), this.l17()]) /** * Index 53: 19 * Total non-refundable credits */ l19 = (): number => Math.max(0, this.l14() - this.l18()) /** * Index 54: 20 * TODO: Household employment tax */ l20 = (): number | undefined => undefined /** * Index 55: 21 * TODO: Use tax on internet, mail order, or other out-of-state purchases */ l21 = (): number | undefined => undefined /** * Index 56: 22 * TODO: Compassionate use of medical cannabis program act and sale of assets by gaming licensee */ l22 = (): number | undefined => undefined /** * Index 57: 23 * Total tax */ l23 = (): number => sumFields([this.l19(), this.l20(), this.l21(), this.l22()]) /** * Index 58: 24 */ l24 = (): number => this.l23() /** * Index 59: 25 * IL Income tax withheld */ l25 = (): number | undefined => this.methods.witholdingForState('IL') /** * Index 60: 26 * TODO: Estimated tax payments */ l26 = (): number | undefined => undefined /** * Index 61: 27 * Pass-through withholding */ l27 = (): number | undefined => undefined /** * Index 62: 28 * Earned income credit from Schedule IL-E/EIC, Step 4, line 8 */ l28 = (): number | undefined => this.scheduleEIC.earnedIncomeCredit() /** * Index 63: 29 * Total payments and refundable credits */ l29 = (): number => sumFields([this.l25(), this.l26(), this.l27(), this.l28()]) /** * Index 64: 30 */ l30 = (): number => Math.max(0, this.l29() - this.l24()) /** * Index 65: 31 */ l31 = (): number => Math.max(0, this.l24() - this.l29()) /** * Index 66: 32 * TODO: late payment penalty for underpayment of estimated tax */ l32 = (): number | undefined => undefined /** * Index 67: Check Box8a * TODO: 2/3 of income from farming */ CheckBox8a = (): boolean | undefined => undefined f67 = (): boolean | undefined => this.CheckBox8a() /** * Index 68: Check Box8b * TODO: You or spouse is 65 or older and permanently in nursing home */ CheckBox8b = (): boolean | undefined => undefined f68 = (): boolean | undefined => this.CheckBox8b() /** * Index 69: Check Box8c * TODO: Income not received evenly during the year and you annualized your income on IL-2210 */ CheckBox8c = (): boolean | undefined => undefined f69 = (): boolean | undefined => this.CheckBox8c() /** * Index 70: Check Box8d * TODO: Check if you were not required to file an IL individual tax return in the previous tax year. */ CheckBox8d = (): boolean | undefined => undefined f70 = (): boolean | undefined => this.CheckBox8d() /** * Index 71: 33 * TODO: Voluntary charitable contributions */ l33 = (): number | undefined => undefined /** * Index 72: 34 */ l34 = (): number => sumFields([this.l32(), this.l33()]) /** * Index 73: 35 * If you have an amount on L30 and this amount is greater than L34, this is your overpayment */ l35 = (): number => Math.max(0, this.l30() - this.l34()) /** * Index 74: 36 * Amount you want refunded to you * TODO: assuming payer wants all refunded. */ l36 = (): number => this.l35() /** * Index 75: rn1 */ rn1 = (): string | undefined => this.info.refund?.routingNumber f75 = (): string | undefined => this.rn1() /** * Index 76: Check Box11 * TODO: support savings account checkbox - radio field issue */ CheckBox11 = (): boolean | undefined => this.info.refund?.accountType === AccountType.checking f76 = (): boolean | undefined => this.CheckBox11() /** * Index 77: ac1 */ ac1 = (): string | undefined => this.info.refund?.accountNumber f77 = (): string | undefined => this.ac1() /** * Index 78: Refund Method * TODO: radio issue, only handling direct deposit. */ RefundMethod = (): boolean | undefined => true f78 = (): boolean | undefined => this.RefundMethod() /** * Index 79: 38 * TODO: not supporting credit forward */ l38 = (): number | undefined => undefined /** * Index 80: 39 */ l39 = (): number | undefined => { const l31 = this.l31() const l34 = this.l34() const l30 = this.l30() if (l31 > 0) return l31 + l34 if (l30 > 0 && l30 < l34) return l34 - l30 } payment = (): number | undefined => this.l39() // Signature block fields omitted fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.l1(), this.l2(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.f33(), this.l8(), this.l9(), this.l10a(), this.f37(), this.f38(), this.l10b(), this.f40(), this.f41(), this.l10c(), this.l10d(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18(), this.l19(), this.l20(), this.l21(), this.l22(), this.l23(), this.l24(), this.l25(), this.l26(), this.l27(), this.l28(), this.l29(), this.l30(), this.l31(), this.l32(), this.f67(), this.f68(), this.f69(), this.f70(), this.l33(), this.l34(), this.l35(), this.l36(), this.f75(), this.f76(), this.f77(), this.f78(), this.l38(), this.l39() ] } const makeIL1040 = (f1040: F1040): IL1040 => new IL1040(f1040) export default makeIL1040 ================================================ FILE: src/forms/Y2020/stateForms/IL/IL1040ScheduleILEIC.ts ================================================ import Form from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { Dependent, PrimaryPerson, State } from 'ustaxes/core/data' import parameters from './Parameters' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class IL1040scheduleileeic extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State formOrder = 1 attachments: () => Form[] = () => [] qualifyingDependents: Dependent[] constructor(info: ValidatedInformation, f1040: F1040) { super() this.info = info this.f1040 = f1040 this.formName = 'il-1040-schedule-il-e-eic' this.state = 'IL' this.qualifyingDependents = this.f1040.scheduleEIC.qualifyingDependents() } get primary(): PrimaryPerson | undefined { return this.info.taxPayer.primaryPerson } isRequired = (): boolean => (this.earnedIncomeCredit() ?? 0) > 0 /** * Index 0: Help */ Help = (): string | undefined => { return undefined } f0 = (): string | undefined => this.Help() /** * Index 1: X 2325 */ X2325 = (): string | undefined => { return undefined } f1 = (): string | undefined => this.X2325() /** * Index 2: Reset */ Reset = (): string | undefined => { return undefined } f2 = (): string | undefined => this.Reset() /** * Index 3: Print */ Print = (): string | undefined => { return undefined } f3 = (): string | undefined => this.Print() /** * Index 4: Your name */ Yourname = (): string | undefined => [this.primary?.firstName, this.primary?.lastName].flat().join(' ') f4 = (): string | undefined => this.Yourname() /** * Index 5: Dependents first name - 2 */ Dependentsfirstname2 = (): string | undefined => this.info.taxPayer.dependents[1]?.firstName f5 = (): string | undefined => this.Dependentsfirstname2() /** * Index 6: Dependent's first name - 1 */ Dependentsfirstname1 = (): string | undefined => this.info.taxPayer.dependents[0]?.firstName f6 = (): string | undefined => this.Dependentsfirstname1() /** * Index 7: Dependent's first name - 3 */ Dependentsfirstname3 = (): string | undefined => this.info.taxPayer.dependents[2]?.firstName f7 = (): string | undefined => this.Dependentsfirstname3() /** * Index 8: Dependent's first name - 4 */ Dependentsfirstname4 = (): string | undefined => this.info.taxPayer.dependents[3]?.firstName f8 = (): string | undefined => this.Dependentsfirstname4() /** * Index 9: Dependent's first name - 5 */ Dependentsfirstname5 = (): string | undefined => this.info.taxPayer.dependents[4]?.firstName f9 = (): string | undefined => this.Dependentsfirstname5() /** * Index 10: Dependent's first name - 6 */ Dependentsfirstname6 = (): string | undefined => this.info.taxPayer.dependents[5]?.firstName f10 = (): string | undefined => this.Dependentsfirstname6() /** * Index 11: Dependent's first name - 7 */ Dependentsfirstname7 = (): string | undefined => this.info.taxPayer.dependents[6]?.firstName f11 = (): string | undefined => this.Dependentsfirstname7() /** * Index 12: Dependent's first name - 8 */ Dependentsfirstname8 = (): string | undefined => this.info.taxPayer.dependents[7]?.firstName f12 = (): string | undefined => this.Dependentsfirstname8() /** * Index 13: Dependent's first name - 9 */ Dependentsfirstname9 = (): string | undefined => this.info.taxPayer.dependents[8]?.firstName f13 = (): string | undefined => this.Dependentsfirstname9() /** * Index 14: Dependent's first name - 10 */ Dependentsfirstname10 = (): string | undefined => this.info.taxPayer.dependents[9]?.firstName f14 = (): string | undefined => this.Dependentsfirstname10() /** * Index 15: Dependent's last name - 2 */ Dependentslastname2 = (): string | undefined => this.info.taxPayer.dependents[1]?.lastName f15 = (): string | undefined => this.Dependentslastname2() /** * Index 16: Dependent's last name - 1 */ Dependentslastname1 = (): string | undefined => this.info.taxPayer.dependents[0]?.lastName f16 = (): string | undefined => this.Dependentslastname1() /** * Index 17: Dependent's last name - 3 */ Dependentslastname3 = (): string | undefined => this.info.taxPayer.dependents[2]?.lastName f17 = (): string | undefined => this.Dependentslastname3() /** * Index 18: Dependent's last name - 4 */ Dependentslastname4 = (): string | undefined => this.info.taxPayer.dependents[3]?.lastName f18 = (): string | undefined => this.Dependentslastname4() /** * Index 19: Dependent's last name - 5 */ Dependentslastname5 = (): string | undefined => this.info.taxPayer.dependents[4]?.lastName f19 = (): string | undefined => this.Dependentslastname5() /** * Index 20: Dependent's last name - 6 */ Dependentslastname6 = (): string | undefined => this.info.taxPayer.dependents[5]?.lastName f20 = (): string | undefined => this.Dependentslastname6() /** * Index 21: Dependent's last name - 7 */ Dependentslastname7 = (): string | undefined => this.info.taxPayer.dependents[6]?.lastName f21 = (): string | undefined => this.Dependentslastname7() /** * Index 22: Dependent's last name - 8 */ Dependentslastname8 = (): string | undefined => this.info.taxPayer.dependents[7]?.lastName f22 = (): string | undefined => this.Dependentslastname8() /** * Index 23: Dependent's last name - 9 */ Dependentslastname9 = (): string | undefined => this.info.taxPayer.dependents[8]?.lastName f23 = (): string | undefined => this.Dependentslastname9() /** * Index 24: Dependent's last name - 10 */ Dependentslastname10 = (): string | undefined => this.info.taxPayer.dependents[9]?.lastName f24 = (): string | undefined => this.Dependentslastname10() /** * Index 25: Social Security number - 2 */ SocialSecuritynumber2 = (): string | undefined => this.info.taxPayer.dependents[1]?.ssid f25 = (): string | undefined => this.SocialSecuritynumber2() /** * Index 26: Social Security number - 3 */ SocialSecuritynumber3 = (): string | undefined => this.info.taxPayer.dependents[2]?.ssid f26 = (): string | undefined => this.SocialSecuritynumber3() /** * Index 27: Social Security number - 4 */ SocialSecuritynumber4 = (): string | undefined => this.info.taxPayer.dependents[3]?.ssid f27 = (): string | undefined => this.SocialSecuritynumber4() /** * Index 28: Social Security number - 5 */ SocialSecuritynumber5 = (): string | undefined => this.info.taxPayer.dependents[4]?.ssid f28 = (): string | undefined => this.SocialSecuritynumber5() /** * Index 29: Social Security number - 6 */ SocialSecuritynumber6 = (): string | undefined => this.info.taxPayer.dependents[5]?.ssid f29 = (): string | undefined => this.SocialSecuritynumber6() /** * Index 30: Social Security number - 7 */ SocialSecuritynumber7 = (): string | undefined => this.info.taxPayer.dependents[6]?.ssid f30 = (): string | undefined => this.SocialSecuritynumber7() /** * Index 31: Social Security number - 8 */ SocialSecuritynumber8 = (): string | undefined => this.info.taxPayer.dependents[7]?.ssid f31 = (): string | undefined => this.SocialSecuritynumber8() /** * Index 32: Social Security number - 9 */ SocialSecuritynumber9 = (): string | undefined => this.info.taxPayer.dependents[8]?.ssid f32 = (): string | undefined => this.SocialSecuritynumber9() /** * Index 33: Social Security number - 10 */ SocialSecuritynumber10 = (): string | undefined => this.info.taxPayer.dependents[9]?.ssid f33 = (): string | undefined => this.SocialSecuritynumber10() /** * Index 34: Dependent's relationship to you - 1 */ Dependentsrelationshiptoyou1 = (): string | undefined => { return undefined } f34 = (): string | undefined => this.Dependentsrelationshiptoyou1() /** * Index 35: Dependent's relationship to you - 2 */ Dependentsrelationshiptoyou2 = (): string | undefined => { return undefined } f35 = (): string | undefined => this.Dependentsrelationshiptoyou2() /** * Index 36: Dependent's relationship to you - 3 */ Dependentsrelationshiptoyou3 = (): string | undefined => { return undefined } f36 = (): string | undefined => this.Dependentsrelationshiptoyou3() /** * Index 37: Dependent's relationship to you - 4 */ Dependentsrelationshiptoyou4 = (): string | undefined => { return undefined } f37 = (): string | undefined => this.Dependentsrelationshiptoyou4() /** * Index 38: Dependent's relationship to you - 5 */ Dependentsrelationshiptoyou5 = (): string | undefined => { return undefined } f38 = (): string | undefined => this.Dependentsrelationshiptoyou5() /** * Index 39: Dependent's relationship to you - 6 */ Dependentsrelationshiptoyou6 = (): string | undefined => { return undefined } f39 = (): string | undefined => this.Dependentsrelationshiptoyou6() /** * Index 40: Dependent's relationship to you - 7 */ Dependentsrelationshiptoyou7 = (): string | undefined => { return undefined } f40 = (): string | undefined => this.Dependentsrelationshiptoyou7() /** * Index 41: Dependent's relationship to you - 8 */ Dependentsrelationshiptoyou8 = (): string | undefined => { return undefined } f41 = (): string | undefined => this.Dependentsrelationshiptoyou8() /** * Index 42: Dependent's relationship to you - 9 */ Dependentsrelationshiptoyou9 = (): string | undefined => { return undefined } f42 = (): string | undefined => this.Dependentsrelationshiptoyou9() /** * Index 43: Dependent's relationship to you - 10 */ Dependentsrelationshiptoyou10 = (): string | undefined => { return undefined } f43 = (): string | undefined => this.Dependentsrelationshiptoyou10() /** * Index 44: Dependent's date of birth (mm/dd/yyyy) - 1 */ Dependentsdateofbirthmmddyyyy1 = (): string | undefined => { return undefined } f44 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy1() /** * Index 45: Dependent's date of birth (mm/dd/yyyy) - 2 */ Dependentsdateofbirthmmddyyyy2 = (): string | undefined => { return undefined } f45 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy2() /** * Index 46: Dependent's date of birth (mm/dd/yyyy) - 3 */ Dependentsdateofbirthmmddyyyy3 = (): string | undefined => { return undefined } f46 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy3() /** * Index 47: Dependent's date of birth (mm/dd/yyyy) - 4 */ Dependentsdateofbirthmmddyyyy4 = (): string | undefined => { return undefined } f47 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy4() /** * Index 48: Dependent's date of birth (mm/dd/yyyy) - 5 */ Dependentsdateofbirthmmddyyyy5 = (): string | undefined => { return undefined } f48 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy5() /** * Index 49: Dependent's date of birth (mm/dd/yyyy) - 6 */ Dependentsdateofbirthmmddyyyy6 = (): string | undefined => { return undefined } f49 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy6() /** * Index 50: Dependent's date of birth (mm/dd/yyyy) - 7 */ Dependentsdateofbirthmmddyyyy7 = (): string | undefined => { return undefined } f50 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy7() /** * Index 51: Dependent's date of birth (mm/dd/yyyy) - 8 */ Dependentsdateofbirthmmddyyyy8 = (): string | undefined => { return undefined } f51 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy8() /** * Index 52: Dependent's date of birth (mm/dd/yyyy) - 9 */ Dependentsdateofbirthmmddyyyy9 = (): string | undefined => { return undefined } f52 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy9() /** * Index 53: Dependent's date of birth (mm/dd/yyyy) - 10 */ Dependentsdateofbirthmmddyyyy10 = (): string | undefined => { return undefined } f53 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy10() /** * Index 54: Full Time Student - 1 */ FullTimeStudent1 = (): boolean | undefined => this.info.taxPayer.dependents[0]?.qualifyingInfo?.isStudent f54 = (): boolean | undefined => this.FullTimeStudent1() /** * Index 55: Full Time Student - 2 */ FullTimeStudent2 = (): boolean | undefined => this.info.taxPayer.dependents[1]?.qualifyingInfo?.isStudent f55 = (): boolean | undefined => this.FullTimeStudent2() /** * Index 56: Full Time Student - 3 */ FullTimeStudent3 = (): boolean | undefined => this.info.taxPayer.dependents[2]?.qualifyingInfo?.isStudent f56 = (): boolean | undefined => this.FullTimeStudent3() /** * Index 57: Full Time Student - 4 */ FullTimeStudent4 = (): boolean | undefined => this.info.taxPayer.dependents[3]?.qualifyingInfo?.isStudent f57 = (): boolean | undefined => this.FullTimeStudent4() /** * Index 58: Full Time Student - 5 */ FullTimeStudent5 = (): boolean | undefined => this.info.taxPayer.dependents[4]?.qualifyingInfo?.isStudent f58 = (): boolean | undefined => this.FullTimeStudent5() /** * Index 59: Full Time Student - 6 */ FullTimeStudent6 = (): boolean | undefined => this.info.taxPayer.dependents[5]?.qualifyingInfo?.isStudent f59 = (): boolean | undefined => this.FullTimeStudent6() /** * Index 60: Full Time Student - 7 */ FullTimeStudent7 = (): boolean | undefined => this.info.taxPayer.dependents[6]?.qualifyingInfo?.isStudent f60 = (): boolean | undefined => this.FullTimeStudent7() /** * Index 61: Full Time Student - 8 */ FullTimeStudent8 = (): boolean | undefined => this.info.taxPayer.dependents[7]?.qualifyingInfo?.isStudent f61 = (): boolean | undefined => this.FullTimeStudent8() /** * Index 62: Full Time Student - 9 */ FullTimeStudent9 = (): boolean | undefined => this.info.taxPayer.dependents[8]?.qualifyingInfo?.isStudent f62 = (): boolean | undefined => this.FullTimeStudent9() /** * Index 63: Full Time Student - 10 */ FullTimeStudent10 = (): boolean | undefined => this.info.taxPayer.dependents[9]?.qualifyingInfo?.isStudent f63 = (): boolean | undefined => this.FullTimeStudent10() /** * Index 64: Person with disability - 1 * TODO: Handle disabilities */ Personwithdisability1 = (): boolean | undefined => undefined f64 = (): boolean | undefined => this.Personwithdisability1() /** * Index 65: Person with disability - 2 * TODO: Handle disabilities */ Personwithdisability2 = (): boolean | undefined => undefined f65 = (): boolean | undefined => this.Personwithdisability2() /** * Index 66: Person with disability - 3 * TODO: Handle disabilities */ Personwithdisability3 = (): boolean | undefined => undefined f66 = (): boolean | undefined => this.Personwithdisability3() /** * Index 67: Person with disability - 4 * TODO: Handle disabilities */ Personwithdisability4 = (): boolean | undefined => undefined f67 = (): boolean | undefined => this.Personwithdisability4() /** * Index 68: Person with disability - 5 * TODO: Handle disabilities */ Personwithdisability5 = (): boolean | undefined => undefined f68 = (): boolean | undefined => this.Personwithdisability5() /** * Index 69: Person with disability - 6 * TODO: Handle disabilities */ Personwithdisability6 = (): boolean | undefined => undefined f69 = (): boolean | undefined => this.Personwithdisability6() /** * Index 70: Person with disability - 7 * TODO: Handle disabilities */ Personwithdisability7 = (): boolean | undefined => undefined f70 = (): boolean | undefined => this.Personwithdisability7() /** * Index 71: Person with disability - 8 * TODO: Handle disabilities */ Personwithdisability8 = (): boolean | undefined => undefined f71 = (): boolean | undefined => this.Personwithdisability8() /** * Index 72: Person with disability - 9 * TODO: Handle disabilities */ Personwithdisability9 = (): boolean | undefined => undefined f72 = (): boolean | undefined => this.Personwithdisability9() /** * Index 73: Person with disability - 10 * TODO: Handle disabilities */ Personwithdisability10 = (): boolean | undefined => undefined f73 = (): boolean | undefined => this.Personwithdisability10() /** * Index 74: Number of months living with you - 1 */ Numberofmonthslivingwithyou1 = (): number | undefined => this.info.taxPayer.dependents[0]?.qualifyingInfo?.numberOfMonths f74 = (): number | undefined => this.Numberofmonthslivingwithyou1() /** * Index 75: Number of months living with you - 2 */ Numberofmonthslivingwithyou2 = (): number | undefined => this.info.taxPayer.dependents[1]?.qualifyingInfo?.numberOfMonths f75 = (): number | undefined => this.Numberofmonthslivingwithyou2() /** * Index 76: Number of months living with you - 3 */ Numberofmonthslivingwithyou3 = (): number | undefined => this.info.taxPayer.dependents[2]?.qualifyingInfo?.numberOfMonths f76 = (): number | undefined => this.Numberofmonthslivingwithyou3() /** * Index 77: Number of months living with you - 4 */ Numberofmonthslivingwithyou4 = (): number | undefined => this.info.taxPayer.dependents[3]?.qualifyingInfo?.numberOfMonths f77 = (): number | undefined => this.Numberofmonthslivingwithyou4() /** * Index 78: Number of months living with you - 5 */ Numberofmonthslivingwithyou5 = (): number | undefined => this.info.taxPayer.dependents[4]?.qualifyingInfo?.numberOfMonths f78 = (): number | undefined => this.Numberofmonthslivingwithyou5() /** * Index 79: Number of months living with you - 6 */ Numberofmonthslivingwithyou6 = (): number | undefined => this.info.taxPayer.dependents[5]?.qualifyingInfo?.numberOfMonths f79 = (): number | undefined => this.Numberofmonthslivingwithyou6() /** * Index 80: Number of months living with you - 7 */ Numberofmonthslivingwithyou7 = (): number | undefined => this.info.taxPayer.dependents[6]?.qualifyingInfo?.numberOfMonths f80 = (): number | undefined => this.Numberofmonthslivingwithyou7() /** * Index 81: Number of months living with you - 8 */ Numberofmonthslivingwithyou8 = (): number | undefined => this.info.taxPayer.dependents[7]?.qualifyingInfo?.numberOfMonths f81 = (): number | undefined => this.Numberofmonthslivingwithyou8() /** * Index 82: Number of months living with you - 9 */ Numberofmonthslivingwithyou9 = (): number | undefined => this.info.taxPayer.dependents[8]?.qualifyingInfo?.numberOfMonths f82 = (): number | undefined => this.Numberofmonthslivingwithyou9() /** * Index 83: Number of months living with you - 10 */ Numberofmonthslivingwithyou10 = (): number | undefined => this.info.taxPayer.dependents[9]?.qualifyingInfo?.numberOfMonths f83 = (): number | undefined => this.Numberofmonthslivingwithyou10() /** * Index 84: Eligible for Earned Income Credit - 1 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit1 = (): boolean => this.info.taxPayer.dependents.length > 0 f84 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit1() /** * Index 85: Eligible for Earned Income Credit - 2 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit2 = (): boolean | undefined => this.info.taxPayer.dependents.length > 1 f85 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit2() /** * Index 86: Eligible for Earned Income Credit - 3 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit3 = (): boolean | undefined => this.info.taxPayer.dependents.length > 2 f86 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit3() /** * Index 87: Eligible for Earned Income Credit - 4 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit4 = (): boolean | undefined => this.info.taxPayer.dependents.length > 3 f87 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit4() /** * Index 88: Eligible for Earned Income Credit - 5 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit5 = (): boolean | undefined => this.info.taxPayer.dependents.length > 4 f88 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit5() /** * Index 89: Eligible for Earned Income Credit - 6 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit6 = (): boolean | undefined => this.info.taxPayer.dependents.length > 5 f89 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit6() /** * Index 90: Eligible for Earned Income Credit - 7 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit7 = (): boolean | undefined => this.info.taxPayer.dependents.length > 6 f90 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit7() /** * Index 91: Eligible for Earned Income Credit - 8 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit8 = (): boolean | undefined => this.info.taxPayer.dependents.length > 7 f91 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit8() /** * Index 92: Eligible for Earned Income Credit - 9 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit9 = (): boolean | undefined => this.info.taxPayer.dependents.length > 8 f92 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit9() /** * Index 93: Eligible for Earned Income Credit - 10 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit10 = (): boolean | undefined => this.info.taxPayer.dependents.length > 9 f93 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit10() /** * Index 94: Mutiplied total number of dependents */ Mutipliedtotalnumberofdependents = (): number | undefined => this.qualifyingDependents.length * parameters.eicDependentCredit f94 = (): number | undefined => this.Mutipliedtotalnumberofdependents() /** * Index 95: Child's first name -1 * TODO: non-dependent children not handled. */ Childsfirstname1 = (): string | undefined => { return undefined } f95 = (): string | undefined => this.Childsfirstname1() /** * Index 96: Child's first name -2 * TODO: non-dependent children not handled. */ Childsfirstname2 = (): string | undefined => { return undefined } f96 = (): string | undefined => this.Childsfirstname2() /** * Index 97: Child's first name -3 * TODO: non-dependent children not handled. */ Childsfirstname3 = (): string | undefined => { return undefined } f97 = (): string | undefined => this.Childsfirstname3() /** * Index 98: Child's first name - 4 * TODO: non-dependent children not handled. */ Childsfirstname4 = (): string | undefined => { return undefined } f98 = (): string | undefined => this.Childsfirstname4() /** * Index 99: Child's first name - 5 * TODO: non-dependent children not handled. */ Childsfirstname5 = (): string | undefined => { return undefined } f99 = (): string | undefined => this.Childsfirstname5() /** * Index 100: Child's first name - 6 * TODO: non-dependent children not handled. */ Childsfirstname6 = (): string | undefined => { return undefined } f100 = (): string | undefined => this.Childsfirstname6() /** * Index 101: Child's first name - 7 * TODO: non-dependent children not handled. */ Childsfirstname7 = (): string | undefined => { return undefined } f101 = (): string | undefined => this.Childsfirstname7() /** * Index 102: Child's first name - 8 * TODO: non-dependent children not handled. */ Childsfirstname8 = (): string | undefined => { return undefined } f102 = (): string | undefined => this.Childsfirstname8() /** * Index 103: Child's last name - 1 * TODO: non-dependent children not handled. */ Childslastname1 = (): string | undefined => { return undefined } f103 = (): string | undefined => this.Childslastname1() /** * Index 104: Child's last name - 2 * TODO: non-dependent children not handled. */ Childslastname2 = (): string | undefined => { return undefined } f104 = (): string | undefined => this.Childslastname2() /** * Index 105: Child's last name - 3 * TODO: non-dependent children not handled. */ Childslastname3 = (): string | undefined => { return undefined } f105 = (): string | undefined => this.Childslastname3() /** * Index 106: Child's last name - 4 * TODO: non-dependent children not handled. */ Childslastname4 = (): string | undefined => { return undefined } f106 = (): string | undefined => this.Childslastname4() /** * Index 107: Child's last name - 5 * TODO: non-dependent children not handled. */ Childslastname5 = (): string | undefined => { return undefined } f107 = (): string | undefined => this.Childslastname5() /** * Index 108: Child's last name - 6 * TODO: non-dependent children not handled. */ Childslastname6 = (): string | undefined => { return undefined } f108 = (): string | undefined => this.Childslastname6() /** * Index 109: Child's last name - 7 * TODO: non-dependent children not handled. */ Childslastname7 = (): string | undefined => { return undefined } f109 = (): string | undefined => this.Childslastname7() /** * Index 110: Child's last name - 8 * TODO: non-dependent children not handled. */ Childslastname8 = (): string | undefined => { return undefined } f110 = (): string | undefined => this.Childslastname8() /** * Index 111: Social Security number - 1 */ SocialSecuritynumber1 = (): string | undefined => this.info.taxPayer.dependents[0]?.ssid f111 = (): string | undefined => this.SocialSecuritynumber1() /** * Index 112: Child's Social Security number - 1 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber1 = (): string | undefined => { return undefined } f112 = (): string | undefined => this.ChildsSocialSecuritynumber1() /** * Index 113: Child's Social Security number - 2 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber2 = (): string | undefined => { return undefined } f113 = (): string | undefined => this.ChildsSocialSecuritynumber2() /** * Index 114: Child's Social Security number - 3 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber3 = (): string | undefined => { return undefined } f114 = (): string | undefined => this.ChildsSocialSecuritynumber3() /** * Index 115: Child's Social Security number - 4 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber4 = (): string | undefined => { return undefined } f115 = (): string | undefined => this.ChildsSocialSecuritynumber4() /** * Index 116: Child's Social Security number - 5 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber5 = (): string | undefined => { return undefined } f116 = (): string | undefined => this.ChildsSocialSecuritynumber5() /** * Index 117: Child's Social Security number - 6 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber6 = (): string | undefined => { return undefined } f117 = (): string | undefined => this.ChildsSocialSecuritynumber6() /** * Index 118: Child's Social Security number - 7 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber7 = (): string | undefined => { return undefined } f118 = (): string | undefined => this.ChildsSocialSecuritynumber7() /** * Index 119: Child's Social Security number - 8 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber8 = (): string | undefined => { return undefined } f119 = (): string | undefined => this.ChildsSocialSecuritynumber8() /** * Index 120: Child's relationship to you - 1 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou1 = (): string | undefined => { return undefined } f120 = (): string | undefined => this.Childsrelationshiptoyou1() /** * Index 121: Child's relationship to you - 2 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou2 = (): string | undefined => { return undefined } f121 = (): string | undefined => this.Childsrelationshiptoyou2() /** * Index 122: Child's relationship to you - 3 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou3 = (): string | undefined => { return undefined } f122 = (): string | undefined => this.Childsrelationshiptoyou3() /** * Index 123: Child's relationship to you - 4 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou4 = (): string | undefined => { return undefined } f123 = (): string | undefined => this.Childsrelationshiptoyou4() /** * Index 124: Child's relationship to you - 5 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou5 = (): string | undefined => { return undefined } f124 = (): string | undefined => this.Childsrelationshiptoyou5() /** * Index 125: Child's relationship to you - 6 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou6 = (): string | undefined => { return undefined } f125 = (): string | undefined => this.Childsrelationshiptoyou6() /** * Index 126: Child's relationship to you - 7 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou7 = (): string | undefined => { return undefined } f126 = (): string | undefined => this.Childsrelationshiptoyou7() /** * Index 127: Child's relationship to you - 8 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou8 = (): string | undefined => { return undefined } f127 = (): string | undefined => this.Childsrelationshiptoyou8() /** * Index 128: Child's date of birth (mm/dd/yyyy) - 1 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy1 = (): string | undefined => { return undefined } f128 = (): string | undefined => this.Childsdateofbirthmmddyyyy1() /** * Index 129: Child's date of birth (mm/dd/yyyy) - 2 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy2 = (): string | undefined => { return undefined } f129 = (): string | undefined => this.Childsdateofbirthmmddyyyy2() /** * Index 130: Child's date of birth (mm/dd/yyyy) - 3 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy3 = (): string | undefined => { return undefined } f130 = (): string | undefined => this.Childsdateofbirthmmddyyyy3() /** * Index 131: Child's date of birth (mm/dd/yyyy) - 4 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy4 = (): string | undefined => { return undefined } f131 = (): string | undefined => this.Childsdateofbirthmmddyyyy4() /** * Index 132: Child's date of birth (mm/dd/yyyy) - 5 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy5 = (): string | undefined => { return undefined } f132 = (): string | undefined => this.Childsdateofbirthmmddyyyy5() /** * Index 133: Child's date of birth (mm/dd/yyyy) - 6 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy6 = (): string | undefined => { return undefined } f133 = (): string | undefined => this.Childsdateofbirthmmddyyyy6() /** * Index 134: Child's date of birth (mm/dd/yyyy) - 7 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy7 = (): string | undefined => { return undefined } f134 = (): string | undefined => this.Childsdateofbirthmmddyyyy7() /** * Index 135: Child's date of birth (mm/dd/yyyy) - 8 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy8 = (): string | undefined => { return undefined } f135 = (): string | undefined => this.Childsdateofbirthmmddyyyy8() /** * Index 136: Child - Full Time Student - 1 */ ChildFullTimeStudent1 = (): boolean | undefined => { return undefined } f136 = (): boolean | undefined => this.ChildFullTimeStudent1() /** * Index 137: Child - Full Time Student - 2 */ ChildFullTimeStudent2 = (): boolean | undefined => { return undefined } f137 = (): boolean | undefined => this.ChildFullTimeStudent2() /** * Index 138: Child - Full Time Student - 3 */ ChildFullTimeStudent3 = (): boolean | undefined => { return undefined } f138 = (): boolean | undefined => this.ChildFullTimeStudent3() /** * Index 139: Child - Full Time Student - 4 */ ChildFullTimeStudent4 = (): boolean | undefined => { return undefined } f139 = (): boolean | undefined => this.ChildFullTimeStudent4() /** * Index 140: Child - Full Time Student - 5 */ ChildFullTimeStudent5 = (): boolean | undefined => { return undefined } f140 = (): boolean | undefined => this.ChildFullTimeStudent5() /** * Index 141: Child - Full Time Student - 6 */ ChildFullTimeStudent6 = (): boolean | undefined => { return undefined } f141 = (): boolean | undefined => this.ChildFullTimeStudent6() /** * Index 142: Child - Full Time Student - 7 */ ChildFullTimeStudent7 = (): boolean | undefined => { return undefined } f142 = (): boolean | undefined => this.ChildFullTimeStudent7() /** * Index 143: Child - Full Time Student - 8 */ ChildFullTimeStudent8 = (): boolean | undefined => { return undefined } f143 = (): boolean | undefined => this.ChildFullTimeStudent8() /** * Index 144: Child - Person with disability - 2 */ ChildPersonwithdisability2 = (): boolean | undefined => { return undefined } f144 = (): boolean | undefined => this.ChildPersonwithdisability2() /** * Index 145: Child - Person with disability - 1 */ ChildPersonwithdisability1 = (): boolean | undefined => { return undefined } f145 = (): boolean | undefined => this.ChildPersonwithdisability1() /** * Index 146: Child - Person with disability - 3 */ ChildPersonwithdisability3 = (): boolean | undefined => { return undefined } f146 = (): boolean | undefined => this.ChildPersonwithdisability3() /** * Index 147: Child - Person with disability - 4 */ ChildPersonwithdisability4 = (): boolean | undefined => { return undefined } f147 = (): boolean | undefined => this.ChildPersonwithdisability4() /** * Index 148: Child - Person with disability - 5 */ ChildPersonwithdisability5 = (): boolean | undefined => { return undefined } f148 = (): boolean | undefined => this.ChildPersonwithdisability5() /** * Index 149: Child - Person with disability - 6 */ ChildPersonwithdisability6 = (): boolean | undefined => { return undefined } f149 = (): boolean | undefined => this.ChildPersonwithdisability6() /** * Index 150: Child - Person with disability - 7 */ ChildPersonwithdisability7 = (): boolean | undefined => { return undefined } f150 = (): boolean | undefined => this.ChildPersonwithdisability7() /** * Index 151: Child - Person with disability - 8 */ ChildPersonwithdisability8 = (): boolean | undefined => { return undefined } f151 = (): boolean | undefined => this.ChildPersonwithdisability8() /** * Index 152: Child - Number of months living with you - 1 */ ChildNumberofmonthslivingwithyou1 = (): string | undefined => { return undefined } f152 = (): string | undefined => this.ChildNumberofmonthslivingwithyou1() /** * Index 153: Child - Number of months living with you - 2 */ ChildNumberofmonthslivingwithyou2 = (): string | undefined => { return undefined } f153 = (): string | undefined => this.ChildNumberofmonthslivingwithyou2() /** * Index 154: Child - Number of months living with you - 3 */ ChildNumberofmonthslivingwithyou3 = (): string | undefined => { return undefined } f154 = (): string | undefined => this.ChildNumberofmonthslivingwithyou3() /** * Index 155: Child - Number of months living with you - 4 */ ChildNumberofmonthslivingwithyou4 = (): string | undefined => { return undefined } f155 = (): string | undefined => this.ChildNumberofmonthslivingwithyou4() /** * Index 156: Child - Number of months living with you - 5 */ ChildNumberofmonthslivingwithyou5 = (): string | undefined => { return undefined } f156 = (): string | undefined => this.ChildNumberofmonthslivingwithyou5() /** * Index 157: Child - Number of months living with you - 6 */ ChildNumberofmonthslivingwithyou6 = (): string | undefined => { return undefined } f157 = (): string | undefined => this.ChildNumberofmonthslivingwithyou6() /** * Index 158: Child - Number of months living with you - 7 */ ChildNumberofmonthslivingwithyou7 = (): string | undefined => { return undefined } f158 = (): string | undefined => this.ChildNumberofmonthslivingwithyou7() /** * Index 159: Child - Number of months living with you - 8 */ ChildNumberofmonthslivingwithyou8 = (): string | undefined => { return undefined } f159 = (): string | undefined => this.ChildNumberofmonthslivingwithyou8() /** * Index 160: Wages - Salaries - Tips */ WagesSalariesTips = (): number | undefined => this.f1040.l1() f160 = (): number | undefined => this.WagesSalariesTips() /** * Index 161: Business income/loss */ Businessincomeloss = (): number | undefined => this.f1040.schedule1.l3() f161 = (): number | undefined => this.Businessincomeloss() /** * Index 162: Occupation requirement * TODO: jurisdictional license question */ Occupationrequirement = (): boolean | undefined => undefined f162 = (): boolean | undefined => this.Occupationrequirement() /** * Index 163: Issuing agency - 1 */ Issuingagency1 = (): string | undefined => undefined f163 = (): string | undefined => this.Issuingagency1() /** * Index 164: Issuing agency - 2 */ Issuingagency2 = (): string | undefined => undefined f164 = (): string | undefined => this.Issuingagency2() /** * Index 165: Issuing agency - 3 */ Issuingagency3 = (): string | undefined => undefined f165 = (): string | undefined => this.Issuingagency3() /** * Index 166: Issuing agency - 5 */ Issuingagency5 = (): string | undefined => undefined f166 = (): string | undefined => this.Issuingagency5() /** * Index 167: Issuing agency - 4 */ Issuingagency4 = (): string | undefined => undefined f167 = (): string | undefined => this.Issuingagency4() /** * Index 168: License/Registration/Certification - 1 */ LicenseRegistrationCertification1 = (): string | undefined => undefined f168 = (): string | undefined => this.LicenseRegistrationCertification1() /** * Index 169: License/Registration/Certification - 2 */ LicenseRegistrationCertification2 = (): string | undefined => undefined f169 = (): string | undefined => this.LicenseRegistrationCertification2() /** * Index 170: License/Registration/Certification - 3 */ LicenseRegistrationCertification3 = (): string | undefined => undefined f170 = (): string | undefined => this.LicenseRegistrationCertification3() /** * Index 171: License/Registration/Certification - 4 */ LicenseRegistrationCertification4 = (): string | undefined => undefined f171 = (): string | undefined => this.LicenseRegistrationCertification4() /** * Index 172: License/Registration/Certification - 5 */ LicenseRegistrationCertification5 = (): string | undefined => undefined f172 = (): string | undefined => this.LicenseRegistrationCertification5() /** * Index 173: Filing - married filing jointly * TODO: Handle MFJ federal / MFS state issue. */ Filingmarriedfilingjointly = (): string | undefined => { return undefined } f173 = (): string | undefined => this.Filingmarriedfilingjointly() /** * Index 174: Spouse' s SSN3-2 * TODO - only applicable for MFS state and MFJ federal return */ SpousesSSN32 = (): string | undefined => undefined f174 = (): string | undefined => this.SpousesSSN32() /** * Index 175: Spouse's SSN2-2 */ SpousesSSN22 = (): string | undefined => undefined f175 = (): string | undefined => this.SpousesSSN22() /** * Index 176: Spouse's SSN4-2 */ SpousesSSN42 = (): string | undefined => undefined f176 = (): string | undefined => this.SpousesSSN42() /** * Index 177: Statutory employee box marked */ Statutoryemployeeboxmarked = (): boolean | undefined => { return undefined } f177 = (): boolean | undefined => this.Statutoryemployeeboxmarked() /** * Index 178: Federal Earned Income Credit amount */ FederalEarnedIncomeCreditamount = (): number | undefined => this.f1040.l27() f178 = (): number | undefined => this.FederalEarnedIncomeCreditamount() /** * Index 179: Multiply L5 */ MultiplyL5 = (): number => (this.FederalEarnedIncomeCreditamount() ?? 0) * parameters.earnedIncomeCreditFactor f179 = (): number => Math.round(this.MultiplyL5()) /** * Index 180: Residents / Non-Residents rate - 1 * TODO: Handle non-residents rate */ ResidentsNonResidentsrate1 = (): number => 1 f180 = (): number => this.ResidentsNonResidentsrate1() /** * Index 181: Residents / Non-Residents rate - 2 * TODO: Handle non-residents rate and Schedule NR */ ResidentsNonResidentsrate2 = (): number => 0 f181 = (): number => this.ResidentsNonResidentsrate2() /** * Index 182: Multiply L6 - L7 */ MultiplyL6L7 = (): number | undefined => this.MultiplyL5() * (this.ResidentsNonResidentsrate1() + this.ResidentsNonResidentsrate2() / 100) earnedIncomeCredit = (): number | undefined => this.MultiplyL6L7() f182 = (): number => Math.round(this.MultiplyL6L7() ?? 0) /** * Index 183: Your SSN3 */ YourSSN3 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(0, 3) f183 = (): string | undefined => this.YourSSN3() /** * Index 184: Your SSN2 */ YourSSN2 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(3, 5) f184 = (): string | undefined => this.YourSSN2() /** * Index 185: Your SSN4 */ YourSSN4 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(5) f185 = (): string | undefined => this.YourSSN4() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.f26(), this.f27(), this.f28(), this.f29(), this.f30(), this.f31(), this.f32(), this.f33(), this.f34(), this.f35(), this.f36(), this.f37(), this.f38(), this.f39(), this.f40(), this.f41(), this.f42(), this.f43(), this.f44(), this.f45(), this.f46(), this.f47(), this.f48(), this.f49(), this.f50(), this.f51(), this.f52(), this.f53(), this.f54(), this.f55(), this.f56(), this.f57(), this.f58(), this.f59(), this.f60(), this.f61(), this.f62(), this.f63(), this.f64(), this.f65(), this.f66(), this.f67(), this.f68(), this.f69(), this.f70(), this.f71(), this.f72(), this.f73(), this.f74(), this.f75(), this.f76(), this.f77(), this.f78(), this.f79(), this.f80(), this.f81(), this.f82(), this.f83(), this.f84(), this.f85(), this.f86(), this.f87(), this.f88(), this.f89(), this.f90(), this.f91(), this.f92(), this.f93(), this.f94(), this.f95(), this.f96(), this.f97(), this.f98(), this.f99(), this.f100(), this.f101(), this.f102(), this.f103(), this.f104(), this.f105(), this.f106(), this.f107(), this.f108(), this.f109(), this.f110(), this.f111(), this.f112(), this.f113(), this.f114(), this.f115(), this.f116(), this.f117(), this.f118(), this.f119(), this.f120(), this.f121(), this.f122(), this.f123(), this.f124(), this.f125(), this.f126(), this.f127(), this.f128(), this.f129(), this.f130(), this.f131(), this.f132(), this.f133(), this.f134(), this.f135(), this.f136(), this.f137(), this.f138(), this.f139(), this.f140(), this.f141(), this.f142(), this.f143(), this.f144(), this.f145(), this.f146(), this.f147(), this.f148(), this.f149(), this.f150(), this.f151(), this.f152(), this.f153(), this.f154(), this.f155(), this.f156(), this.f157(), this.f158(), this.f159(), this.f160(), this.f161(), this.f162(), this.f163(), this.f164(), this.f165(), this.f166(), this.f167(), this.f168(), this.f169(), this.f170(), this.f171(), this.f172(), this.f173(), this.f174(), this.f175(), this.f176(), this.f177(), this.f178(), this.f179(), this.f180(), this.f181(), this.f182(), this.f183(), this.f184(), this.f185() ] } const makeil1040scheduleileeic = ( info: ValidatedInformation, f1040: F1040 ): IL1040scheduleileeic => new IL1040scheduleileeic(info, f1040) export default makeil1040scheduleileeic ================================================ FILE: src/forms/Y2020/stateForms/IL/IL1040V.ts ================================================ import Form from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { IL1040 } from './IL1040' import { Field } from 'ustaxes/core/pdfFiller' import { State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export default class IL1040V extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State il1040: IL1040 formOrder = -1 attachments: () => Form[] = () => [] constructor(info: ValidatedInformation, f1040: F1040, il1040: IL1040) { super() this.info = info this.f1040 = f1040 this.formName = 'IL-1040-V' this.state = 'IL' this.il1040 = il1040 } /** * Index 0: Help */ Help = (): string | undefined => { return undefined } f0 = (): string | undefined => this.Help() /** * Index 1: PrimarySSN1 */ PrimarySSN1 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(0, 3) f1 = (): string | undefined => this.PrimarySSN1() /** * Index 2: PrimarySSN2 */ PrimarySSN2 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(3, 5) f2 = (): string | undefined => this.PrimarySSN2() /** * Index 3: PrimarySSN3 */ PrimarySSN3 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(5) f3 = (): string | undefined => this.PrimarySSN3() /** * Index 4: SpouseSSN1 */ SpouseSSN1 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(0, 3) f4 = (): string | undefined => this.SpouseSSN1() /** * Index 5: SpouseSSN2 */ SpouseSSN2 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(3, 5) f5 = (): string | undefined => this.SpouseSSN2() /** * Index 6: SpouseSSN3 */ SpouseSSN3 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(5) f6 = (): string | undefined => this.SpouseSSN3() /** * Index 7: FirstName */ FirstName = (): string | undefined => this.info.taxPayer.primaryPerson.firstName f7 = (): string | undefined => this.FirstName() /** * Index 8: SpouseFirstName */ SpouseFirstName = (): string | undefined => this.info.taxPayer.spouse?.firstName f8 = (): string | undefined => this.SpouseFirstName() /** * Index 9: LastName */ LastName = (): string | undefined => this.info.taxPayer.primaryPerson.lastName f9 = (): string | undefined => this.LastName() /** * Index 10: Address */ Address = (): string | undefined => this.info.taxPayer.primaryPerson.address.address f10 = (): string | undefined => this.Address() /** * Index 11: City */ City = (): string | undefined => this.info.taxPayer.primaryPerson.address.city f11 = (): string | undefined => this.City() /** * Index 12: State */ State = (): string | undefined => this.info.taxPayer.primaryPerson.address.state f12 = (): string | undefined => this.State() /** * Index 13: ZIP */ ZIP = (): string | undefined => this.info.taxPayer.primaryPerson.address.zip f13 = (): string | undefined => this.ZIP() /** * Index 14: PaymentAmount */ PaymentAmount = (): number | undefined => { const amount = this.il1040.payment() if (amount !== undefined) { return Math.trunc(amount) } } f14 = (): number | undefined => this.PaymentAmount() /** * Index 15: PaymentAmountCents */ PaymentAmountCents = (): number | undefined => { const amount = this.il1040.payment() if (amount !== undefined) { return Math.round((amount - Math.trunc(amount)) * 100) } } f15 = (): number | undefined => this.PaymentAmountCents() /** * Index 16: Reset */ Reset = (): string | undefined => { return undefined } f16 = (): string | undefined => this.Reset() /** * Index 17: Print */ Print = (): string | undefined => { return undefined } f17 = (): string | undefined => this.Print() /** * Index 18: scanline */ scanline = (): string | undefined => { return undefined } f18 = (): string | undefined => this.scanline() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18() ] } ================================================ FILE: src/forms/Y2020/stateForms/IL/ILWit.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { IncomeW2, PersonRole, PrimaryPerson, State } from 'ustaxes/core/data' import _ from 'lodash' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' type FormType = | 'W' // W-2 | 'WG' // W2-G | 'R' // 1099-R | 'G' // 1099-G | 'M' // 1099-MISC | 'O' // 1099-OID | 'D' // 1099-DIV | 'I' // 1099-INT | 'S' // 1042-S | 'B' // 1099-B | 'K' // 1099-K | 'N' // 1099-NEC interface WithholdingForm { formType: FormType role: PersonRole ein: string federalWages: number ilWages: number ilTax: number } const toWithholdingForm = (w2: IncomeW2): WithholdingForm | undefined => { if ( w2.stateWages !== undefined && w2.stateWithholding !== undefined && w2.employer?.EIN !== undefined ) { return { formType: 'W', ein: w2.employer.EIN, federalWages: w2.income, ilWages: w2.stateWages, ilTax: w2.stateWithholding, role: w2.personRole } } } /** * Each ILWIT form supports 5 withholding forms for * primary taxpayer and 5 for spouse * TODO: support more than 5 for each */ export class ILWIT extends Form { info: ValidatedInformation f1040: F1040 formName = 'IL-WIT' state: State = 'IL' formOrder = 31 methods: FormMethods formIndex: number static WITHHOLDING_FORMS_PER_PAGE = 5 constructor(f1040: F1040, subFormIndex = 0) { super() this.info = f1040.info this.f1040 = f1040 this.methods = new FormMethods(this) this.formIndex = subFormIndex } get primary(): PrimaryPerson | undefined { return this.info.taxPayer.primaryPerson } attachments = (): Form[] => { // If this is the head form, see if we need // more copies. For example if the SSIDs have 4 and 11 forms, // we will need 2 extra copies. this one will have 4 + 5, // next will have 0 + 5, last will have 0 + 1 if (this.formIndex === 0) { const copiesNeeded = Math.ceil( Math.max( ...[PersonRole.PRIMARY, PersonRole.SPOUSE].map( (r) => this.methods .stateW2s() .filter((w2) => (w2.stateWithholding ?? 0) > 0) .filter((w2) => w2.personRole === r).length ) ) / ILWIT.WITHHOLDING_FORMS_PER_PAGE ) - 1 return Array(copiesNeeded) .fill(undefined) .map((x, i) => new ILWIT(this.f1040, i + 1)) } return [] } /** * Index 0: Help */ Help = (): string | undefined => { return undefined } f0 = (): string | undefined => this.Help() /** * Index 1: Reset */ Reset = (): string | undefined => { return undefined } f1 = (): string | undefined => this.Reset() /** * Index 2: Print */ Print = (): string | undefined => { return undefined } f2 = (): string | undefined => this.Print() /** * Index 3: Your name */ Yourname = (): string | undefined => [this.primary?.firstName, this.primary?.lastName].flat().join(' ') f3 = (): string | undefined => this.Yourname() /** * Index 4: Your SSN-3 */ YourSSN3 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(0, 3) f4 = (): string | undefined => this.YourSSN3() /** * Index 5: Your SSN-2 */ YourSSN2 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(3, 5) f5 = (): string | undefined => this.YourSSN2() /** * Index 6: Your SSN-4 */ YourSSN4 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(5) f6 = (): string | undefined => this.YourSSN4() allWithholdingForms = (): WithholdingForm[] => this.methods .stateW2s() .map((w2) => toWithholdingForm(w2)) .filter((x) => x !== undefined) as WithholdingForm[] formsByRole = (role: PersonRole): WithholdingForm[] => this.allWithholdingForms().filter((w2) => w2.role === role) primaryForms = (): WithholdingForm[] => _.chain(this.formsByRole(PersonRole.PRIMARY)) .drop(this.formIndex * 5) .take(5) .value() spouseForms = (): WithholdingForm[] => _.chain(this.formsByRole(PersonRole.SPOUSE)) .drop(this.formIndex * 5) .take(5) .value() /** * 4 x 5 grid for primary taxpayer, columnwise * Note the form type column is indexed after all this. */ formGrid = (forms: WithholdingForm[]): (string | number | undefined)[] => [ forms.map((form) => form.ein), forms.map((form) => form.federalWages), forms.map((form) => form.ilWages), forms.map((form) => form.ilTax) ].flatMap((column) => [ ...column, ...Array(5 - column.length).fill(undefined) ]) /** * Primary ssid withholding, Column B -> Column E */ f7Tof26 = (): (string | number | undefined)[] => this.formGrid(this.primaryForms()) /** * Index 27: Spouse's name */ Spousesname = (): string | undefined => { const spouse = this.info.taxPayer.spouse if (spouse !== undefined) { return `${spouse.firstName} ${spouse.lastName}` } } f27 = (): string | undefined => this.Spousesname() /** * Index 28: Spouse's SSN-3 */ SpousesSSN3 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(0, 3) f28 = (): string | undefined => this.SpousesSSN3() /** * Index 29: Spouse's SSN-2 */ SpousesSSN2 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(3, 5) f29 = (): string | undefined => this.SpousesSSN2() /** * Index 30: Spouse's SSN-4 */ SpousesSSN4 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(5) f30 = (): string | undefined => this.SpousesSSN4() /** * Spouse ssid forms, Column B -> Column E */ f31tof50 = (): (string | number | undefined)[] => this.formGrid(this.spouseForms()) /** * Index 51: Total amount */ Totalamount = (): number | undefined => { if (this.formIndex === 0) { return this.allWithholdingForms().reduce((s, f) => s + f.ilTax, 0) } } f51 = (): number | undefined => this.Totalamount() /** * Index 52 -> 56 * Spouse form types */ formTypesColumn = (forms: WithholdingForm[]): (string | undefined)[] => [ ...forms.map((f) => f.formType), ...Array(5 - forms.length).fill(undefined) ] f52to56 = (): (string | undefined)[] => this.formTypesColumn(this.spouseForms()) /** * Index 57 -> 61 * Primary form types */ f57to61 = (): (string | undefined)[] => this.formTypesColumn(this.primaryForms()) /** * There's a second field in the Column A column, * purpose not clear. */ f62to71 = (): undefined[] => Array(10).fill(undefined) fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), ...this.f7Tof26(), this.f27(), this.f28(), this.f29(), this.f30(), ...this.f31tof50(), this.f51(), ...this.f52to56(), ...this.f57to61(), ...this.f62to71() ] } const makeILWIT = (f1040: F1040): ILWIT => new ILWIT(f1040) export default makeILWIT ================================================ FILE: src/forms/Y2020/stateForms/IL/Parameters.ts ================================================ import { FilingStatus } from 'ustaxes/core/data' const parameters = { exemptions: { [FilingStatus.S]: { incomeLowerLimit: 2325, incomeUpperLimit: 250000, exemptionAmount: 2325 }, [FilingStatus.MFJ]: { incomeLowerLimit: 4650, incomeUpperLimit: 500000, exemptionAmount: 4650 } }, taxRate: 0.0495, seniorExemption: 1000, blindExemption: 1000, earnedIncomeCreditFactor: 0.18, eicDependentCredit: 2325 } export default parameters ================================================ FILE: src/forms/Y2020/stateForms/IN/Form.ts ================================================ export default class INForm {} ================================================ FILE: src/forms/Y2020/stateForms/KS/Form.ts ================================================ export default class KSForm {} ================================================ FILE: src/forms/Y2020/stateForms/KY/Form.ts ================================================ export default class KYForm {} ================================================ FILE: src/forms/Y2020/stateForms/LA/Form.ts ================================================ export default class LAForm {} ================================================ FILE: src/forms/Y2020/stateForms/MA/Form.ts ================================================ export default class MAForm {} ================================================ FILE: src/forms/Y2020/stateForms/MD/Form.ts ================================================ export default class MDForm {} ================================================ FILE: src/forms/Y2020/stateForms/ME/Form.ts ================================================ export default class MEForm {} ================================================ FILE: src/forms/Y2020/stateForms/MI/Form.ts ================================================ export default class MIForm {} ================================================ FILE: src/forms/Y2020/stateForms/MN/Form.ts ================================================ export default class MNForm {} ================================================ FILE: src/forms/Y2020/stateForms/MO/Form.ts ================================================ export default class MOForm {} ================================================ FILE: src/forms/Y2020/stateForms/MS/Form.ts ================================================ export default class MSForm {} ================================================ FILE: src/forms/Y2020/stateForms/MT/Form.ts ================================================ export default class MTForm {} ================================================ FILE: src/forms/Y2020/stateForms/NC/Form.ts ================================================ export default class NCForm {} ================================================ FILE: src/forms/Y2020/stateForms/ND/Form.ts ================================================ export default class NDForm {} ================================================ FILE: src/forms/Y2020/stateForms/NE/Form.ts ================================================ export default class NEForm {} ================================================ FILE: src/forms/Y2020/stateForms/NH/Form.ts ================================================ export default class NHForm {} ================================================ FILE: src/forms/Y2020/stateForms/NJ/Form.ts ================================================ export default class NJForm {} ================================================ FILE: src/forms/Y2020/stateForms/NM/Form.ts ================================================ export default class NMForm {} ================================================ FILE: src/forms/Y2020/stateForms/NV/Form.ts ================================================ export default class NVForm {} ================================================ FILE: src/forms/Y2020/stateForms/NY/Form.ts ================================================ export default class NYForm {} ================================================ FILE: src/forms/Y2020/stateForms/OH/Form.ts ================================================ export default class OHForm {} ================================================ FILE: src/forms/Y2020/stateForms/OK/Form.ts ================================================ export default class OKForm {} ================================================ FILE: src/forms/Y2020/stateForms/OR/Form.ts ================================================ export default class ORForm {} ================================================ FILE: src/forms/Y2020/stateForms/PA/Form.ts ================================================ export default class PAForm {} ================================================ FILE: src/forms/Y2020/stateForms/RI/Form.ts ================================================ export default class RIForm {} ================================================ FILE: src/forms/Y2020/stateForms/SC/Form.ts ================================================ export default class SCForm {} ================================================ FILE: src/forms/Y2020/stateForms/SD/Form.ts ================================================ export default class SDForm {} ================================================ FILE: src/forms/Y2020/stateForms/TN/Form.ts ================================================ export default class TNForm {} ================================================ FILE: src/forms/Y2020/stateForms/TX/Form.ts ================================================ export default class TXForm {} ================================================ FILE: src/forms/Y2020/stateForms/UT/Form.ts ================================================ export default class UTForm {} ================================================ FILE: src/forms/Y2020/stateForms/VA/Form.ts ================================================ export default class VAForm {} ================================================ FILE: src/forms/Y2020/stateForms/VT/Form.ts ================================================ export default class VTForm {} ================================================ FILE: src/forms/Y2020/stateForms/WA/Form.ts ================================================ export default class WAForm {} ================================================ FILE: src/forms/Y2020/stateForms/WI/Form.ts ================================================ export default class WIForm {} ================================================ FILE: src/forms/Y2020/stateForms/WV/Form.ts ================================================ export default class WVForm {} ================================================ FILE: src/forms/Y2020/stateForms/WY/Form.ts ================================================ export default class WYForm {} ================================================ FILE: src/forms/Y2020/stateForms/index.ts ================================================ import F1040 from '../irsForms/F1040' import { State } from 'ustaxes/core/data' import StateForm from 'ustaxes/core/stateForms/Form' import il1040 from './IL/IL1040' import { Either } from 'ustaxes/core/util' import { createStateReturn as createStateReturnF } from '../../StateForms' import { StateFormError } from '../../StateForms' export const noFilingRequirementStates: State[] = [ 'AK', 'TN', 'WY', 'FL', 'NH', 'SD', 'TX', 'WA', 'NV' ] export const stateForms: { [K in State]?: (f1040: F1040) => StateForm } = { IL: il1040 } export const createStateReturn = ( f1040: F1040 ): Either => createStateReturnF(noFilingRequirementStates, stateForms)(f1040) ================================================ FILE: src/forms/Y2020/tests/ScheduleD.test.ts ================================================ import * as fc from 'fast-check' import { testKit, commonTests } from '.' describe('ScheduleD', () => { it('should never pass through more than allowed losses', async () => { await fc.assert( testKit.with1040Property(async (forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) if (f1040.scheduleD.isNeeded()) { expect(Math.round(f1040.l7() ?? 0)).toBeGreaterThanOrEqual( -f1040.scheduleD.l21Min() ) } return Promise.resolve() }) ) }) }) ================================================ FILE: src/forms/Y2020/tests/ScheduleEIC.test.ts ================================================ /* eslint @typescript-eslint/no-empty-function: "off" */ import * as federal from '../data/federal' import { testKit, commonTests } from '.' beforeAll(() => jest.spyOn(console, 'warn').mockImplementation(() => {})) describe('ScheduleEIC', () => { it('should disallow EIC for income below threshold', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) const formula = federal.EIC.formulas[f1040.info.taxPayer.filingStatus] if (formula !== undefined && f1040.wages() < formula[0][1].lowerBound) { expect(f1040.scheduleEIC.allowed()).toBe(false) expect(f1040.scheduleEIC.credit()).toBe(0) } return Promise.resolve() }) }) }) ================================================ FILE: src/forms/Y2020/tests/f1040.test.ts ================================================ import { displayRound } from 'ustaxes/core/irsForms/util' import { commonTests, testKit } from '.' jest.setTimeout(40000) beforeAll(() => { jest.spyOn(console, 'warn').mockImplementation((x: string) => { if (!x.includes('Removing XFA form data as pdf-lib')) { console.warn(x) } }) }) describe('f1040', () => { commonTests.run() it('should never have higher AGI than total income', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040(forms) expect(f1040).not.toBeUndefined() if (f1040 !== undefined) { expect(displayRound(f1040.l11()) ?? 0).toBeLessThanOrEqual( // It is possible for losses to create negative income. displayRound(Math.max(0, f1040.l9())) ?? 0 ) } return Promise.resolve() }) }) it('should never produce higher tax than total income', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040(forms) expect(f1040).not.toBeUndefined() if (f1040 !== undefined) { expect(displayRound(f1040.l24()) ?? 0).toBeLessThanOrEqual( displayRound(Math.max(0, f1040.l9())) ?? 0 ) } return Promise.resolve() }) }) it('should never produce tax on taxable income higher than income', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040(forms) expect(f1040).not.toBeUndefined() if (f1040 !== undefined) { // tax on taxable income should be less than taxable income if (f1040.l15() > 0) { expect(f1040.l16() ?? 0).toBeLessThan(f1040.l15()) } else { expect(f1040.l16() ?? 0).toEqual(0) } } return Promise.resolve() }) }) }) ================================================ FILE: src/forms/Y2020/tests/f8889.test.ts ================================================ /* eslint @typescript-eslint/no-empty-function: "off" */ import { FilingStatus, Income1099Type, PersonRole } from 'ustaxes/core/data' import { CURRENT_YEAR, healthSavingsAccounts } from '../data/federal' import F8889 from '../irsForms/F8889' import { cloneDeep } from 'lodash' import F1040 from '../irsForms/F1040' import { blankState } from 'ustaxes/redux/reducer' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' import { yearFormBuilder } from 'ustaxes/forms/YearForms' import { run } from 'ustaxes/core/util' import { commonTests } from '.' const baseInformation: ValidatedInformation = { ...blankState, f1099s: [ { payer: 'payer-name', type: Income1099Type.INT, form: { income: 1111111 }, personRole: PersonRole.PRIMARY } ], w2s: [ { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.SPOUSE, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, ssWithholding: 444, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 } ], estimatedTaxes: [], realEstate: [], taxPayer: { primaryPerson: { address: { address: '0001', aptNo: '', city: 'AR city', state: 'AR', zip: '1234567' }, firstName: 'payer-first-name', lastName: 'payer-last-name', isTaxpayerDependent: false, role: PersonRole.PRIMARY, ssid: '111111111' }, spouse: { firstName: 'spouse-first-name', isTaxpayerDependent: false, lastName: 'spouse-last-name', role: PersonRole.SPOUSE, ssid: '222222222' }, dependents: [], filingStatus: FilingStatus.MFS }, questions: {}, f1098es: [], stateResidencies: [{ state: 'AL' }], healthSavingsAccounts: [] } describe('Health Savings Accounts', () => { it('should not need form 8889 if there are no health savings accounts', () => { const builder = yearFormBuilder('Y2020').build(baseInformation, []) const fs = run(builder.f1040()).orThrow() expect(commonTests.findF1040OrFail(fs).f8889.isNeeded()).toEqual(false) }) it('should have a max contribution limit when covered for all months', () => { const information = cloneDeep(baseInformation) information.healthSavingsAccounts = [ { coverageType: 'self-only', contributions: 3550, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), endDate: new Date(CURRENT_YEAR, 11, 31).toISOString(), label: 'test', totalDistributions: 500, qualifiedDistributions: 500 } ] const f1040 = new F1040(information) const f8889 = new F8889(f1040, information.taxPayer.primaryPerson) expect(f8889.fullYearHsa()).toEqual(true) expect(f8889.lastMonthCoverage()).toEqual('self-only') expect(f8889.contributionLimit()).toEqual( healthSavingsAccounts.contributionLimit['self-only'] ) expect(f8889.calculatedCoverageType).toEqual('self-only') }) it('should select family coverage when both are options', () => { const information = cloneDeep(baseInformation) information.healthSavingsAccounts = [ { coverageType: 'self-only', contributions: 3550, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), endDate: new Date(CURRENT_YEAR, 11, 31).toISOString(), label: 'test', totalDistributions: 500, qualifiedDistributions: 500 }, { coverageType: 'family', contributions: 7100, personRole: PersonRole.SPOUSE, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), endDate: new Date(CURRENT_YEAR, 11, 31).toISOString(), label: 'test', totalDistributions: 500, qualifiedDistributions: 500 } ] const f1040 = new F1040(information) const f8889 = new F8889(f1040, information.taxPayer.primaryPerson) expect(f8889.fullYearHsa()).toEqual(true) expect(f8889.lastMonthCoverage()).toEqual('family') expect(f8889.contributionLimit()).toEqual( healthSavingsAccounts.contributionLimit['family'] ) expect(f8889.calculatedCoverageType).toEqual('family') }) it('should calculate a partial contribution limit correctly with a single HSA', () => { const information = cloneDeep(baseInformation) information.healthSavingsAccounts = [ { coverageType: 'family', contributions: 3550, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), // Jan 1st endDate: new Date(CURRENT_YEAR, 5, 30).toISOString(), // Jun 30th label: 'test', totalDistributions: 500, qualifiedDistributions: 500 } ] const f1040 = new F1040(information) const f8889 = new F8889(f1040, information.taxPayer.primaryPerson) expect(f8889.fullYearHsa()).toEqual(false) expect(f8889.contributionLimit()).toEqual( Math.round((healthSavingsAccounts.contributionLimit['family'] * 6) / 12) ) expect(f8889.calculatedCoverageType).toEqual('family') }) it('should calculate a partial contribution limit correctly with a multiple HSA', () => { const information = cloneDeep(baseInformation) information.healthSavingsAccounts = [ { coverageType: 'family', contributions: 3550, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), // Jan 1st endDate: new Date(CURRENT_YEAR, 5, 30).toISOString(), // Jun 30th label: 'test', totalDistributions: 500, qualifiedDistributions: 500 }, { coverageType: 'self-only', contributions: 1750, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 6, 1).toISOString(), // Jul 1st endDate: new Date(CURRENT_YEAR, 10, 30).toISOString(), // Nov 30st label: 'test', totalDistributions: 500, qualifiedDistributions: 500 } ] const f1040 = new F1040(information) const f8889 = new F8889(f1040, information.taxPayer.primaryPerson) expect(f8889.fullYearHsa()).toEqual(false) expect(f8889.contributionLimit()).toEqual( Math.round( (healthSavingsAccounts.contributionLimit['family'] * 6) / 12 + (healthSavingsAccounts.contributionLimit['self-only'] * 5) / 12 ) ) expect(f8889.calculatedCoverageType).toEqual('family') }) it('should calculate a partial contribution limit correctly with a multiple overlapping HSA', () => { const information = cloneDeep(baseInformation) information.healthSavingsAccounts = [ { coverageType: 'family', contributions: 3550, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), // Jan 1st endDate: new Date(CURRENT_YEAR, 5, 30).toISOString(), // Jun 30th label: 'test', totalDistributions: 500, qualifiedDistributions: 500 }, { coverageType: 'self-only', contributions: 1750, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 3, 1).toISOString(), // Apr 1st endDate: new Date(CURRENT_YEAR, 10, 30).toISOString(), // Nov 30st label: 'test', totalDistributions: 500, qualifiedDistributions: 500 } ] const f1040 = new F1040(information) const f8889 = new F8889(f1040, information.taxPayer.primaryPerson) expect(f8889.fullYearHsa()).toEqual(false) expect(f8889.contributionLimit()).toEqual( Math.round( (healthSavingsAccounts.contributionLimit['family'] * 6) / 12 + (healthSavingsAccounts.contributionLimit['self-only'] * 5) / 12 ) ) expect(f8889.calculatedCoverageType).toEqual('family') }) it('should split the family contribution correctly', () => { const information = cloneDeep(baseInformation) information.healthSavingsAccounts = [ { coverageType: 'family', contributions: 3550, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), // Jan 1st endDate: new Date(CURRENT_YEAR, 4, 31).toISOString(), // May 31st label: 'test', totalDistributions: 500, qualifiedDistributions: 500 }, { coverageType: 'self-only', contributions: 1750, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 5, 1).toISOString(), // Jun 1st endDate: new Date(CURRENT_YEAR, 11, 31).toISOString(), // Dec 31st label: 'test', totalDistributions: 500, qualifiedDistributions: 500 } ] const f1040 = new F1040(information) const f8889 = new F8889(f1040, information.taxPayer.primaryPerson) expect(f8889.splitFamilyContributionLimit()).toEqual(3550) expect(f8889.calculatedCoverageType).toEqual('self-only') }) it('Should apply employer contributions to only the form belonging to the right person', () => { const information = cloneDeep(baseInformation) information.w2s[0].box12 = { W: 4000 } // The w2 belongs to the spouse information.healthSavingsAccounts = [ { coverageType: 'self-only', contributions: 3550, personRole: PersonRole.PRIMARY, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), endDate: new Date(CURRENT_YEAR, 11, 31).toISOString(), label: 'test', totalDistributions: 500, qualifiedDistributions: 500 }, { coverageType: 'self-only', contributions: 3550, personRole: PersonRole.SPOUSE, startDate: new Date(CURRENT_YEAR, 0, 1).toISOString(), endDate: new Date(CURRENT_YEAR, 11, 31).toISOString(), label: 'test', totalDistributions: 500, qualifiedDistributions: 500 } ] const f1040 = new F1040(information) const f8889 = new F8889(f1040, information.taxPayer.primaryPerson) expect(f8889.l9()).toEqual(0) if (information.taxPayer.spouse !== undefined) { const f8889spouse = new F8889(f1040, information.taxPayer.spouse) expect(f8889spouse.l9()).toEqual(4000) } }) }) ================================================ FILE: src/forms/Y2020/tests/f8949.test.ts ================================================ import { testKit } from '.' describe('f8949', () => { it('should attach 8949 if there are sales this year', async () => { await testKit.with1040Assert((forms, info, assets): Promise => { if ( assets.filter((p) => p.closeDate?.getFullYear() === 2020).length > 0 ) { expect(forms.filter((s) => s.tag === 'f8949').length).toBeGreaterThan(0) } return Promise.resolve() }) }) }) ================================================ FILE: src/forms/Y2020/tests/fica.test.ts ================================================ import { fica } from '../data/federal' import F1040 from '../irsForms/F1040' import F8959 from '../irsForms/F8959' import Form from 'ustaxes/core/irsForms/Form' import { displayRound } from 'ustaxes/core/irsForms/util' import Schedule2 from '../irsForms/Schedule2' import Schedule3 from '../irsForms/Schedule3' import { claimableExcessSSTaxWithholding } from 'ustaxes/forms/Y2020/irsForms/Schedule3' import { testKit, commonTests } from '.' import { PersonRole } from 'ustaxes/core/data' beforeAll(() => { jest.spyOn(console, 'warn').mockImplementation(() => { // do nothing }) }) jest.setTimeout(10000) const hasSSRefund = (f1040: F1040): boolean => f1040.schedule3.l10() > 0 const hasAdditionalMedicareTax = (f1040: F1040): boolean => f1040.f8959.l18() > 0 /* eslint-disable @typescript-eslint/no-explicit-any */ type Constructor = new (...args: any[]) => T function hasAttachment( attachments: Form[], formType: Constructor ): boolean { return ( attachments.find((f) => { return f instanceof formType }) !== undefined ) } describe('fica', () => { it('should give refund SS tax overpayment only in some conditions', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) if (f1040.validW2s().length <= 1) { // Should never give SS refund with 1 or fewer W2s expect(hasSSRefund(f1040)).toEqual(false) } else { const ssWithheld = f1040 .validW2s() .reduce((sum, w2) => sum + w2.ssWithholding, 0) if ( f1040.wages() <= fica.maxIncomeSSTaxApplies || f1040.validW2s().some((w2) => w2.ssWithholding > fica.maxSSTax) || ssWithheld < fica.maxSSTax ) { // Should never give SS refund if W2 income below max threshold, some W2 has // withheld over the max, or there is no SS withholding to refund. expect(hasSSRefund(f1040)).toEqual(false) } else { // Otherwise, should always give SS refund, and attach schedule 3 expect(hasSSRefund(f1040)).toEqual(true) expect(hasAttachment(forms, Schedule3)).toEqual(true) } } return Promise.resolve() }) }) it('should give SS refund based on filing status', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) if (hasSSRefund(f1040)) { const s3l10 = f1040.schedule3.l10() expect(displayRound(s3l10)).not.toBeUndefined() expect(s3l10).toBeGreaterThan(0) const ssWithheld = f1040 .validW2s() .reduce((sum, w2) => sum + w2.ssWithholding, 0) expect(s3l10).toEqual(ssWithheld - fica.maxSSTax) } return Promise.resolve() }) }) it('should not give a refund if each person has less than the max', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) f1040.info.w2s = [ { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.SPOUSE, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, ssWithholding: fica.maxSSTax, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 }, { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.PRIMARY, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, ssWithholding: fica.maxSSTax, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 } ] expect(claimableExcessSSTaxWithholding(f1040.info.w2s)).toEqual(0) return Promise.resolve() }) }) it('should give a refund if a person has more than the max if they have two w2s', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) f1040.info.w2s = [ { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.SPOUSE, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, ssWithholding: fica.maxSSTax, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 }, { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.SPOUSE, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, // This person has already contributed to the max for their other w2 so the refund should equal this amount ssWithholding: 1000, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 }, { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.PRIMARY, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, ssWithholding: fica.maxSSTax, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 } ] expect(claimableExcessSSTaxWithholding(f1040.info.w2s)).toEqual(1000) return Promise.resolve() }) }) it('should add Additional Medicare Tax form 8959', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) const filingStatus = f1040.info.taxPayer.filingStatus // Should add Additional Medicare Tax if medicare wages over threshold if ( f1040.medicareWages() > fica.additionalMedicareTaxThreshold(filingStatus) ) { expect(hasAdditionalMedicareTax(f1040)).toEqual(true) // Should attach both S2 and F8959 to return expect(hasAttachment(forms, Schedule2)).toEqual(true) expect(hasAttachment(forms, F8959)).toEqual(true) } else { expect(hasAdditionalMedicareTax(f1040)).toEqual(false) expect(hasAttachment(forms, F8959)).toEqual(false) } return Promise.resolve() }) }) it('should add Additional Medicare Tax based on filing status', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) if (hasAdditionalMedicareTax(f1040)) { const filingStatus = f1040.info.taxPayer.filingStatus const incomeOverThreshold = f1040.medicareWages() - fica.additionalMedicareTaxThreshold(filingStatus) expect(incomeOverThreshold).toBeGreaterThan(0) // Adds the right amount of additional tax const s2l8 = f1040.f8959.l18() expect(s2l8).not.toBeUndefined() expect(displayRound(s2l8)).toEqual( displayRound(incomeOverThreshold * fica.additionalMedicareTaxRate) ) // Also adds in the extra Medicare tax withheld to 1040 taxes already paid const medicareWithheld = f1040 .validW2s() .reduce((sum, w2) => sum + w2.medicareWithholding, 0) const regularWithholding = Math.round( fica.regularMedicareTaxRate * f1040.medicareWages() ) if (medicareWithheld > regularWithholding) { const f1040l25c = f1040.l25c() expect(f1040l25c).not.toBeUndefined() const additionalWithheld = medicareWithheld - regularWithholding expect(displayRound(f1040l25c)).toEqual( displayRound(additionalWithheld) ) } else { expect(displayRound(f1040.l25c())).toBeUndefined() } } return Promise.resolve() }) }) }) ================================================ FILE: src/forms/Y2020/tests/index.ts ================================================ import CommonTests, { FormTestInfo } from 'ustaxes/forms/tests/CommonTests' import TestKit from 'ustaxes/forms/tests/TestKit' import F1040 from '../irsForms/F1040' export const testKit = new TestKit('Y2020') class FormTestInfo2020 extends FormTestInfo { getAssets = (f1040: F1040) => f1040.assets getInfo = (f1040: F1040) => f1040.info } export const commonTests = new CommonTests( testKit, new FormTestInfo2020() ) ================================================ FILE: src/forms/Y2020/tests/states/il.test.ts ================================================ import * as fc from 'fast-check' import F1040 from '../../irsForms/F1040' import Form from 'ustaxes/core/irsForms/Form' import { create1040 } from '../../irsForms/Main' import { Information, PersonRole } from 'ustaxes/core/data' import { createStateReturn } from '../../stateForms' import { ILWIT } from '../../stateForms/IL/ILWit' import { isLeft } from 'ustaxes/core/util' import StateForm from 'ustaxes/core/stateForms/Form' import * as arbitraries from 'ustaxes/core/tests/arbitraries' import { fail } from 'assert' const withStateReturn = ( info: Information, logContext: fc.ContextValue, test: (f1040Forms: [F1040, Form[]], stateForms: StateForm[]) => void ): void => { const f1040Result = create1040(info, []) if (isLeft(f1040Result)) { // ignore error infos with no 1040 logContext.log(f1040Result.left.join(';')) return } const [f1040] = f1040Result.right const stateReturn = createStateReturn(f1040) if (isLeft(stateReturn)) { fail(stateReturn.left.join(';')) } test(f1040Result.right, stateReturn.right) } const A = new arbitraries.Arbitraries(2020) describe('il year 2020', () => { it('should produce correct withholding attachments in', () => { fc.assert( fc.property(A.information(), fc.context(), (info, ctx) => { info.stateResidencies = [{ state: 'IL' }] info.w2s.forEach((w2) => { w2.state = 'IL' }) withStateReturn(info, ctx, (_, stateForms) => { ctx.log(stateForms.map((f) => f.formName).join(';')) expect(stateForms.filter((f) => f.formName === 'IL-WIT').length).toBe( Math.ceil( Math.max( ...[PersonRole.PRIMARY, PersonRole.SPOUSE].map( (r) => info.w2s.filter( (w2) => w2.personRole === r && (w2.stateWithholding ?? 0) > 0 ).length ) ) / ILWIT.WITHHOLDING_FORMS_PER_PAGE ) ) }) }) ) }) }) ================================================ FILE: src/forms/Y2021/data/federal.ts ================================================ import { FilingStatus } from 'ustaxes/core/data' import { linear, Piecewise } from 'ustaxes/core/util' export const CURRENT_YEAR = 2021 interface TaggedAmount { name: string amount: number } interface Brackets { brackets: number[] } interface Deductions { deductions: TaggedAmount[] exemptions: TaggedAmount[] } interface Rates { rates: number[] } interface FederalBrackets { ordinary: Rates & { status: { [key in FilingStatus]: Brackets & Deductions } } longTermCapGains: Rates & { status: { [key in FilingStatus]: Brackets } } } const federalBrackets: FederalBrackets = { ordinary: { rates: [10, 12, 22, 24, 32, 35, 37], status: { [FilingStatus.S]: { brackets: [9950, 40525, 86375, 164925, 209425, 523600], deductions: [ { name: 'Standard Deduction (Single)', amount: 12550 }, { name: 'Standard Deduction (Single) with 1 age or blindness allowance', amount: 14250 }, { name: 'Standard Deduction (Single) with 2 age or blindness allowances', amount: 15950 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] }, [FilingStatus.MFJ]: { brackets: [19900, 81050, 172750, 329850, 418850, 628300], deductions: [ { name: 'Standard Deduction (Married)', amount: 25100 }, { name: 'Standard Deduction (Married) with 1 age or blindness allowance', amount: 26450 }, { name: 'Standard Deduction (Married) with 2 age or blindness allowances', amount: 27800 }, { name: 'Standard Deduction (Married) with 3 age or blindness allowances', amount: 29150 }, { name: 'Standard Deduction (Married) with 4 age or blindness allowances', amount: 30500 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] }, [FilingStatus.W]: { brackets: [19900, 81050, 172750, 329850, 418850, 628300], deductions: [ { name: 'Standard Deduction (Widowed)', amount: 24800 }, { name: 'Standard Deduction (Widowed) with 1 age or blindness allowance', amount: 26450 }, { name: 'Standard Deduction (Widowed) with 2 age or blindness allowances', amount: 27800 } ], exemptions: [ { name: 'Standard Exemption (Widowed)', amount: 0 } ] }, [FilingStatus.MFS]: { brackets: [9950, 40525, 86375, 164925, 209425, 314150], deductions: [ { name: 'Standard Deduction (Married Filing Separately)', amount: 12550 }, { name: 'Standard Deduction (Married Filing Separately) with 1 age or blindness allowance', amount: 13900 }, { name: 'Standard Deduction (Married Filing Separately) with 2 age or blindness allowances', amount: 15250 }, { name: 'Standard Deduction (Married Filing Separately) with 3 age or blindness allowances', amount: 16600 }, { name: 'Standard Deduction (Married Filing Separately) with 4 age or blindness allowances', amount: 17950 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] }, [FilingStatus.HOH]: { brackets: [14200, 54200, 86350, 164900, 209400, 523600], deductions: [ { name: 'Standard Deduction (Head of Household)', amount: 18800 }, { name: 'Standard Deduction (Head of Household) with 1 age or blindness allowance', amount: 20500 }, { name: 'Standard Deduction (Head of Household) with 2 age or blindness allowances', amount: 22200 } ], exemptions: [ { name: 'Standard Exemption (Single)', amount: 0 } ] } } }, longTermCapGains: { rates: [0, 15, 20], status: { [FilingStatus.S]: { brackets: [40400, 445850] }, [FilingStatus.MFJ]: { brackets: [80800, 501600] }, [FilingStatus.W]: { brackets: [80800, 501600] }, [FilingStatus.MFS]: { brackets: [40400, 250800] }, [FilingStatus.HOH]: { brackets: [54100, 473750] } } } } export const fica = { maxSSTax: 8853.6, maxIncomeSSTaxApplies: 142800, regularMedicareTaxRate: 1.45 / 100, additionalMedicareTaxRate: 0.9 / 100, additionalMedicareTaxThreshold: (filingStatus: FilingStatus): number => { switch (filingStatus) { case FilingStatus.MFJ: { return 250000 } case FilingStatus.MFS: { return 125000 } default: { return 200000 // Single, Head of Household, Windower } } } } // Net Investment Income Tax calculated on form 8960 export const netInvestmentIncomeTax = { taxRate: 0.038, // 3.8% taxThreshold: (filingStatus: FilingStatus): number => { switch (filingStatus) { case FilingStatus.MFJ: { return 250000 } case FilingStatus.W: { return 250000 } case FilingStatus.MFS: { return 125000 } default: { return 200000 // Single, Head of Household } } } } export const healthSavingsAccounts = { contributionLimit: { 'self-only': 3600, family: 7200 } } // line 11 caps based on step one in instructions const line11Caps = [21430, 42158, 47915, 51464] const line11MfjCaps = [27830, 48108, 53865, 57414] type Point = [number, number] // Provided a list of points, create a piecewise function // that makes linear segments through the list of points. const toPieceWise = (points: Point[]): Piecewise => points .slice(0, points.length - 1) .map((point, idx) => [point, points[idx + 1]]) .map(([[x1, y1], [x2, y2]]) => ({ // starting point slope intercept lowerBound: x1, f: linear((y2 - y1) / (x2 - x1), y1 - (x1 * (y2 - y1)) / (x2 - x1)) })) // These points are taken directly from IRS publication // IRS Rev. Proc. 2019-44 for tax year 2020 // https://www.irs.gov/pub/irs-drop/rp-19-44.pdf const unmarriedFormulas: Piecewise[] = (() => { const points: Point[][] = [ [ [0, 0], [7030, 538], [8790, 3584], [15820, 0] ], // 0 [ [0, 0], [10540, 3584], [19330, 3584], [41756, 0] ], // 1 [ [0, 0], [14800, 5920], [19330, 5920], [47440, 0] ], // 2 [ [0, 0], [14800, 6660], [19330, 6660], [50954, 0] ] // 3 or more ] return points.map((ps: Point[]) => toPieceWise(ps)) })() const marriedFormulas: Piecewise[] = (() => { const points: Point[][] = [ [ [0, 0], [7030, 538], [14680, 3584], [21710, 0] ], // 0 [ [0, 0], [10540, 3584], [25220, 3584], [47646, 0] ], // 1 [ [0, 0], [14800, 5920], [25220, 5920], [53330, 0] ], // 2 [ [0, 0], [14800, 6660], [25220, 6660], [56844, 0] ] // 3 or more ] return points.map((ps) => toPieceWise(ps)) })() interface EICDef { caps: { [k in FilingStatus]: number[] | undefined } maxInvestmentIncome: number formulas: { [k in FilingStatus]: Piecewise[] | undefined } } export const QualifyingDependents = { childMaxAge: 17, qualifyingDependentMaxAge: 19, qualifyingStudentMaxAge: 24 } export const EIC: EICDef = { // credit caps for number of children (0, 1, 2, 3 or more): // Step 1 caps: { [FilingStatus.S]: line11Caps, [FilingStatus.W]: line11Caps, [FilingStatus.HOH]: line11Caps, [FilingStatus.MFS]: undefined, [FilingStatus.MFJ]: line11MfjCaps }, maxInvestmentIncome: 10000, formulas: { [FilingStatus.S]: unmarriedFormulas, [FilingStatus.W]: unmarriedFormulas, [FilingStatus.HOH]: unmarriedFormulas, [FilingStatus.MFS]: undefined, [FilingStatus.MFJ]: marriedFormulas } } export default federalBrackets // Constants used in the social security benefits worksheet interface SocialSecurityBenefitsDef { caps: { [k in FilingStatus]: { l8: number; l10: number } } } export const SSBenefits: SocialSecurityBenefitsDef = { caps: { [FilingStatus.S]: { l8: 25000, l10: 9000 }, [FilingStatus.W]: { l8: 25000, l10: 9000 }, [FilingStatus.HOH]: { l8: 25000, l10: 9000 }, [FilingStatus.MFS]: { l8: 25000, l10: 9000 }, [FilingStatus.MFJ]: { l8: 32000, l10: 12000 } } } ================================================ FILE: src/forms/Y2021/irsForms/F1040.ts ================================================ import { AccountType, Dependent, FilingStatus, IncomeW2, PersonRole, PlanType1099, Asset } from 'ustaxes/core/data' import federalBrackets, { CURRENT_YEAR } from '../data/federal' import F4972 from './F4972' import F5695 from './F5695' import F8814 from './F8814' import F8888 from './F8888' import F8889 from './F8889' import F8910 from './F8910' import F8936 from './F8936' import F8959 from './F8959' import F8995, { getF8995PhaseOutIncome } from './F8995' import F8995A from './F8995A' import Schedule1 from './Schedule1' import Schedule2 from './Schedule2' import Schedule3 from './Schedule3' import Schedule8812 from './Schedule8812' import ScheduleA from './ScheduleA' import ScheduleD from './ScheduleD' import ScheduleE from './ScheduleE' import ScheduleSE from './ScheduleSE' import ScheduleEIC from './ScheduleEIC' import ScheduleR from './ScheduleR' import Form, { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import ScheduleB from './ScheduleB' import { computeOrdinaryTax } from './TaxTable' import SDQualifiedAndCapGains from './worksheets/SDQualifiedAndCapGains' import QualifyingDependents from './worksheets/QualifyingDependents' import SocialSecurityBenefitsWorksheet from './worksheets/SocialSecurityBenefits' import F4797 from './F4797' import StudentLoanInterestWorksheet from './worksheets/StudentLoanInterestWorksheet' import F1040V from './F1040v' import _ from 'lodash' import F8960 from './F8960' import F4952 from './F4952' import F2555 from './F2555' import F4563 from './F4563' import F8863 from './F8863' import F8962 from './F8962' import F4136 from './F4136' import F2439 from './F2439' import F2441 from './F2441' import ScheduleC from './ScheduleC' import F8949 from './F8949' import F6251 from './F6251' import F4137 from './F4137' import F8919 from './F8919' import F8853 from './F8853' import F8582 from './F8582' import { Field } from 'ustaxes/core/pdfFiller' import F1040Base, { ValidatedInformation } from 'ustaxes/forms/F1040Base' import F1040Attachment from './F1040Attachment' export default class F1040 extends F1040Base { tag: FormTag = 'f1040' sequenceIndex = 0 assets: Asset[] schedule1: Schedule1 schedule2: Schedule2 schedule3: Schedule3 scheduleA: ScheduleA scheduleB: ScheduleB scheduleC?: ScheduleC scheduleD: ScheduleD scheduleE: ScheduleE scheduleSE: ScheduleSE scheduleEIC: ScheduleEIC scheduleR?: ScheduleR schedule8812: Schedule8812 f2439?: F2439 f2441?: F2441 f2555?: F2555 f4136?: F4136 f4137?: F4137 f4563?: F4563 f4797?: F4797 f4952?: F4952 f4972?: F4972 f5695?: F5695 f6251: F6251 f8814?: F8814 f8582?: F8582 f8853?: F8853 f8863?: F8863 f8888?: F8888 f8889: F8889 f8889Spouse?: F8889 f8910?: F8910 f8919?: F8919 f8936?: F8936 f8949: F8949 _f8949s?: F8949[] f8959: F8959 f8960: F8960 f8962?: F8962 f8995?: F8995 | F8995A qualifiedAndCapGainsWorksheet?: SDQualifiedAndCapGains studentLoanInterestWorksheet?: StudentLoanInterestWorksheet socialSecurityBenefitsWorksheet?: SocialSecurityBenefitsWorksheet qualifyingDependents: QualifyingDependents constructor(info: ValidatedInformation, assets: Asset[]) { super(info) this.assets = assets this.qualifyingDependents = new QualifyingDependents(this) this.scheduleA = new ScheduleA(this) this.scheduleB = new ScheduleB(this) this.scheduleD = new ScheduleD(this) this.scheduleE = new ScheduleE(this) this.scheduleEIC = new ScheduleEIC(this) this.scheduleSE = new ScheduleSE(this) this.schedule1 = new Schedule1(this) this.schedule2 = new Schedule2(this) this.schedule3 = new Schedule3(this) this.schedule8812 = new Schedule8812(this) this.f6251 = new F6251(this) this.f8949 = new F8949(this) this.f8889 = new F8889(this, this.info.taxPayer.primaryPerson) // add in separate form 8889 for the spouse if (this.info.taxPayer.spouse) { this.f8889Spouse = new F8889(this, this.info.taxPayer.spouse) } this.f8959 = new F8959(this) this.f8960 = new F8960(this) if (this.f1099ssas().length > 0) { const ssws = new SocialSecurityBenefitsWorksheet(this) this.socialSecurityBenefitsWorksheet = ssws } if (this.info.f1098es.length > 0) { this.studentLoanInterestWorksheet = new StudentLoanInterestWorksheet( this, this.info.f1098es ) } if (this.totalQbi() > 0) { const formAMinAmount = getF8995PhaseOutIncome( this.info.taxPayer.filingStatus ) if (this.l11() - this.l12c() >= formAMinAmount) { this.f8995 = new F8995A(this) } else { this.f8995 = new F8995(this) } } } get f8949s(): F8949[] { if (this._f8949s === undefined) { this._f8949s = [this.f8949, ...this.f8949.copies()] } return this._f8949s } totalQbi = () => this.info.scheduleK1Form1065s .map((k1) => k1.section199AQBI) .reduce((c, a) => c + a, 0) toString = (): string => ` Form 1040 generated from information: Information: ${JSON.stringify(this.info)} ` // TODO - get from W2 box 12, code Q nonTaxableCombatPay = (): number | undefined => undefined schedules = (): Form[] => { const res1: (F1040Attachment | undefined)[] = [ this.scheduleA, this.scheduleB, this.scheduleD, this.scheduleE, this.scheduleSE, this.scheduleR, this.scheduleEIC, this.schedule8812, this.f4797, this.f4952, this.f4972, this.f5695, this.f6251, this.f8814, this.f8888, this.f8889, this.f8889Spouse, this.f8910, this.f8936, this.f8949, this.f8959, this.f8960, this.f8995, this.schedule1, this.schedule2, this.schedule3 ] const res = _.compact(res1) .filter((f) => f.isNeeded()) .flatMap((f) => [f, ...f.copies()]) // Attach payment voucher to front if there is a payment due if (this.l37() > 0) { res.push(new F1040V(this)) } return [this, ...res].sort((a, b) => a.sequenceIndex - b.sequenceIndex) } // born before 1957/01/02 bornBeforeDate = (): boolean => this.info.taxPayer.primaryPerson.dateOfBirth < new Date(CURRENT_YEAR - 64, 0, 2) blind = (): boolean => this.info.taxPayer.primaryPerson.isBlind spouseBeforeDate = (): boolean => (this.info.taxPayer.spouse?.dateOfBirth ?? new Date()) < new Date(CURRENT_YEAR - 64, 0, 2) spouseBlind = (): boolean => this.info.taxPayer.spouse?.isBlind ?? false validW2s = (): IncomeW2[] => { if (this.info.taxPayer.filingStatus === FilingStatus.MFS) { return this.info.w2s.filter((w2) => w2.personRole === PersonRole.PRIMARY) } return this.info.w2s } wages = (): number => this.validW2s().reduce((res, w2) => res + w2.income, 0) medicareWages = (): number => this.validW2s().reduce((res, w2) => res + w2.medicareIncome, 0) occupation = (r: PersonRole): string | undefined => this.info.w2s.find((w2) => w2.personRole === r && w2.occupation !== '') ?.occupation standardDeduction = (): number | undefined => { const filingStatus = this.info.taxPayer.filingStatus const allowances = [ this.bornBeforeDate(), this.blind(), this.spouseBeforeDate(), this.spouseBlind() ].reduce((res, e) => res + +!!e, 0) if ( this.info.taxPayer.primaryPerson.isTaxpayerDependent || (this.info.taxPayer.spouse?.isTaxpayerDependent ?? false) ) { const l4a = Math.min( federalBrackets.ordinary.status[filingStatus].deductions[0].amount, this.wages() > 750 ? this.wages() + 350 : 1100 ) if (allowances > 0) { if ( filingStatus === FilingStatus.HOH || filingStatus === FilingStatus.S ) { return l4a + allowances * 1700 } else { return l4a + allowances * 1350 } } else { return l4a } } return federalBrackets.ordinary.status[filingStatus].deductions[allowances] .amount } totalQualifiedDividends = (): number => this.f1099Divs() .map((f) => f.form.qualifiedDividends) .reduce((l, r) => l + r, 0) totalGrossDistributionsFromIra = (): number => this.info.individualRetirementArrangements.reduce( (res, i) => res + i.grossDistribution, 0 ) totalTaxableFromIra = (): number => this.info.individualRetirementArrangements.reduce( (r, i) => r + i.taxableAmount, 0 ) totalGrossDistributionsFrom1099R = (planType: PlanType1099): number => this.f1099rs() .filter((element) => element.form.planType === planType) .reduce((res, f1099) => res + f1099.form.grossDistribution, 0) totalTaxableFrom1099R = (planType: PlanType1099): number => this.f1099rs() .filter((element) => element.form.planType === planType) .reduce((res, f1099) => res + f1099.form.taxableAmount, 0) l1 = (): number => this.wages() l2a = (): number | undefined => this.scheduleB.l3() l2b = (): number | undefined => this.scheduleB.to1040l2b() l3a = (): number | undefined => this.totalQualifiedDividends() l3b = (): number | undefined => this.scheduleB.to1040l3b() // This is the value of box 1 in 1099-R forms coming from IRAs l4a = (): number | undefined => this.totalGrossDistributionsFromIra() // This should be the value of box 2a in 1099-R coming from IRAs l4b = (): number | undefined => this.totalTaxableFromIra() // This is the value of box 1 in 1099-R forms coming from pensions/annuities l5a = (): number | undefined => this.totalGrossDistributionsFrom1099R(PlanType1099.Pension) // this is the value of box 2a in 1099-R forms coming from pensions/annuities l5b = (): number | undefined => this.totalTaxableFrom1099R(PlanType1099.Pension) // The sum of box 5 from SSA-1099 l6a = (): number | undefined => this.socialSecurityBenefitsWorksheet?.l1() // calculation of the taxable amount of line 6a based on other income l6b = (): number | undefined => this.socialSecurityBenefitsWorksheet?.taxableAmount() l7Box = (): boolean => !this.scheduleD.isNeeded() l7 = (): number | undefined => this.scheduleD.to1040() l8 = (): number | undefined => this.schedule1.l10() l9 = (): number => sumFields([ this.l1(), this.l2b(), this.l3b(), this.l4b(), this.l5b(), this.l6b(), this.l7(), this.l8() ]) l10 = (): number | undefined => this.schedule1.to1040Line10() l11 = (): number => Math.max(0, this.l9() - (this.l10() ?? 0)) l12a = (): number | undefined => { if (this.scheduleA.isNeeded()) { return this.scheduleA.deductions() } return this.standardDeduction() } // TODO: Charitable contributions with standard deduction l12b = (): number | undefined => undefined l12c = (): number => sumFields([this.l12a(), this.l12b()]) l13 = (): number | undefined => this.f8995?.deductions() l14 = (): number => sumFields([this.l12c(), this.l13()]) l15 = (): number => Math.max(0, this.l11() - this.l14()) f8814Box = (): boolean | undefined => this.f8814 !== undefined f4972Box = (): boolean | undefined => this.f4972 !== undefined // TODO: tax from other form? otherFormBox = (): boolean => false otherFormName = (): string | undefined => undefined computeTax = (): number | undefined => { if ( this.scheduleD.computeTaxOnQDWorksheet() || this.totalQualifiedDividends() > 0 ) { this.qualifiedAndCapGainsWorksheet = new SDQualifiedAndCapGains(this) return this.qualifiedAndCapGainsWorksheet.tax() } return computeOrdinaryTax(this.info.taxPayer.filingStatus, this.l15()) } l16 = (): number | undefined => sumFields([this.f8814?.tax(), this.f4972?.tax(), this.computeTax()]) l17 = (): number | undefined => this.schedule2.l3() l18 = (): number => sumFields([this.l16(), this.l17()]) l19 = (): number | undefined => this.schedule8812.to1040Line19() l20 = (): number | undefined => this.schedule3.isNeeded() ? this.schedule3.l7() : undefined l21 = (): number => sumFields([this.l19(), this.l20()]) l22 = (): number => Math.max(0, this.l18() - this.l21()) l23 = (): number | undefined => this.schedule2.l21() l24 = (): number => sumFields([this.l22(), this.l23()]) l25a = (): number => this.validW2s().reduce((res, w2) => res + w2.fedWithholding, 0) // tax withheld from 1099s l25b = (): number => this.f1099rs().reduce( (res, f1099) => res + f1099.form.federalIncomeTaxWithheld, 0 ) + this.f1099ssas().reduce( (res, f1099) => res + f1099.form.federalIncomeTaxWithheld, 0 ) // TODO: form(s) W-2G box 4, schedule K-1, form 1042-S, form 8805, form 8288-A l25c = (): number | undefined => this.f8959.l24() l25d = (): number => sumFields([this.l25a(), this.l25b(), this.l25c()]) l26 = (): number => this.info.estimatedTaxes.reduce((res, et) => res + et.payment, 0) l27a = (): number => this.scheduleEIC.isNeeded() ? this.scheduleEIC.credit() : 0 // TODO: handle taxpayers between 1998 and 2004 that // can claim themselves for eic. l27acheckBox = (): boolean => false // TODO: nontaxable combat pay l27b = (): number | undefined => undefined // TODO: prior year earned income l27c = (): number | undefined => undefined l28 = (): number | undefined => this.schedule8812.to1040Line28() l29 = (): number | undefined => this.f8863?.l8() // TODO: recovery rebate credit? l30 = (): number | undefined => undefined l31 = (): number | undefined => this.schedule3.isNeeded() ? this.schedule3.l15() : undefined l32 = (): number => sumFields([this.l27a(), this.l28(), this.l29(), this.l30(), this.l31()]) l33 = (): number => sumFields([this.l25d(), this.l26(), this.l32()]) l34 = (): number => Math.max(0, this.l33() - this.l24()) // TODO: assuming user wants amount refunded // rather than applied to estimated tax l35a = (): number => this.l34() l36 = (): number => Math.max(0, this.l34() - this.l35a()) l37 = (): number => Math.max(0, this.l24() - this.l33()) // TODO - estimated tax penalty l38 = (): number | undefined => undefined _depField = (idx: number): string | boolean => { const deps: Dependent[] = this.info.taxPayer.dependents // Based on the PDF row we are on, select correct dependent const depIdx = Math.floor(idx / 5) const depFieldIdx = idx % 5 let fieldArr = ['', '', '', false, false] if (depIdx < deps.length) { const dep = deps[depIdx] // Based on the PDF column, select the correct field fieldArr = [ `${dep.firstName} ${dep.lastName}`, dep.ssid, dep.relationship, this.qualifyingDependents.qualifiesChild(dep), this.qualifyingDependents.qualifiesOther(dep) ] } return fieldArr[depFieldIdx] } // 1040 allows 4 dependents listed without a supplemental schedule, // so create field mappings for 4x5 grid of fields _depFieldMappings = (): Array => Array.from(Array(20)).map((u, n: number) => this._depField(n)) fields = (): Field[] => [ this.info.taxPayer.filingStatus === FilingStatus.S, this.info.taxPayer.filingStatus === FilingStatus.MFJ, this.info.taxPayer.filingStatus === FilingStatus.MFS, this.info.taxPayer.filingStatus === FilingStatus.HOH, this.info.taxPayer.filingStatus === FilingStatus.W, // TODO: implement non dependent child for HOH and QW this.info.taxPayer.filingStatus === 'MFS' ? this.spouseFullName() : '', this.info.taxPayer.primaryPerson.firstName, this.info.taxPayer.primaryPerson.lastName, this.info.taxPayer.primaryPerson.ssid, this.info.taxPayer.filingStatus === FilingStatus.MFJ ? this.info.taxPayer.spouse?.firstName : '', this.info.taxPayer.filingStatus === FilingStatus.MFJ ? this.info.taxPayer.spouse?.lastName ?? '' : '', this.info.taxPayer.spouse?.ssid, this.info.taxPayer.primaryPerson.address.address, this.info.taxPayer.primaryPerson.address.aptNo, this.info.taxPayer.primaryPerson.address.city, this.info.taxPayer.primaryPerson.address.state, this.info.taxPayer.primaryPerson.address.zip, this.info.taxPayer.primaryPerson.address.foreignCountry, this.info.taxPayer.primaryPerson.address.province, this.info.taxPayer.primaryPerson.address.postalCode, false, // election campaign boxes false, this.info.questions.CRYPTO ?? false, !(this.info.questions.CRYPTO ?? false), this.info.taxPayer.primaryPerson.isTaxpayerDependent, this.info.taxPayer.spouse?.isTaxpayerDependent ?? false, false, // TODO: spouse itemizes separately, this.bornBeforeDate(), this.blind(), this.spouseBeforeDate(), this.spouseBlind(), this.info.taxPayer.dependents.length > 4, ...this._depFieldMappings(), this.l1(), this.l2a(), this.l2b(), this.l3a(), this.l3b(), this.l4a(), this.l4b(), this.l5a(), this.l5b(), this.l6a(), this.l6b(), this.l7Box(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12a(), this.l12b(), this.l12c(), this.l13(), this.l14(), this.l15(), this.f8814Box(), this.f4972Box(), this.otherFormBox(), this.otherFormName(), this.l16(), this.l17(), this.l18(), this.l19(), this.l20(), this.l21(), this.l22(), this.l23(), this.l24(), this.l25a(), this.l25b(), this.l25c(), this.l25d(), this.l26(), this.l27a(), this.l27acheckBox(), this.l27b(), this.l27c(), this.l28(), this.l29(), this.l30(), this.l31(), this.l32(), this.l33(), this.l34(), this.f8888 !== undefined, this.l35a(), this.info.refund?.routingNumber, this.info.refund?.accountType === AccountType.checking, this.info.refund?.accountType === AccountType.savings, this.info.refund?.accountNumber, this.l36(), this.l37(), this.l38(), // TODO: 3rd party false, false, '', '', '', this.occupation(PersonRole.PRIMARY), // TODO: pin numbers '', this.occupation(PersonRole.SPOUSE), '', this.info.taxPayer.contactPhoneNumber, this.info.taxPayer.contactEmail, // Paid preparer fields: '', '', false, '', '', '', '' ].map((x) => (x === undefined ? '' : x)) } ================================================ FILE: src/forms/Y2021/irsForms/F1040Attachment.ts ================================================ import Form from 'ustaxes/core/irsForms/Form' import F1040 from './F1040' abstract class F1040Attachment extends Form { f1040: F1040 constructor(f1040: F1040) { super() this.f1040 = f1040 } isNeeded = (): boolean => true copies = (): F1040Attachment[] => [] } export abstract class Worksheet { f1040: F1040 constructor(f1040: F1040) { this.f1040 = f1040 } } export default F1040Attachment ================================================ FILE: src/forms/Y2021/irsForms/F1040v.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Field } from 'ustaxes/core/pdfFiller' export default class F1040V extends F1040Attachment { tag: FormTag = 'f1040v' sequenceIndex = -1 fields = (): Field[] => { const tp = this.f1040.info.taxPayer const taxOwed = this.f1040.l37() const result = [ tp.primaryPerson.ssid, tp.spouse?.ssid, taxOwed.toFixed(2), // dollars tp.primaryPerson.firstName, tp.primaryPerson.lastName, tp.spouse?.firstName, tp.spouse?.lastName, tp.primaryPerson.address.address, tp.primaryPerson.address.aptNo, tp.primaryPerson.address.city, tp.primaryPerson.address.state, tp.primaryPerson.address.zip, tp.primaryPerson.address.foreignCountry, tp.primaryPerson.address.province, tp.primaryPerson.address.postalCode ] return result } } ================================================ FILE: src/forms/Y2021/irsForms/F2439.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * TODO: not implemented * Referenced Schedule 3 line 13a */ export default class F2439 extends F1040Attachment { tag: FormTag = 'f2439' sequenceIndex = 999 credit = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F2441.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * TODO: Credit for child and dependent care expenses */ export default class F2441 extends F1040Attachment { tag: FormTag = 'f2441' sequenceIndex = 999 credit = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F2555.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Impacts EIC, 1040 instructions L27 step 1 squestion 4 */ export default class F2555 extends F1040Attachment { tag: FormTag = 'f2555' sequenceIndex = 34 // TODO - Required from SDCapitalGainWorksheet l3 = (): number | undefined => undefined // TODO - required from 6251 l36 = (): number => 0 // TODO - required from 6251 l42 = (): number => 0 // TODO - required from 8812 l45 = (): number => 0 // TODO - required from 6251 & 8812 l50 = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F4136.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * TODO: not implemented * Credit for federal tax on fuels */ export default class F4136 extends F1040Attachment { tag: FormTag = 'f4136' sequenceIndex = 999 credit = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F4137.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' // TODO export default class F4137 extends F1040Attachment { tag = 'f4137' sequenceIndex = 999 l6 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F4563.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Exclusion of income for residents of American Somoa * Impacts 8812, */ export default class F4563 extends F1040Attachment { sequenceIndex = 563 tag: FormTag = 'f4563' // TODO - required from 8812 l15 = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F4797.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Impacts EIC, 1040 instructions L27 step 2 question 3 * Not implemented yet */ export default class F4797 extends F1040Attachment { tag: FormTag = 'f4797' sequenceIndex = 999 // TODO, required from schedule EIC, PUB 596, worksheet 1 l7 = (): number | undefined => undefined l8 = (): number | undefined => undefined l9 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F4952.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Impacts Schedule D, capital gains and taxes worksheet, * TODO: Not implemented yet */ export default class F4952 extends F1040Attachment { tag: FormTag = 'f4952' sequenceIndex = 999 l4e = (): number | undefined => undefined l4g = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F4972.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * TODO: Not implemented yet */ export default class F4972 extends F1040Attachment { tag: FormTag = 'f4972' sequenceIndex = 999 tax = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F5695.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented yet */ export default class F5695 extends F1040Attachment { tag: FormTag = 'f5695' sequenceIndex = 999 l30 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F6168.ts ================================================ import { Field } from 'ustaxes/core/pdfFiller' import F1040Attachment from './F1040Attachment' /** * Referenced from line 21 of Schedule E * TODO: Not implemented */ export default class F6168 extends F1040Attachment { sequenceIndex = 999 tag = 'f6168' fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F6251.ts ================================================ import F1040Attachment from './F1040Attachment' import { FilingStatus, PersonRole } from 'ustaxes/core/data' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Field } from 'ustaxes/core/pdfFiller' type Part3 = Partial<{ l12: number l13: number l14: number l15: number l16: number l17: number l18: number l19: number l20: number l21: number l22: number l23: number l24: number l25: number l26: number l27: number l28: number l29: number l30: number l31: number l32: number l33: number l34: number l35: number l36: number l37: number l38: number l39: number l40: number }> export default class F6251 extends F1040Attachment { tag: FormTag = 'f6251' sequenceIndex = 32 isNeeded = (): boolean => { // See https://www.irs.gov/instructions/i6251 // 1. Form 6251, line 7, is greater than line 10. if ((this.l7() ?? 0) > this.l10()) { return true } // TODO: 2. You claim any general business credit, and either line 6 (in Part I) of Form 3800 or line 25 of Form 3800 is more than zero. // TODO: 3. You claim the qualified electric vehicle credit (Form 8834), the personal use part of the alternative fuel vehicle refueling property credit (Form 8911), or the credit for prior year minimum tax (Form 8801). // 4. The total of Form 6251, lines 2c through 3, is negative and line 7 would be greater than line 10 if you didn’t take into account lines 2c through 3. const l2cTo3Total = (this.l2c() ?? 0) + (this.l2d() ?? 0) + (this.l2e() ?? 0) + (this.l2f() ?? 0) + (this.l2g() ?? 0) + (this.l2h() ?? 0) + (this.l2i() ?? 0) + (this.l2j() ?? 0) + (this.l2k() ?? 0) + (this.l2l() ?? 0) + (this.l2m() ?? 0) + (this.l2n() ?? 0) + (this.l2o() ?? 0) + (this.l2p() ?? 0) + (this.l2q() ?? 0) + (this.l2r() ?? 0) + (this.l2s() ?? 0) + (this.l2t() ?? 0) + (this.l3() ?? 0) if (l2cTo3Total < 0 && (this.l7(-l2cTo3Total) ?? 0) > this.l10()) return true return false } l1 = (): number | undefined => { const l15 = this.f1040.l15() if (l15 !== 0) { return l15 } return this.f1040.l11() - this.f1040.l14() } l2a = (): number | undefined => { if (this.f1040.scheduleA.isNeeded()) { return this.f1040.scheduleA.l7() } return this.f1040.l12a() } l2b = (): number | undefined => { return (this.f1040.schedule1.l1() ?? 0) + this.f1040.schedule1.l8z() } // TODO: Investment interest expense (difference between regular tax and AMT) l2c = (): number | undefined => undefined // TODO: Depletion (difference between regular tax and AMT) l2d = (): number | undefined => undefined l2e = (): number | undefined => { return Math.abs(this.f1040.schedule1.l8a() ?? 0) } // TODO: Alternative tax net operating loss deduction l2f = (): number | undefined => undefined // TODO: Interest from specified private activity bonds exempt from the regular tax l2g = (): number | undefined => undefined // TODO: Qualified small business stock, see instructions l2h = (): number | undefined => undefined // Exercise of incentive stock options (excess of AMT income over regular tax income) l2i = (): number | undefined => { let f3921s = this.f1040.info.f3921s if (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS) { f3921s = f3921s.filter((w2) => w2.personRole === PersonRole.PRIMARY) } return f3921s.reduce( (amount, f) => (f.fmv - f.exercisePricePerShare) * f.numShares + amount, 0 ) } // TODO: Estates and trusts (amount from Schedule K-1 (Form 1041), box 12, code A) l2j = (): number | undefined => undefined // TODO: Disposition of property (difference between AMT and regular tax gain or loss) l2k = (): number | undefined => undefined // TODO: Depreciation on assets placed in service after 1986 (difference between regular tax and AMT) l2l = (): number | undefined => undefined // TODO: Passive activities (difference between AMT and regular tax income or loss) l2m = (): number | undefined => undefined // TODO: Loss limitations (difference between AMT and regular tax income or loss) l2n = (): number | undefined => undefined // TODO: Circulation costs (difference between regular tax and AMT) l2o = (): number | undefined => undefined // TODO: Long-term contracts (difference between AMT and regular tax income) l2p = (): number | undefined => undefined // TODO: Mining costs (difference between regular tax and AMT) l2q = (): number | undefined => undefined // TODO: Research and experimental costs (difference between regular tax and AMT) l2r = (): number | undefined => undefined // TODO: Income from certain installment sales before January 1, 1987 l2s = (): number | undefined => undefined // TODO: Intangible drilling costs preference l2t = (): number | undefined => undefined // TODO: Other adjustments, including income-based related adjustments l3 = (): number | undefined => undefined l4 = (additionalAmount = 0): number | undefined => additionalAmount + (this.l1() ?? 0) + (this.l2a() ?? 0) - (this.l2b() ?? 0) + (this.l2c() ?? 0) + (this.l2d() ?? 0) + (this.l2e() ?? 0) - (this.l2f() ?? 0) + (this.l2g() ?? 0) + (this.l2h() ?? 0) + (this.l2i() ?? 0) + (this.l2j() ?? 0) + (this.l2k() ?? 0) + (this.l2l() ?? 0) + (this.l2m() ?? 0) + (this.l2n() ?? 0) + (this.l2o() ?? 0) + (this.l2p() ?? 0) + (this.l2q() ?? 0) + (this.l2r() ?? 0) - (this.l2s() ?? 0) + (this.l2t() ?? 0) + (this.l3() ?? 0) l5 = (additionalAmount = 0): number | undefined => { const l4 = this.l4(additionalAmount) ?? 0 switch (this.f1040.info.taxPayer.filingStatus) { case FilingStatus.S: if (l4 <= 523600) { return 73600 } break case FilingStatus.MFJ: if (l4 <= 1047200) { return 114600 } break case FilingStatus.MFS: if (l4 <= 523600) { return 57300 } else { return 57300 } break } // TODO: Handle "Exemption Worksheet" return undefined } l6 = (additionalAmount = 0): number => Math.max( 0, (this.l4(additionalAmount) ?? 0) - (this.l5(additionalAmount) ?? 0) ) requiresPartIII = (): boolean => { // If you reported capital gain distributions directly on Form 1040 or 1040-SR, line 7; // you reported qualified dividends on Form 1040 or 1040-SR, line 3a; // or you had a gain on both lines 15 and 16 of Schedule D (Form 1040) (as refigured for the AMT, if necessary), // complete Part III on the back and enter the amount from line 40 here. return ( this.f1040.l7() !== undefined || this.f1040.l3a() !== undefined || (this.f1040.scheduleD.l15() > 0 && this.f1040.scheduleD.l16() > 0) ) } l7 = (additionalAmount = 0): number | undefined => { const l6 = this.l6(additionalAmount) if (l6 === 0) { return 0 } // TODO: Handle Form 2555 const f2555 = this.f1040.f2555 if ( f2555 !== undefined && (f2555.l36() > 0 || f2555.l42() > 0 || f2555.l50() > 0) ) { // TODO: Foreign Earned Income Tax Worksheet—Line 7 } // Use line 40 if Part III is required if (this.requiresPartIII()) { return this.part3().l40 } const cap = this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS ? 99950 : 199900 if (l6 <= cap) { return l6 * 0.26 } return l6 * 0.28 } // TODO: Alternative minimum tax foreign tax credit l8 = (): number | undefined => undefined l9 = (additionalAmount = 0): number => { const l6 = this.l6(additionalAmount) if (l6 === 0) { return 0 } return (this.l7(additionalAmount) ?? 0) - (this.l8() ?? 0) } // Add Form 1040 or 1040-SR, line 16 (minus any tax from Form 4972), // and Schedule 2 (Form 1040), line 2. // Subtract from the result Schedule 3 (Form 1040), line 1 // and any negative amount reported on Form 8978, line 14 (treated as a positive number). // If zero or less, enter -0-. // TODO: If you used Schedule J to figure your tax on Form 1040 or 1040-SR, line 16, refigure that tax without using Schedule J before completing this line. See instructions l10 = (): number => { const f1040L16 = this.f1040.l16() ?? 0 const f4972 = this.f1040.f4972?.tax() ?? 0 const sch2L2 = this.f1040.schedule2.l2() ?? 0 const sch3L1 = this.f1040.schedule3.l1() ?? 0 const f8978L14 = Math.abs(0) // TODO: Form 8978 return Math.max(0, f1040L16 - f4972 + sch2L2 - sch3L1 - f8978L14) } l11 = (): number => { const l6 = this.l6() if (l6 === 0) { return 0 } return Math.max(0, this.l9() - this.l10()) } part3 = (): Part3 => { if (!this.requiresPartIII()) { return {} } const fs = this.f1040.info.taxPayer.filingStatus const qdivWorksheet = this.f1040.qualifiedAndCapGainsWorksheet const schDWksht = this.f1040.scheduleD.taxWorksheet const usingTaxWorksheet = schDWksht.isNeeded() const l18Consts: [number, number] = (() => { if (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS) { return [99950, 1999] } return [199900, 3998] })() const l19Value: { [k in FilingStatus]: number } = { [FilingStatus.MFJ]: 80800, [FilingStatus.W]: 80800, [FilingStatus.S]: 40400, [FilingStatus.MFS]: 40400, [FilingStatus.HOH]: 54100 } const l25Value: { [k in FilingStatus]: number } = { [FilingStatus.MFJ]: 501600, [FilingStatus.W]: 501600, [FilingStatus.S]: 445860, [FilingStatus.MFS]: 250800, [FilingStatus.HOH]: 473750 } const l12 = this.l6() // TODO - for F2555, see the instructions for amount const l13: number = (() => { if (usingTaxWorksheet) { return schDWksht.l13() ?? 0 } return qdivWorksheet?.l4() ?? 0 })() const l14 = this.f1040.scheduleD.l19() ?? 0 const l15 = (() => { if (!usingTaxWorksheet) { return l13 } return Math.min(l13 + l14, schDWksht.l10() ?? 0) })() const l16 = Math.min(l12, l15) const l17 = l12 - l16 const l18 = (() => { const [c1, c2] = l18Consts if (l17 <= c1) { return l17 * 0.26 } return l17 * 0.28 - c2 })() const l19 = l19Value[fs] const l20 = (() => { if (usingTaxWorksheet) { return schDWksht.l14() ?? 0 } if (qdivWorksheet !== undefined) { return qdivWorksheet.l5() } return Math.max(0, this.f1040.l15()) })() const l21 = Math.max(0, l19 - l20) const l22 = Math.min(l12, l13) const l23 = Math.min(l21, l22) const l24 = Math.max(0, l22 - l23) const l25 = l25Value[fs] const l26 = l21 // TODO - see instructions for F2555 const l27 = (() => { if (usingTaxWorksheet) { return schDWksht.l21() ?? 0 } if (qdivWorksheet !== undefined) { return qdivWorksheet.l5() } return Math.max(0, this.f1040.l15()) })() const l28 = l26 + l27 const l29 = Math.max(0, l25 - l28) const l30 = Math.min(l24, l29) const l31 = l30 * 0.15 const l32 = l23 + l30 const l33 = l22 - l32 const l34 = l33 * 0.2 const l35 = l17 + l32 + l33 const l36 = l12 - l35 const l37 = l36 * 0.25 const l38 = l18 + l31 + l34 + l37 const l39 = (() => { // numbers referenced here are the same as l18. const [c1, c2] = l18Consts if (l12 <= c1) { return l12 * 0.26 } return l12 * 0.28 - c2 })() const l40 = Math.min(l38, l39) return { l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40 } } fields = (): Field[] => { const p3 = this.part3() return [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, // Part I this.l1(), this.l2a(), this.l2b(), this.l2c(), this.l2d(), this.l2e(), this.l2f(), this.l2g(), this.l2h(), this.l2i(), this.l2j(), this.l2k(), this.l2l(), this.l2m(), this.l2n(), this.l2o(), this.l2p(), this.l2q(), this.l2r(), this.l2s(), this.l2t(), this.l3(), this.l4(), // Part II this.l5(), this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), // Part III p3.l12, p3.l13, p3.l14, p3.l15, p3.l16, p3.l17, p3.l18, p3.l19, p3.l20, p3.l21, p3.l22, p3.l23, p3.l24, p3.l25, p3.l26, p3.l27, p3.l28, p3.l29, p3.l30, p3.l31, p3.l32, p3.l33, p3.l34, p3.l35, p3.l36, p3.l37, p3.l38, p3.l39, p3.l40 ] } } ================================================ FILE: src/forms/Y2021/irsForms/F8582.ts ================================================ import { MatrixRow } from './ScheduleE' import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' /** * Referenced from line 22 of Schedule E * TODO: Not implemented */ export default class F8582 extends F1040Attachment { tag = 'f8562' sequenceIndex = 999 // TODO: 'Deducible rental estate loss after limitation, assuming all allowed' deductibleRealEstateLossAfterLimitation = (): MatrixRow => this.f1040.scheduleE.rentalNet().map((v) => { if (v === undefined || v >= 0) { return undefined } return v }) as MatrixRow fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8814.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Field } from 'ustaxes/core/pdfFiller' export default class F8814 extends F1040Attachment { tag: FormTag = 'f8814' sequenceIndex = 999 // TODO: required from schedule EIC, pub596, worksheet 1 l1b = (): number | undefined => undefined tax = (): number => 0 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8853.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' // Not yet implemented export default class F8853 extends F1040Attachment { tag: FormTag = 'f8853' sequenceIndex = 999 l1 = (): number | undefined => undefined l2 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8863.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' // Not yet implemented export default class F8863 extends F1040Attachment { tag: FormTag = 'f8863' sequenceIndex = 999 l8 = (): number | undefined => undefined l19 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8888.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented yet */ export default class F8888 extends F1040Attachment { tag: FormTag = 'f8888' sequenceIndex = 999 fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8889.ts ================================================ import { Information, Person, HealthSavingsAccount } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import F8853 from './F8853' import { CURRENT_YEAR, healthSavingsAccounts } from '../data/federal' import F1040Attachment from './F1040Attachment' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' type ContributionType = 'self-only' | 'family' type PerMonthContributionType = { amount: number[] type: ContributionType[] } export default class F8889 extends F1040Attachment { tag: FormTag = 'f8889' sequenceIndex = 52 // these should only be the HSAs that belong to this person // the person can be either the primary person or the spouse hsas: HealthSavingsAccount[] f8853?: F8853 person: Person state: Information calculatedCoverageType: 'self-only' | 'family' perMonthContributions: PerMonthContributionType readonly firstDayOfLastMonth: Date isNeeded = (): boolean => { return this.f1040.info.healthSavingsAccounts.some( (h) => h.personRole === this.person.role || h.coverageType === 'family' ) } constructor(f1040: F1040, person: Person) { super(f1040) this.f8853 = f1040.f8853 this.person = person this.state = f1040.info // The relevant HSAs are the ones either for this person or any that // have family coverage. this.hsas = this.state.healthSavingsAccounts .filter((h) => { if (h.personRole == person.role || h.coverageType == 'family') { return true } return false }) .map((h) => { return { ...h, startDate: new Date(h.startDate), endDate: new Date(h.endDate) } }) this.calculatedCoverageType = 'self-only' this.firstDayOfLastMonth = new Date(CURRENT_YEAR, 11, 1) this.perMonthContributions = { amount: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], type: new Array(12) } } calculatePerMonthLimits = (): void => { for ( let index = 0; index < this.perMonthContributions.amount.length; index++ ) { // for each month check each HSA to see if we are covered. this.hsas.forEach((h) => { const firstDayOfThisMonth = new Date(CURRENT_YEAR, index, 1) if ( h.startDate <= firstDayOfThisMonth && h.endDate >= firstDayOfThisMonth ) { // the coverage limit for that month is based on the type of coverage of the // HSA. If you have both types of HSA coverage for that month, then the family // coverage limit wins out. Since family coverage limit is higher we can just // take the max of the coverage limit for this month. if ( this.perMonthContributions.amount[index] < healthSavingsAccounts.contributionLimit[h.coverageType] ) { this.perMonthContributions.amount[index] = healthSavingsAccounts.contributionLimit[h.coverageType] this.perMonthContributions.type[index] = h.coverageType } } }) } // The calculated coverage type is whichever one was in effect for longer let familyMonthCount = 0 let singleMonthCount = 0 this.perMonthContributions.amount.forEach((m) => { if (m == healthSavingsAccounts.contributionLimit.family) { familyMonthCount += 1 } else if (m == healthSavingsAccounts.contributionLimit['self-only']) { singleMonthCount += 1 } }) if (familyMonthCount >= singleMonthCount) { this.calculatedCoverageType = 'family' } else { this.calculatedCoverageType = 'self-only' } } /* If you are an eligible individual on the first day of the last month of your tax year (December 1 for most taxpayers), you are considered to be an eligible individual for the entire year. */ lastMonthRule = (): boolean => { return this.hsas.some((hsa) => hsa.endDate >= this.firstDayOfLastMonth) } /*If, on the first day of the last month of your tax year (December 1 for most taxpayers), you had family coverage, check the "family" box. */ lastMonthCoverage = (): string | undefined => { let coverage = undefined for (const hsa of this.hsas) { if (hsa.endDate >= this.firstDayOfLastMonth) { if (hsa.coverageType == 'family') { coverage = 'family' break } coverage = 'self-only' } } return coverage } fullYearHsa = (): boolean => { return this.hsas.some( (hsa) => hsa.startDate <= new Date(CURRENT_YEAR, 0, 1) && hsa.endDate >= this.firstDayOfLastMonth ) } contributionLimit = (): number => { /*If you were under age 55 at the end of 2020 and, on the first day of every month during 2020, you were, or were considered, an eligible individual with the same coverage, enter $3,550 ($7,100 for family coverage). All others, see the instructions for the amount to enter. */ /*If the last-month rule (see Last-month rule, earlier) applies, you are considered an eligible individual for the entire year. You are treated as having the same HDHP coverage for the entire year as you had on the first day of the last month of your tax year. */ if (this.lastMonthRule()) { // If, on the first day of the last month of your tax year (December 1 for most taxpayers), // you had family coverage, check the "family" box. const lastMonthCoverage = this.lastMonthCoverage() if (lastMonthCoverage !== undefined) { if (lastMonthCoverage === 'family') { this.calculatedCoverageType = 'family' return healthSavingsAccounts.contributionLimit.family } else if (lastMonthCoverage === 'self-only') { this.calculatedCoverageType = 'self-only' return healthSavingsAccounts.contributionLimit['self-only'] } } } /* If you don't have coverage in the last month, then you need to figure out your contribution limit. If you don't have coverage for that month then your contribution limit is 0. So let's initialize our per-month contribution limit based on that. */ this.calculatePerMonthLimits() return Math.round( this.perMonthContributions.amount.reduce((a, b) => a + b) / 12 ) } splitFamilyContributionLimit = (): number | undefined => { /* if you and your spouse each have separate HSAs and had family coverage under an HDHP at any time during 2020*/ /* If you are treated as having family coverage for each month, divide the amount on line 5 equally between you and your spouse, unless you both agree on a different allocation (such as allocating nothing to one spouse). Enter your allocable share on line 6.*/ /* Example. In 2020, you are an eligible individual and have self-only HDHP coverage. In March you marry and as of April 1 you have family HDHP coverage. Neither you nor your spouse qualify for the additional contribution amount. Your spouse has a separate HSA and is an eligible individual from April 1 to December 31, 2020. Because you and your spouse are considered to have family coverage on December 1, your contribution limit is $7,100 (the family coverage maximum). You and your spouse can divide this amount in any allocation to which you agree (such as allocating nothing to one spouse).*/ if (!this.hsas.some((h) => h.coverageType === 'family')) { return this.l5() } if (this.lastMonthCoverage() === 'family') { // TODO: This hard codes the allocation at 50% for each spouse but the // rules say any contribution allowcation is allowed return Math.round(this.l5() / 2) } else { // get the number of months of family coverage const familyMonths: number = this.perMonthContributions.type.filter( (t) => t === 'family' ).length // TODO: This hard codes the allocation at 50% for each spouse but the // rules say any contribution allowcation is allowed const familyContribution: number = (familyMonths * healthSavingsAccounts.contributionLimit['family']) / 12 / 2 // Add this to the contributions of the self-only portion of the year const selfMonths: number = 12 - familyMonths const selfContribution: number = (selfMonths * healthSavingsAccounts.contributionLimit['self-only']) / 12 return familyContribution + selfContribution } } /*Include on line 2 only those amounts you, or others on your behalf, contributed to your HSA in 2020. Also, include those contributions made from January 1, 2021, through April 15, 2021, that were for 2020. Do not include employer contributions (see line 9) or amounts rolled over from another HSA or Archer MSA. See Rollovers, earlier. Also, do not include any qualified HSA funding distributions (see line 10). Contributions to an employee's account through a cafeteria plan are treated as employer contributions and are not included on line 2. */ l2 = (): number => this.hsas.reduce((total, hsa) => hsa.contributions + total, 0) l3 = (): number => this.contributionLimit() l4 = (): number => sumFields([this.f8853?.l1(), this.f8853?.l2()]) l5 = (): number => this.l3() - this.l4() l6 = (): number | undefined => this.splitFamilyContributionLimit() // TODO: Additional contirbution amount. Need to know the age of the user l7 = (): number | undefined => undefined l8 = (): number => sumFields([this.l6(), this.l7()]) // Employer contributions are listed in W2 box 12 with code W l9 = (): number => this.state.w2s .filter((w2) => w2.personRole == this.person.role) .reduce((res, w2) => res + (w2.box12?.W ?? 0), 0) l10 = (): number | undefined => undefined l11 = (): number => sumFields([this.l9(), this.l10()]) l12 = (): number => { const tmp = this.l8() - this.l11() return tmp < 0 ? 0 : tmp } l13 = (): number | undefined => this.l2() < this.l12() ? this.l2() : this.l12() l14a = (): number => this.hsas.reduce((total, hsa) => hsa.totalDistributions + total, 0) l14b = (): number | undefined => undefined l14c = (): number => Math.max(0, this.l14a() - (this.l14b() ?? 0)) l15 = (): number => this.hsas.reduce((total, hsa) => hsa.qualifiedDistributions + total, 0) l16 = (): number => Math.max(0, this.l14c() - this.l15()) l17a = (): boolean => false // TODO: add in logic for when line 17a is true l17b = (): number | undefined => Math.round(this.l16() * 0.2) l18 = (): number | undefined => undefined l19 = (): number | undefined => undefined l20 = (): number => sumFields([this.l18(), this.l19()]) l21 = (): number => Math.round(this.l20() * 0.1) fields = (): Field[] => [ `${this.person.firstName} ${this.person.lastName}`, this.person.ssid, this.calculatedCoverageType === 'self-only', // line 1: self-only check box this.calculatedCoverageType === 'family', // line 1: family checkbox this.l2(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14a(), this.l14b(), this.l14c(), this.l15(), this.l16(), this.l17a(), this.l17b(), this.l18(), this.l19(), this.l20(), this.l21() ] } ================================================ FILE: src/forms/Y2021/irsForms/F8910.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented */ export default class F8910 extends F1040Attachment { sequenceIndex = 999 tag: FormTag = 'f8910' l15 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8919.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' // TODO: Not implemented yet export default class F8919 extends F1040Attachment { tag = 'f8919' sequenceIndex = 999 l6 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8936.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' export default class F8936 extends F1040Attachment { tag: FormTag = 'f8936' sequenceIndex = 999 l15 = (): number | undefined => undefined l23 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8949.ts ================================================ import { FormTag } from 'ustaxes/core/irsForms/Form' import { Asset, isSold, SoldAsset } from 'ustaxes/core/data' import F1040Attachment from './F1040Attachment' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' type EmptyLine = [ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined ] type Line = | [string, string, string, number, number, undefined, undefined, number] | EmptyLine const emptyLine: EmptyLine = [ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined ] const showDate = (date: Date): string => `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` const toLine = (position: SoldAsset): Line => [ position.name, showDate(position.openDate), showDate(position.closeDate), position.closePrice * position.quantity, position.openPrice * position.quantity, undefined, undefined, (position.closePrice - position.openPrice) * position.quantity ] const NUM_SHORT_LINES = 14 const NUM_LONG_LINES = 14 const padUntil = (xs: A[], v: B, n: number): (A | B)[] => { if (xs.length >= n) { return xs } return [...xs, ...Array.from(Array(n - xs.length)).map(() => v)] } export default class F8949 extends F1040Attachment { tag: FormTag = 'f8949' sequenceIndex = 12.1 index = 0 constructor(f1040: F1040, index = 0) { super(f1040) this.index = index } isNeeded = (): boolean => this.thisYearSales().length > 0 copies = (): F8949[] => { if (this.index === 0) { const extraCopiesNeeded = Math.round( Math.max( this.thisYearShortTermSales().length / NUM_SHORT_LINES, this.thisYearLongTermSales().length / NUM_LONG_LINES ) ) return Array.from(Array(extraCopiesNeeded)).map( (_, i) => new F8949(this.f1040, i + 1) ) } return [] } // Assuming we're only handling non-reported transactions part1BoxA = (): boolean => false part1BoxB = (): boolean => false part1BoxC = (): boolean => true part2BoxD = (): boolean => false part2BoxE = (): boolean => false part2BoxF = (): boolean => true thisYearSales = (): SoldAsset[] => this.f1040.assets.filter( (p) => isSold(p) && p.closeDate.getFullYear() === 2021 ) as SoldAsset[] thisYearLongTermSales = (): SoldAsset[] => this.thisYearSales().filter((p) => this.isLongTerm(p)) thisYearShortTermSales = (): SoldAsset[] => this.thisYearSales().filter((p) => !this.isLongTerm(p)) // in milliseconds oneDay = 1000 * 60 * 60 * 24 isLongTerm = (p: Asset): boolean => { if (p.closeDate === undefined || p.closePrice === undefined) return false const milliInterval = p.closeDate.getTime() - p.openDate.getTime() return milliInterval / this.oneDay > 366 } /** * Take the short term transactions that fit on this copy of the 8949 */ shortTermSales = (): SoldAsset[] => this.thisYearShortTermSales().slice( this.index * NUM_SHORT_LINES, (this.index + 1) * NUM_SHORT_LINES ) /** * Take the long term transactions that fit on this copy of the 8949 */ longTermSales = (): SoldAsset[] => this.thisYearLongTermSales().slice( this.index * NUM_LONG_LINES, (this.index + 1) * NUM_LONG_LINES ) shortTermLines = (): Line[] => padUntil( this.shortTermSales().map((p) => toLine(p)), emptyLine, NUM_SHORT_LINES ) longTermLines = (): Line[] => padUntil( this.longTermSales().map((p) => toLine(p)), emptyLine, NUM_LONG_LINES ) shortTermTotalProceeds = (): number => this.shortTermSales().reduce( (acc, p) => acc + p.closePrice * p.quantity - (p.closeFee ?? 0), 0 ) shortTermTotalCost = (): number => this.shortTermSales().reduce( (acc, p) => acc + p.openPrice * p.quantity + p.openFee, 0 ) shortTermTotalGain = (): number => this.shortTermTotalProceeds() - this.shortTermTotalCost() // TODO: handle adjustments column. shortTermTotalAdjustments = (): number | undefined => undefined longTermTotalProceeds = (): number => this.longTermSales().reduce( (acc, p) => acc + p.closePrice * p.quantity - (p.closeFee ?? 0), 0 ) longTermTotalCost = (): number => this.longTermSales().reduce( (acc, p) => acc + p.openPrice * p.quantity + p.openFee, 0 ) longTermTotalGain = (): number => this.longTermTotalProceeds() - this.longTermTotalCost() // TODO: handle adjustments column. longTermTotalAdjustments = (): number | undefined => undefined fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.part1BoxA(), this.part1BoxB(), this.part1BoxC(), ...this.shortTermLines().flat(), this.shortTermTotalProceeds(), this.shortTermTotalCost(), undefined, // greyed out field this.shortTermTotalAdjustments(), this.shortTermTotalGain(), this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.part2BoxD(), this.part2BoxE(), this.part2BoxF(), ...this.longTermLines().flat(), this.longTermTotalProceeds(), this.longTermTotalCost(), undefined, // greyed out field this.longTermTotalAdjustments(), this.longTermTotalGain() ] } ================================================ FILE: src/forms/Y2021/irsForms/F8959.ts ================================================ import F1040Attachment from './F1040Attachment' import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import { fica } from '../data/federal' import { Field } from 'ustaxes/core/pdfFiller' export default class F8959 extends F1040Attachment { tag: FormTag = 'f8959' sequenceIndex = 71 isNeeded = (): boolean => { const filingStatus = this.f1040.info.taxPayer.filingStatus const totalW2Income = this.f1040.info.w2s .map((w2) => w2.medicareIncome) .reduce((l, r) => l + r, 0) return ( fica.additionalMedicareTaxThreshold(filingStatus) < totalW2Income + (this.f1040.scheduleSE.l6() ?? 0) ) } thresholdFromFilingStatus = (): number => fica.additionalMedicareTaxThreshold(this.f1040.info.taxPayer.filingStatus) computeAdditionalMedicareTax = (compensation: number): number => fica.additionalMedicareTaxRate * compensation // Part I: Additional Medicare Tax on Medicare Wages l1 = (): number => this.f1040.medicareWages() l2 = (): number | undefined => this.f1040.f4137?.l6() l3 = (): number | undefined => this.f1040.f8919?.l6() l4 = (): number => sumFields([this.l1(), this.l2(), this.l3()]) l5 = (): number => this.thresholdFromFilingStatus() l6 = (): number => Math.max(0, this.l4() - this.l5()) l7 = (): number | undefined => this.computeAdditionalMedicareTax(this.l6()) // Part II: Additional Medicare Tax on Self-Employment Income l8 = (): number | undefined => this.f1040.scheduleSE.l6() l9 = (): number => this.thresholdFromFilingStatus() l10 = (): number => this.l4() l11 = (): number => Math.max(0, this.l9() - this.l10()) l12 = (): number => Math.max(0, (this.l8() ?? 0) - this.l11()) l13 = (): number | undefined => this.computeAdditionalMedicareTax(this.l12()) // Part III: Additional Medicare Tax on Railroad Retirement Tax Act // (RRTA) Compensation l14 = (): number | undefined => undefined // TODO: RRTA in W2 l15 = (): number => this.thresholdFromFilingStatus() l16 = (): number => Math.max(0, (this.l14() ?? 0) - this.l15()) l17 = (): number => this.computeAdditionalMedicareTax(this.l16()) // Part IV: Total Medicare Tax l18 = (): number => sumFields([this.l7(), this.l13(), this.l17()]) // Part V: Withholding Reconciliation l19 = (): number => this.f1040 .validW2s() .map((w2) => w2.medicareWithholding) .reduce((l, r) => l + r, 0) l20 = (): number => this.l1() l21 = (): number => fica.regularMedicareTaxRate * this.l20() l22 = (): number => Math.max(0, this.l19() - this.l21()) l23 = (): number | undefined => 0 // TODO: RRTA l24 = (): number => sumFields([this.l22(), this.l23()]) toSchedule2l11 = (): number => this.l18() to1040l25c = (): number => this.l24() fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18(), this.l19(), this.l20(), this.l21(), this.l22(), this.l23(), this.l24() ] } ================================================ FILE: src/forms/Y2021/irsForms/F8960.ts ================================================ import F1040Attachment from './F1040Attachment' import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import { netInvestmentIncomeTax } from '../data/federal' import { Field } from 'ustaxes/core/pdfFiller' export default class F8960 extends F1040Attachment { tag: FormTag = 'f8960' sequenceIndex = 72 isNeeded = (): boolean => { const filingStatus = this.f1040.info.taxPayer.filingStatus const totalW2Income = this.f1040.info.w2s .map((w2) => w2.income) .reduce((l, r) => l + r, 0) return netInvestmentIncomeTax.taxThreshold(filingStatus) < totalW2Income } //Taxable Interest l1 = (): number | undefined => this.f1040.l2b() // Ordinary Dividends l2 = (): number | undefined => this.f1040.l3b() /* Enter the gross income from all annuities, except annuities paid from the following. - Section 401—Qualified pension, profit-sharing, and stock bonus plans. - Section 403(a)—Qualified annuity plans purchased by an employer for an employee. - Section 403(b)—Annuities purchased by public schools or section 501(c)(3) tax-exempt organizations. - Section 408—Individual Retirement Accounts (IRAs) or Annuities. - Section 408A—Roth IRAs. - Section 457(b)—Deferred compensation plans of a state and local government and tax-exempt organization. - Amounts paid in consideration for services (for example, distributions from a foreign retirement plan that are paid in the form of an annuity and include investment income that was earned by the retirement plan). How your annuities are reported to you. Net investment income from annuities is reported to a recipient on Form 1099-R, Distributions From Pensions, Annuities, Retirement or Profit-Sharing Plans, IRAs, Insurance Contracts, etc. However, the amount reported on Form 1099-R may also include annuity payments from retirement plans that are exempt from NIIT. Amounts subject to NIIT should be identified with code "D" in box 7. If code "D" is shown in box 7 of Form 1099-R, include on Form 8960, line 3, the taxable amount reported on Form 1099-R, box 2a. However, if the payor checks box 2b indicating the taxable amount can’t be determined, you may need to calculate the taxable portion of your distribution. See Pub. 939, General Rule for Pensions and Annuities, and Pub. 575, Pension and Annuity Income, for details. */ l3 = (): number | undefined => undefined /* Rental Real Estate, Royalties, Partnerships, S Corporations, and Trusts Enter the following amount from your properly completed return. - Schedule 1 (Form 1040), line 5. - Form 1041, line 5. - Form 1041-QFT, the portion of line 4 that’s income and loss that properly would be reported by a trust filing Form 1041 on Form 1041, line 5. - Form 1040-NR, the amount properly reported on the attachment to your Form 1040-NR representing the amount that you would properly include on Schedule 1 (Form 1040), line 5, if you were filing Form 1040 or 1040‐SR and including income and loss only for your period of U.S. residency. */ l4a = (): number | undefined => this.f1040.schedule1.l5() l4b = (): number | undefined => undefined l4c = (): number => sumFields([this.l4a(), this.l4b()]) // Line 5a-5d: Gains and Losses on the Disassets of Property l5a = (): number => sumFields([this.f1040.l7(), this.f1040.schedule1.l4()]) // TODO: implement line 5b and 5c from worksheet. l5b = (): number | undefined => undefined l5c = (): number | undefined => undefined l5d = (): number => sumFields([this.l5a(), this.l5b(), this.l5c()]) // TODO: Line 6: Adjustments to Investment Income for Certain CFCs and PFICs l6 = (): number | undefined => undefined // TODO: Line 7: Other Modifications to Investment Income l7 = (): number | undefined => undefined l8 = (): number => sumFields([ this.l1(), this.l2(), this.l3(), this.l4c(), this.l5d(), this.l6(), this.l7() ]) // Todo: Implement Schedule A l9a = (): number | undefined => this.f1040.scheduleA.isNeeded() ? this.f1040.scheduleA.l9() : undefined // Line 9b Reasonable method: Total state tax from W2 * Form 8960 Line 8 / 1040 Line 11, Adjusted gross income l9b = (): number | undefined => { const totalStateWithholding = this.f1040 .validW2s() .map((w2) => w2.stateWithholding ?? 0) .reduce((l, r) => l + r, 0) const f1040L11 = this.f1040.l11() if (f1040L11 === 0) { return 0 } return (totalStateWithholding * this.l8()) / f1040L11 } l9c = (): number | undefined => undefined l9d = (): number => sumFields([this.l9a(), this.l9b(), this.l9c()]) l10 = (): number | undefined => undefined l11 = (): number => sumFields([this.l9d(), this.l10()]) l12 = (): number => Math.max(0, this.l8() - this.l11()) // TODO: This should also take into account values on form 2555 and adjustments for Certain CFCs and Certain PFICs l13 = (): number => this.f1040.l11() l14 = (): number => netInvestmentIncomeTax.taxThreshold(this.f1040.info.taxPayer.filingStatus) l15 = (): number => Math.max(0, this.l13() - this.l14()) l16 = (): number => (this.l12() < this.l15() ? this.l12() : this.l15()) l17 = (): number => Math.round(this.l16() * netInvestmentIncomeTax.taxRate) // TODO: Estates and Trusts // leave all of the following undefined until we support estates and trusts // these lines are to be left blank for individuals l18a = (): number | undefined => undefined l18b = (): number | undefined => undefined l18c = (): number | undefined => undefined l19a = (): number | undefined => undefined l19b = (): number | undefined => undefined l19c = (): number | undefined => undefined l20 = (): number | undefined => undefined // this.l19c() < this.l18c()? this.l19c() : this.l18c() l21 = (): number | undefined => undefined // Math.round(this.l20() * netInvestmentIncomeTax.taxRate) toSchedule2l12 = (): number | undefined => this.l17() fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, undefined, // Section 6013(g) election checkbox undefined, // Section 6013(h) election checkbox undefined, // Regulations section 1.1411-10(g) election checkbox this.l1(), this.l2(), this.l3(), this.l4a(), this.l4b(), this.l4c(), this.l5a(), this.l5b(), this.l5c(), this.l5d(), this.l6(), this.l7(), this.l8(), this.l9a(), this.l9b(), this.l9c(), this.l9d(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18a(), this.l18b(), this.l18c(), this.l19a(), this.l19b(), this.l19c(), this.l20(), this.l21() ] } ================================================ FILE: src/forms/Y2021/irsForms/F8962.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * TODO: Not yet implemented * Net premium tax credit */ export default class F8962 extends F1040Attachment { tag: FormTag = 'f8962' sequenceIndex = 999 credit = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/F8995.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { FilingStatus } from 'ustaxes/core/data' import { Field } from 'ustaxes/core/pdfFiller' export function getF8995PhaseOutIncome(filingStatus: FilingStatus): number { let formAMinAmount = 164900 if (filingStatus === FilingStatus.MFS) { formAMinAmount = 164925 } else if (filingStatus === FilingStatus.MFJ) { formAMinAmount = 329800 } return formAMinAmount } function ifNumber( num: number | undefined, f: (num: number) => number | undefined ) { return num !== undefined ? f(num) : undefined } export default class F8995 extends F1040Attachment { tag: FormTag = 'f8995' sequenceIndex = 999 applicableK1s = () => this.f1040.info.scheduleK1Form1065s.filter((k1) => k1.section199AQBI > 0) netCapitalGains = (): number => { let rtn = this.f1040.l3a() ?? 0 if (this.f1040.scheduleD.isNeeded()) { const l15 = this.f1040.scheduleD.l15() const l16 = this.f1040.scheduleD.l16() const min = Math.min(l15, l16) if (min > 0) rtn += min } else { rtn += this.f1040.l7() ?? 0 } return rtn } l2 = (): number | undefined => this.applicableK1s() .map((k1) => k1.section199AQBI) .reduce((c, a) => c + a, 0) l3 = (): number | undefined => undefined l4 = (): number | undefined => ifNumber(this.l2(), (num) => num + (this.l3() ?? 0)) l5 = (): number | undefined => ifNumber(this.l4(), (num) => num * 0.2) // TODO: REIT l6 = (): number => 0 l7 = (): number => 0 l8 = (): number | undefined => ifNumber(this.l6(), (num) => num + this.l7()) l9 = (): number | undefined => ifNumber(this.l8(), (num) => num * 0.2) l10 = (): number | undefined => ifNumber(this.l5(), (num) => num + (this.l9() ?? 0)) l11 = (): number => this.f1040.l11() - this.f1040.l12c() l12 = (): number => this.netCapitalGains() l13 = (): number => Math.max(0, this.l11() - this.l12()) l14 = (): number => this.l13() * 0.2 l15 = (): number => Math.min(this.l10() ?? 0, this.l14()) l16 = (): number => Math.min(0, (this.l2() ?? 0) + (this.l3() ?? 0)) l17 = (): number => Math.min(0, this.l6() + this.l7()) deductions = (): number => this.l15() fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.applicableK1s()[0]?.partnershipName, this.applicableK1s()[0]?.partnershipEin, this.applicableK1s()[0]?.section199AQBI, this.applicableK1s()[1]?.partnershipName, this.applicableK1s()[1]?.partnershipEin, this.applicableK1s()[1]?.section199AQBI, this.applicableK1s()[2]?.partnershipName, this.applicableK1s()[2]?.partnershipEin, this.applicableK1s()[2]?.section199AQBI, this.applicableK1s()[3]?.partnershipName, this.applicableK1s()[3]?.partnershipEin, this.applicableK1s()[3]?.section199AQBI, this.applicableK1s()[4]?.partnershipName, this.applicableK1s()[4]?.partnershipEin, this.applicableK1s()[4]?.section199AQBI, this.l2(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17() ] } ================================================ FILE: src/forms/Y2021/irsForms/F8995A.ts ================================================ import F8995, { getF8995PhaseOutIncome } from './F8995' import { FormTag } from 'ustaxes/core/irsForms/Form' import { FilingStatus } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import { Field } from 'ustaxes/core/pdfFiller' function ifNumber( num: number | undefined, f: (num: number) => number | undefined ) { return num !== undefined ? f(num) : undefined } export default class F8995A extends F8995 { tag: FormTag = 'f8995a' l2a = (): number | undefined => this.applicableK1s()[0]?.section199AQBI l2b = (): number | undefined => this.applicableK1s()[1]?.section199AQBI l2c = (): number | undefined => this.applicableK1s()[2]?.section199AQBI l3a = (): number | undefined => ifNumber(this.l2a(), (num) => num * 0.2) l3b = (): number | undefined => ifNumber(this.l2b(), (num) => num * 0.2) l3c = (): number | undefined => ifNumber(this.l2c(), (num) => num * 0.2) // TODO: Allow W2 income with QBI l4a = (): number | undefined => ifNumber(this.l2a(), () => 0) l4b = (): number | undefined => ifNumber(this.l2a(), () => 0) l4c = (): number | undefined => ifNumber(this.l2a(), () => 0) l5a = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.5) l5b = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.5) l5c = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.5) l6a = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.25) l6b = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.25) l6c = (): number | undefined => ifNumber(this.l4a(), (num) => num * 0.25) // TODO: Allow UBIA l7a = (): number | undefined => ifNumber(this.l2a(), () => 0) l7b = (): number | undefined => ifNumber(this.l2a(), () => 0) l7c = (): number | undefined => ifNumber(this.l2a(), () => 0) l8a = (): number | undefined => ifNumber(this.l7a(), (num) => num * 0.25) l8b = (): number | undefined => ifNumber(this.l7a(), (num) => num * 0.25) l8c = (): number | undefined => ifNumber(this.l7a(), (num) => num * 0.25) l9a = (): number | undefined => ifNumber(this.l6a(), (num) => num + (this.l8a() ?? 0)) l9b = (): number | undefined => ifNumber(this.l6a(), (num) => num + (this.l8b() ?? 0)) l9c = (): number | undefined => ifNumber(this.l6a(), (num) => num + (this.l8c() ?? 0)) l10a = (): number | undefined => ifNumber(this.l5a(), (num) => Math.max(num, this.l9a() ?? 0)) l10b = (): number | undefined => ifNumber(this.l5b(), (num) => Math.max(num, this.l9b() ?? 0)) l10c = (): number | undefined => ifNumber(this.l5c(), (num) => Math.max(num, this.l9c() ?? 0)) l11a = (): number | undefined => ifNumber(this.l3a(), (num) => Math.min(num, this.l10a() ?? 0)) l11b = (): number | undefined => ifNumber(this.l3b(), (num) => Math.min(num, this.l10b() ?? 0)) l11c = (): number | undefined => ifNumber(this.l3c(), (num) => Math.min(num, this.l10c() ?? 0)) l12a = (): number | undefined => ifNumber(this.l26a(), (num) => num) l12b = (): number | undefined => ifNumber(this.l26b(), (num) => num) l12c = (): number | undefined => ifNumber(this.l26c(), (num) => num) l13a = (): number | undefined => ifNumber(this.l12a(), (num) => Math.max(num, this.l11a() ?? 0)) l13b = (): number | undefined => ifNumber(this.l12b(), (num) => Math.max(num, this.l11b() ?? 0)) l13c = (): number | undefined => ifNumber(this.l12c(), (num) => Math.max(num, this.l11c() ?? 0)) // TODO: Patron reduction l14a = (): number | undefined => ifNumber(this.l2a(), () => 0) l14b = (): number | undefined => ifNumber(this.l2a(), () => 0) l14c = (): number | undefined => ifNumber(this.l2a(), () => 0) l15a = (): number | undefined => ifNumber(this.l13a(), (num) => num - (this.l14a() ?? 0)) l15b = (): number | undefined => ifNumber(this.l13b(), (num) => num - (this.l14b() ?? 0)) l15c = (): number | undefined => ifNumber(this.l13c(), (num) => num - (this.l14c() ?? 0)) l16 = (): number => sumFields([this.l15a(), this.l15b(), this.l15c()]) l17a = (): number | undefined => ifNumber(this.l3a(), (num) => num) l17b = (): number | undefined => ifNumber(this.l3b(), (num) => num) l17c = (): number | undefined => ifNumber(this.l3c(), (num) => num) l18a = (): number | undefined => ifNumber(this.l10a(), (num) => num) l18b = (): number | undefined => ifNumber(this.l10b(), (num) => num) l18c = (): number | undefined => ifNumber(this.l10c(), (num) => num) l19a = (): number | undefined => ifNumber(this.l17a(), (num) => num - (this.l18a() ?? 0)) l19b = (): number | undefined => ifNumber(this.l17b(), (num) => num - (this.l18b() ?? 0)) l19c = (): number | undefined => ifNumber(this.l17c(), (num) => num - (this.l18c() ?? 0)) l20 = (): number => this.f1040.l11() - this.f1040.l12c() l21 = (): number => getF8995PhaseOutIncome(this.f1040.info.taxPayer.filingStatus) l22 = (): number => this.l20() - this.l21() l23 = (): number => this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ ? 100000 : 50000 l24 = (): number => Math.round((this.l22() / this.l23()) * 10000) / 10000 // We want xx.xx% l25a = (): number | undefined => ifNumber(this.l19a(), (num) => num * this.l24()) l25b = (): number | undefined => ifNumber(this.l19b(), (num) => num * this.l24()) l25c = (): number | undefined => ifNumber(this.l19c(), (num) => num * this.l24()) l26a = (): number | undefined => ifNumber(this.l17a(), (num) => num - (this.l25a() ?? 0)) l26b = (): number | undefined => ifNumber(this.l17b(), (num) => num - (this.l25b() ?? 0)) l26c = (): number | undefined => ifNumber(this.l17c(), (num) => num - (this.l25c() ?? 0)) l27 = (): number => this.l16() // TODO: REIT l28 = (): number => 0 l29 = (): number => 0 l30 = (): number => Math.max(0, this.l28() + this.l29()) l31 = (): number => this.l30() * 0.2 l32 = (): number => this.l27() + this.l31() l33 = (): number => this.l20() l34 = (): number => this.netCapitalGains() l35 = (): number => this.l33() - this.l34() l36 = (): number => this.l35() * 0.2 l37 = (): number => Math.min(this.l32(), this.l36()) // TODO: DPAD l38 = (): number => 0 l39 = (): number => this.l37() - this.l38() deductions = (): number => this.l39() l40 = (): number => Math.min(0, this.l28() + this.l29()) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.applicableK1s()[0]?.partnershipName, false, // 1Ab false, // 1Ac this.applicableK1s()[0]?.partnershipEin, false, // 1Ae this.applicableK1s()[1]?.partnershipName, false, // 1Bb false, // 1Bc this.applicableK1s()[1]?.partnershipEin, false, // 1Be this.applicableK1s()[2]?.partnershipName, false, // 1Cb false, // 1Cc this.applicableK1s()[2]?.partnershipEin, false, // 1Ce this.l2a(), this.l2b(), this.l2c(), this.l3a(), this.l3b(), this.l3c(), this.l4a(), this.l4b(), this.l4c(), this.l5a(), this.l5b(), this.l5c(), this.l6a(), this.l6b(), this.l6c(), this.l7a(), this.l7b(), this.l7c(), this.l8a(), this.l8b(), this.l8c(), this.l9a(), this.l9b(), this.l9c(), this.l10a(), this.l10b(), this.l10c(), this.l11a(), this.l11b(), this.l11c(), this.l12a(), this.l12b(), this.l12c(), this.l13a(), this.l13b(), this.l13c(), this.l14a(), this.l14b(), this.l14c(), this.l15a(), this.l15b(), this.l15c(), this.l16(), undefined, // Gray undefined, // Gray this.l17a(), this.l17b(), this.l17c(), this.l18a(), this.l18b(), this.l18c(), this.l19a(), this.l19b(), this.l19c(), this.l20(), undefined, // Gray undefined, // Gray undefined, // Gray this.l21(), undefined, // Gray undefined, // Gray undefined, // Gray this.l22(), undefined, // Gray undefined, // Gray undefined, // Gray this.l23(), undefined, // Gray undefined, // Gray undefined, // Gray (this.l24() * 100).toFixed(2) + '%', // TODO: Percent sign is duplicated, but it prevents Fill.ts from rounding this undefined, // Gray undefined, // Gray undefined, // Gray this.l25a(), this.l25b(), this.l25c(), this.l26a(), this.l26b(), this.l26c(), this.l27(), this.l28(), this.l29(), this.l30(), this.l31(), this.l32(), this.l33(), this.l34(), this.l35(), this.l36(), this.l37(), this.l38(), this.l39(), this.l40() ] } ================================================ FILE: src/forms/Y2021/irsForms/Main.ts ================================================ import { Asset, Information } from 'ustaxes/core/data' import { Either, run } from 'ustaxes/core/util' import F1040 from './F1040' import Form from 'ustaxes/core/irsForms/Form' import { F1040Error } from 'ustaxes/forms/errors' import { validate } from 'ustaxes/forms/F1040Base' export const create1040 = ( info: Information, assets: Asset[] ): Either => run(validate(info)) .map<[F1040, Form[]]>((info) => { const f1040 = new F1040(info, assets) return [f1040, f1040.schedules()] }) .value() ================================================ FILE: src/forms/Y2021/irsForms/Schedule1.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' export default class Schedule1 extends F1040Attachment { tag: FormTag = 'f1040s1' sequenceIndex = 1 otherIncomeStrings: Set constructor(f1040: F1040) { super(f1040) this.otherIncomeStrings = new Set() } isNeeded = (): boolean => this.f1040.scheduleE.isNeeded() || (this.f1040.studentLoanInterestWorksheet !== undefined && this.f1040.studentLoanInterestWorksheet.notMFS() && this.f1040.studentLoanInterestWorksheet.isNotDependent()) || this.f1040.f8889.isNeeded() || (this.f1040.f8889Spouse?.isNeeded() ?? false) l1 = (): number | undefined => undefined l2a = (): number | undefined => undefined l2b = (): number | undefined => undefined l3 = (): number | undefined => undefined l4 = (): number | undefined => undefined l5 = (): number | undefined => this.f1040.scheduleE.l41() l6 = (): number | undefined => undefined l7 = (): number | undefined => undefined l8a = (): number | undefined => undefined l8b = (): number | undefined => undefined l8c = (): number | undefined => undefined l8d = (): number | undefined => undefined l8e = (): number | undefined => sumFields([this.f1040.f8889.l16(), this.f1040.f8889Spouse?.l16()]) l8f = (): number | undefined => undefined l8g = (): number | undefined => undefined l8h = (): number | undefined => undefined l8i = (): number | undefined => undefined l8j = (): number | undefined => undefined l8k = (): number | undefined => undefined l8l = (): number | undefined => undefined l8m = (): number | undefined => undefined l8n = (): number | undefined => undefined l8o = (): number | undefined => undefined l8p = (): number | undefined => undefined l8q = (): number | undefined => undefined l8z = (): number => { if ( (this.f1040.f8889.isNeeded() && this.f1040.f8889.l20() > 0) || ((this.f1040.f8889Spouse?.isNeeded() ?? false) && this.f1040.f8889Spouse?.l20() !== undefined && this.f1040.f8889Spouse.l20() > 0) ) { this.otherIncomeStrings.add('HSA') } return sumFields([this.f1040.f8889.l20(), this.f1040.f8889Spouse?.l20()]) } l9 = (): number => sumFields([ this.l8a(), this.l8b(), this.l8c(), this.l8d(), this.l8e(), this.l8f(), this.l8g(), this.l8h(), this.l8i(), this.l8j(), this.l8k(), this.l8l(), this.l8m(), this.l8n(), this.l8o(), this.l8p(), this.l8q(), this.l8z() ]) l10 = (): number => sumFields([ this.l1(), this.l2a(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l9() ]) to1040Line8 = (): number => this.l10() l11 = (): number | undefined => undefined l12 = (): number | undefined => undefined l13 = (): number | undefined => sumFields([this.f1040.f8889.l13(), this.f1040.f8889Spouse?.l13()]) l14 = (): number | undefined => undefined l15 = (): number | undefined => this.f1040.scheduleSE.l13() l16 = (): number | undefined => undefined l17 = (): number | undefined => undefined l18 = (): number | undefined => undefined l19a = (): number | undefined => undefined l19b = (): string | undefined => undefined l19c = (): string | undefined => undefined l20 = (): number | undefined => undefined l21 = (): number | undefined => this.f1040.studentLoanInterestWorksheet?.l9() // Reserved for future use l22 = (): string | undefined => undefined l23 = (): number | undefined => undefined l24a = (): number | undefined => undefined l24b = (): number | undefined => undefined l24c = (): number | undefined => undefined l24d = (): number | undefined => undefined l24e = (): number | undefined => undefined l24f = (): number | undefined => undefined l24g = (): number | undefined => undefined l24h = (): number | undefined => undefined l24i = (): number | undefined => undefined l24j = (): number | undefined => undefined l24k = (): number | undefined => undefined l24zDesc = (): string | undefined => undefined l24zDesc2 = (): string | undefined => undefined l24z = (): number | undefined => undefined l25 = (): number => sumFields([ this.l24a(), this.l24b(), this.l24c(), this.l24d(), this.l24e(), this.l24f(), this.l24g(), this.l24h(), this.l24i(), this.l24j(), this.l24k(), this.l24z() ]) l26 = (): number => sumFields([ this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18(), this.l19a(), this.l20(), this.l21(), this.l23(), this.l25() ]) to1040Line10 = (): number => this.l26() fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2a(), this.l2b(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8a(), this.l8b(), this.l8c(), this.l8d(), this.l8e(), this.l8f(), this.l8g(), this.l8h(), this.l8i(), this.l8j(), this.l8k(), this.l8l(), this.l8m(), this.l8n(), this.l8o(), this.l8p(), Array.from(this.otherIncomeStrings).join(' '), undefined, this.l8z(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18(), this.l19a(), this.l19b(), this.l19c(), this.l20(), this.l21(), this.l22(), this.l23(), this.l24a(), this.l24b(), this.l24c(), this.l24d(), this.l24e(), this.l24f(), this.l24g(), this.l24h(), this.l24i(), this.l24j(), this.l24k(), this.l24zDesc(), this.l24zDesc2(), this.l24z(), this.l25(), this.l26() ] } ================================================ FILE: src/forms/Y2021/irsForms/Schedule2.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import { Field } from 'ustaxes/core/pdfFiller' export default class Schedule2 extends F1040Attachment { tag: FormTag = 'f1040s2' sequenceIndex = 2 // Part I: Tax l1 = (): number | undefined => this.f1040.f6251.l11() l2 = (): number | undefined => undefined // TODO: excess advance premium tax credit repayment (form 8962) l3 = (): number => sumFields([this.l1(), this.l2()]) // Part II: Other Tax l4 = (): number | undefined => this.f1040.scheduleSE.l12() // self-employment tax (schedule SE) l5 = (): number | undefined => undefined // TODO: unreported FICA tax l6 = (): number | undefined => undefined // TODO: additional tax on retirement accounts l7 = (): number | undefined => undefined // TODO: total additional ss and medicare tax l8 = (): number | undefined => undefined // TODO: additional tax on IRAs or other tax favored accoutns, form 5329 l9 = (): number | undefined => undefined // TODO: household employment taxes, schedule H l10 = (): number | undefined => undefined // repayment of firsttime homebuyer credit, form 5405 l11 = (): number | undefined => this.f1040.f8959.toSchedule2l11() l12 = (): number | undefined => this.f1040.f8960.toSchedule2l12() l13 = (): number | undefined => undefined // TODO: uncollected ss and medicare or rrta tax on tips or group-term life insurance, w-2, box 12 l14 = (): number | undefined => undefined // TODO - interest on tax due on installment income from the sale of residential lots and timeshares l15 = (): number | undefined => undefined //interest on the deferred tax on gain from certain installment sales with a sales price over 150000. l16 = (): number | undefined => undefined // recapture of low-income housing credit, form 8611 // Other additional taxes: // TODO: Recapture of other credits. List type, form number, and // amount ▶ l17aDesc = (): string | undefined => undefined l17a = (): number | undefined => undefined // TODO: Recapture of federal mortgage subsidy. If you sold your home in // 2021, see instructions l17b = (): number | undefined => undefined l17c = (): number | undefined => sumFields([this.f1040.f8889.l17b(), this.f1040.f8889Spouse?.l17b()]) l17d = (): number | undefined => sumFields([this.f1040.f8889.l21(), this.f1040.f8889Spouse?.l21()]) // TODO: Additional tax on Archer MSA distributions. Attach Form 8853 l17e = (): number | undefined => undefined // TODO: Additional tax on Medicare Advantage MSA distributions. Attach // Form 8853 l17f = (): number | undefined => undefined // TODO: Recapture of a charitable contribution deduction related to a // fractional interest in tangible personal property...17g l17g = (): number | undefined => undefined // TODO: Income you received from a nonqualified deferred compensation // plan that fails to meet the requirements of section 409A.17h l17h = (): number | undefined => undefined // TODO Compensation you received from a nonqualified deferred // compensation plan described in section 457A l17i = (): number | undefined => undefined // Section 72(m)(5) excess benefits tax l17j = (): number | undefined => undefined // TODO: Golden parachute payments l17k = (): number | undefined => undefined // Tax on accumulation distribution of trusts l17l = (): number | undefined => undefined // m Excise tax on insider stock compensation from an expatriated // corporation l17m = (): number | undefined => undefined // n Look-back interest under section 167(g) or 460(b) from Form // 8697 or 8866 l17n = (): number | undefined => undefined // o Tax on non-effectively connected income for any part of the // year you were a nonresident alien from Form 1040-NR l17o = (): number | undefined => undefined // p Any interest from Form 8621, line 16f, relating to distributions // from, and disassets of, stock of a section 1291 fund.. 17p l17p = (): number | undefined => undefined // q Any interest from Form 8621, line 24 l17q = (): number | undefined => undefined // z Any other taxes. List type and amount ▶ l17zDesc = (): string | undefined => undefined l17z = (): number | undefined => undefined // 18Total additional taxes. Add lines 17a through 17z.......18 l18 = (): number => sumFields([ this.l17a(), this.l17b(), this.l17c(), this.l17d(), this.l17e(), this.l17f(), this.l17g(), this.l17h(), this.l17i(), this.l17j(), this.l17k(), this.l17l(), this.l17m(), this.l17n(), this.l17o(), this.l17p(), this.l17q(), this.l17z() ]) // 19 Additional tax from Schedule 8812 l19 = (): number | undefined => this.f1040.schedule8812.toSchedule2Line19() // TODO: Section 965 net tax liability installment from Form 965-A. . l20 = (): number | undefined => undefined // Add lines 4, 7 through 16, 18, and 19. These are your total other taxes. Enter here l21 = (): number => sumFields([ this.l4(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l18(), this.l19() ]) to1040l23 = (): number => this.l21() // and on Form 1040 or 1040-SR, line 23, or Form 1040-NR, line 23b fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17aDesc(), this.l17a(), this.l17b(), this.l17c(), this.l17d(), this.l17e(), this.l17f(), this.l17g(), this.l17h(), this.l17i(), this.l17j(), this.l17k(), this.l17l(), this.l17m(), this.l17n(), this.l17o(), this.l17p(), this.l17q(), this.l17zDesc(), undefined, this.l17z(), this.l18(), this.l19(), this.l20(), this.l21() ] } ================================================ FILE: src/forms/Y2021/irsForms/Schedule3.ts ================================================ import F1040Attachment from './F1040Attachment' import { PersonRole } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import { fica } from '../data/federal' import { Field } from 'ustaxes/core/pdfFiller' export default class Schedule3 extends F1040Attachment { tag: FormTag = 'f1040s3' sequenceIndex = 3 claimableExcessSSTaxWithholding = (): number => { const w2s = this.f1040.validW2s() // Excess FICA taxes are calculated per person. If an individual person // has greater than the applicable amount then they are entitled to a refund // of that amount let claimableExcessFica = 0 const primaryFica = w2s .filter((w2) => w2.personRole == PersonRole.PRIMARY) .map((w2) => w2.ssWithholding) .reduce((l, r) => l + r, 0) const spouseFica = w2s .filter((w2) => w2.personRole == PersonRole.SPOUSE) .map((w2) => w2.ssWithholding) .reduce((l, r) => l + r, 0) if ( primaryFica > fica.maxSSTax && w2s .filter((w2) => w2.personRole == PersonRole.PRIMARY) .every((w2) => w2.ssWithholding <= fica.maxSSTax) ) { claimableExcessFica += primaryFica - fica.maxSSTax } if ( spouseFica > fica.maxSSTax && w2s .filter((w2) => w2.personRole == PersonRole.SPOUSE) .every((w2) => w2.ssWithholding <= fica.maxSSTax) ) { claimableExcessFica += spouseFica - fica.maxSSTax } return claimableExcessFica } isNeeded = (): boolean => this.claimableExcessSSTaxWithholding() > 0 deductions = (): number => 0 // Part I: Nonrefundable credits l1 = (): number | undefined => undefined l2 = (): number | undefined => undefined l3 = (): number | undefined => this.f1040.f8863?.l19() l4 = (): number | undefined => undefined l5 = (): number | undefined => undefined l6a = (): number | undefined => undefined // TODO: other credits l6b = (): number | undefined => undefined // TODO: other credits l6c = (): number | undefined => undefined // TODO: other credits l6d = (): number | undefined => undefined // TODO: other credits l6e = (): number | undefined => undefined // TODO: other credits l6f = (): number | undefined => undefined // TODO: other credits l6g = (): number | undefined => undefined // TODO: other credits l6h = (): number | undefined => undefined // TODO: other credits l6i = (): number | undefined => undefined // TODO: other credits l6j = (): number | undefined => undefined // TODO: other credits l6k = (): number | undefined => undefined // TODO: other credits l6l = (): number | undefined => undefined // TODO: other credits l6zDesc1 = (): string | undefined => undefined l6zDesc2 = (): string | undefined => undefined l6z = (): number | undefined => undefined // TODO: other credits l7 = (): number => sumFields([ this.l6a(), this.l6b(), this.l6c(), this.l6d(), this.l6e(), this.l6f(), this.l6g(), this.l6h(), this.l6i(), this.l6j(), this.l6k(), this.l6l(), this.l6z() ]) l8 = (): number => sumFields([ this.l1(), this.l2(), this.l3(), this.l4(), this.l5(), this.l7() ]) // Part II: Other payments and refundable credits l9 = (): number | undefined => this.f1040.f8962?.credit() // TODO: Amount paid with extension for time to file l10 = (): number | undefined => undefined l11 = (): number => // TODO: also applies to RRTA tax this.claimableExcessSSTaxWithholding() l12 = (): number | undefined => this.f1040.f4136?.credit() l13a = (): number | undefined => this.f1040.f2439?.credit() // TODO: qualified sick and family leave credits // Schedule H and form 7202 pre 4/1/21 l13b = (): number | undefined => undefined // Health coverage tax credit from 8885 l13c = (): number | undefined => undefined // TODO: Credit for repayment of amounts included in income from earlier years l13d = (): number | undefined => undefined // TODO: 'other' box // reserved! l13e = (): number | undefined => undefined // deferred amount of net 965 tax liability l13f = (): number | undefined => undefined l13g = (): number | undefined => this.f1040.f2441?.credit() // TODO: qualified sick and family leave credits // Schedule H and form 7202 post 3/31/21 l13h = (): number | undefined => undefined l13zDesc1 = (): string | undefined => undefined l13zDesc2 = (): string | undefined => undefined l13z = (): number | undefined => undefined l14 = (): number => sumFields([ this.l13a(), this.l13b(), this.l13c(), this.l13d(), this.l13e(), this.l13f(), this.l13g(), this.l13h(), this.l13z() ]) l15 = (): number => sumFields([this.l9(), this.l10(), this.l11(), this.l12(), this.l14()]) // Credit for child and dependent care expenses form 2441, line 10 fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2(), this.l3(), this.l4(), this.l5(), this.l6a(), this.l6b(), this.l6c(), this.l6d(), this.l6e(), this.l6f(), this.l6g(), this.l6h(), this.l6i(), this.l6j(), this.l6k(), this.l6l(), this.l6zDesc1(), this.l6zDesc2(), this.l6z(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13a(), this.l13b(), this.l13c(), this.l13d(), //this.l13e(), // this field is left for future use and is not fillable this.l13f(), this.l13g(), this.l13h(), this.l13zDesc1(), this.l13zDesc2(), this.l13z(), this.l14(), this.l15() ] } ================================================ FILE: src/forms/Y2021/irsForms/Schedule8812.ts ================================================ import F1040Attachment from './F1040Attachment' import { CreditType, Dependent, FilingStatus } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import { FormTag } from 'ustaxes/core/irsForms/Form' import { CURRENT_YEAR } from '../data/federal' import { Field } from 'ustaxes/core/pdfFiller' import { nextMultipleOf1000 } from 'ustaxes/core/util' type Part1b = { allowed: boolean } & Partial<{ l14a: number l14b: number l14c: number l14d: number l14e: number l14f: number l14g: number l14h: number l14i: number }> type Part1c = { allowed: boolean } & Partial<{ l15a: number l15b: number l15c: number l15d: number l15e: number l15f: number l15g: number l15h: number }> type Part2a = { allowed: boolean } & Partial<{ l16a: number l16bdeps: number l16b: number l17: number l18a: number l18b: number l19No: boolean l19Yes: boolean l19: number l20: number l20No: boolean l20Yes: boolean toLine27: number }> type Part2b = { allowed: boolean } & Partial<{ l21: number l22: number l23: number l24: number l25: number l26: number toLine27: number }> type Part3 = { allowed: boolean } & Partial<{ l28a: number l28b: number l29: number l30: number l31: number l32: number l33: number l34: number l35: number l36: string l37: number l38: number l39: number l40: number }> export default class Schedule8812 extends F1040Attachment { tag: FormTag = 'f1040s8' sequenceIndex = 47 isNeeded = (): boolean => this.f1040.info.taxPayer.dependents.some( (dep) => this.f1040.qualifyingDependents.qualifiesChild(dep) || this.f1040.qualifyingDependents.qualifiesOther(dep) ) l1 = (): number => this.f1040.l11() // TODO: Puerto Rico income l2a = (): number => 0 l2b = (): number => sumFields([this.f1040.f2555?.l45(), this.f1040.f2555?.l50()]) l2c = (): number => this.f1040.f4563?.l15() ?? 0 l2d = (): number => sumFields([this.l2a(), this.l2b(), this.l2c()]) l3 = (): number => sumFields([this.l1(), this.l2d()]) creditDependents = (): Dependent[] => this.f1040.qualifyingDependents.qualifyingChildren() l4a = (): number => this.creditDependents().length // Number of qualifying children under age 6 at EOY l4b = (): number => this.creditDependents().filter( (d) => d.qualifyingInfo !== undefined && d.dateOfBirth.getFullYear() > CURRENT_YEAR - 6 ).length l4c = (): number => Math.max(0, this.l4a() - this.l4b()) /** * Computed using the line 5 worksheet, Schedule 8812 instructions */ l5 = (): number => { const fs = this.f1040.info.taxPayer.filingStatus const wsl1 = this.l4b() * 3600 const wsl2 = this.l4c() * 3000 const wsl3 = wsl1 + wsl2 const wsl4 = this.l4a() * 2000 // Note wsl3 >= wsl4 const wsl5 = wsl3 - wsl4 const wsl6values: Partial<{ [key in FilingStatus]: number }> = { [FilingStatus.MFJ]: 12500, [FilingStatus.W]: 2500, [FilingStatus.HOH]: 4375 } const wsl6 = wsl6values[fs] ?? 6250 const wsl7 = Math.min(wsl5, wsl6) const wsl8values: Partial<{ [key in FilingStatus]: number }> = { [FilingStatus.MFJ]: 150000, [FilingStatus.W]: 150000, [FilingStatus.HOH]: 112500 } const wsl8 = wsl8values[fs] ?? 75000 const wsl9 = nextMultipleOf1000(Math.max(0, this.l3() - wsl8)) const wsl10 = wsl9 * 0.05 const wsl11 = Math.min(wsl7, wsl10) return Math.max(0, wsl3 - wsl11) } // TODO: Verify: // Number of other dependents, including any qualifying children, who are not under age 18 or who do not have the required SSN. Do not include yourself, your spouse, // or anyone who is not a US citizen/national/resident alien, // or do not have the required SSN. l6 = (): number => this.f1040.info.taxPayer.dependents.length - this.l4a() l7 = (): number => this.l6() * 500 l8 = (): number => sumFields([this.l5(), this.l7()]) l9 = (): number => this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ ? 400000 : 200000 l10 = (): number => nextMultipleOf1000(Math.max(0, this.l3() - this.l9())) l11 = (): number => this.l10() * 0.05 l12 = (): number => Math.max(0, this.l8() - this.l11()) // you and spouse have residence in US for more than half of year. // TODO: Assuming true l13a = (): boolean => true // Resident of puerto rico for 2021 // TODO: assuming false l13b = (): boolean => false // Part 1 B - requires a box on line 13 req = (pred: boolean) => (f: () => A) => (): A | undefined => { if (pred) { return f() } else { return undefined } } req13 = this.req(this.l13a() || this.l13b()) creditLimitWorksheetB = (): number | undefined => undefined creditLimitWorksheetA = (): number => { const wsl1 = this.f1040.l18() const schedule3Fields = this.f1040.schedule3.isNeeded() ? [ this.f1040.schedule3.l1(), this.f1040.schedule3.l2(), this.f1040.schedule3.l3(), this.f1040.schedule3.l4(), this.f1040.schedule3.l6l() ] : [] const wsl2 = sumFields([ ...schedule3Fields, this.f1040.f5695?.l30(), this.f1040.f8936?.l15(), this.f1040.f8936?.l23(), this.f1040.scheduleR?.l22() ]) const wsl3 = Math.max(0, wsl1 - wsl2) const wsl4 = this.creditLimitWorksheetB() ?? 0 const wsl5 = Math.max(0, wsl3 - wsl4) return wsl5 } // TODO: Letter 6419 advance child tax credit payments letter6419Payments = (): number | undefined => this.f1040.info.credits .filter((c) => c.type === CreditType.AdvanceChildTaxCredit) .reduce((sum, c) => sum + c.amount, 0) part1b = (): Part1b => { const allowed = this.l13a() || this.l13b() if (!allowed) return { allowed: false } const l14a = Math.min(this.l7(), this.l12()) const l14b = Math.max(this.l12() - l14a) const l14c = l14a === 0 ? 0 : this.creditLimitWorksheetA() const l14d = Math.min(l14a, l14c) const l14e = sumFields([l14b, l14d]) // TODO: // IMPORTANT: Letter 6419 advance child tax credit payments const l14f = this.letter6419Payments() ?? 0 const l14g = Math.max(0, l14e - l14f) // Credit for other dependents const l14h = Math.min(l14d, l14g) // Refundable child tax credit const l14i = Math.max(0, l14g - l14h) return { allowed, l14a, l14b, l14c, l14d, l14e, l14f, l14g, l14h, l14i } } part1c = (): Part1c => { const allowed = !(this.l13a() || this.l13b()) if (!allowed) return { allowed: false } const l15a = this.creditLimitWorksheetA() const l15b = Math.min(this.l12(), l15a) //TODO - implement after 2a through 2c const l15c = this.l27() ?? 0 const l15d = sumFields([l15b, l15c]) const l15e = this.letter6419Payments() ?? 0 const l15f = Math.max(0, l15d - l15e) const l15g = Math.min(l15b, l15f) const l15h = Math.max(0, l15f - l15g) return { allowed, l15a, l15b, l15c, l15d, l15e, l15f, l15g, l15h } } // from Part 1-C, Line 15b 1 - 3 part2AllowedBy1C = (): boolean => { const part1c = this.part1c() return ( !part1c.allowed || (this.f1040.f2555 === undefined && this.l4a() > 0 && this.l12() > (part1c.l15a ?? 0)) ) } to1040Line19 = (): number | undefined => this.part1b().l14h ?? this.part1c().l15g to1040Line28 = (): number | undefined => this.part1b().l14i ?? this.part1c().l15h earnedIncomeWorksheet = (): number => { const l1a = this.f1040.l1() const l1b = this.f1040.nonTaxableCombatPay() const l2a = this.f1040.scheduleC?.l1() ?? 0 // Todo: 1065 Schedule K-1, box 14, code A, and other // data also belong here. const l2b = this.f1040.scheduleC?.l31() ?? 0 // TODO: Net farm profit... // const l2c = undefined // TODO: Farm optional method for self-employment net earnings const l2d = 0 // TODO: min(l2c, l2d) // Allowed to be a loss: const l2e = Math.min(0, l2d) const l3 = sumFields([l1a, l1b, l2a, l2b, l2e]) const allowed = l3 > 0 // TODO: Scholarship or grant not reported on w-2 const l4a = !allowed ? undefined : 0 // TODO: Penal income const l4b = !allowed ? undefined : 0 // TODO: nonqualified deferred comp plan or 457 plan const l4c = !allowed ? undefined : 0 // TODO: Amount included on 1040 that is a medicaid // waiver payment excluded from income, schedule 1, line 8z // or choose to include in earned income, then enter 0. const l4d = !allowed ? undefined : 0 const l5 = this.f1040.schedule1.l15() ?? 0 const l6 = sumFields([l4a, l4b, l4c, l4d, l5]) const l7 = Math.max(0, l3 - l6) return l7 } part2a = (): Part2a => { const l16a = Math.max(0, this.l12() - (this.part1c().l15b ?? 0)) const l16bdeps = this.l4a() const allowed = this.part2AllowedBy1C() && this.f1040.f2555 === undefined && !(this.l13a() || this.l13b()) && l16a > 0 && l16bdeps > 0 if (!allowed) return { allowed: false } const l16b = l16bdeps * 1400 const l17 = Math.min(l16a, l16b) const l18a = this.earnedIncomeWorksheet() const l18b = this.f1040.nonTaxableCombatPay() ?? 0 const l19No = l18a > 2500 const l19Yes = l18a <= 2500 const l19 = Math.max(0, l18a - 2500) const l20 = l19 * 0.15 const l20No = l16b >= 4200 const l20Yes = l16b < 4200 const toLine27 = (() => { if (l20No && l20 > 0) { return Math.min(l17, l20) } else if (l20Yes && l20 >= l17) { return l17 } })() return { allowed: true, l16a, l16bdeps, l16b, l17, l18a, l18b, l19No, l19Yes, l19, l20, l20No, l20Yes, toLine27 } } part2b = (): Part2b => { const part2a = this.part2a() // three or more qualifying children. const allowed = part2a.allowed if (!allowed) return { allowed: false } const ssWithholding = this.f1040 .validW2s() .reduce((res, w2) => res + w2.ssWithholding, 0) const medicareWithholding = this.f1040 .validW2s() .reduce((res, w2) => res + w2.medicareWithholding, 0) const l21 = ssWithholding + medicareWithholding const l22 = sumFields([ this.f1040.schedule1.l15(), this.f1040.schedule2.l5(), this.f1040.schedule2.l6(), this.f1040.schedule2.l13() ]) const l23 = sumFields([l21, l22]) const l24 = sumFields([this.f1040.l27a(), this.f1040.schedule3.l11()]) const l25 = Math.max(0, l23 - l24) const l26 = Math.max(part2a.l20 ?? 0, l25) const toLine27 = Math.min(part2a.l17 ?? 0, l26) return { allowed: true, l21, l22, l23, l24, l25, l26, toLine27 } } l27 = (): number | undefined => this.part2a().toLine27 ?? this.part2b().toLine27 part3 = (): Part3 => { const fs = this.f1040.info.taxPayer.filingStatus const part1b = this.part1b() const part1c = this.part1c() const allowed = (part1b.allowed && part1b.l14g === 0) || (part1c.allowed && part1c.l15f === 0) if (!allowed) return { allowed: false } const l28a = this.part1b().l14f ?? this.part1c().l15e const l28b = part1b.l14e ?? part1c.l15d const l29 = Math.max(0, (l28a ?? 0) - (l28b ?? 0)) // Enter the number of qualifying children taken into account in determining the annual advance amount you // received for 2021. See your Letter 6419 for this number. If you are missing your Letter 6419, you are filing a joint // return, or you received more than one Letter 6419, see the instructions before entering a number on this line const l30 = this.l4a() const l31 = Math.min(this.l4a(), l30) const l32 = Math.max(0, l30 - l31) const l33Values: { [key in FilingStatus]: number } = { [FilingStatus.MFJ]: 60000, [FilingStatus.W]: 60000, [FilingStatus.HOH]: 50000, [FilingStatus.S]: 40000, [FilingStatus.MFS]: 40000 } const l33 = l33Values[fs] const l34 = Math.max(0, this.l3() - l33) const l35 = l33 const l36 = Math.min(1, l34 / l35).toFixed(3) const l37 = l32 * 2000 const l38 = l37 * parseFloat(l36) const l39 = Math.max(0, l37 - l38) const l40 = Math.max(0, l29 - l39) return { allowed: true, l28a, l28b, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40 } } l40 = (): number => this.part3().l40 ?? 0 // This is your additional tax. toSchedule2Line19: () => number = this.l40 fields = (): Field[] => { const part1b = this.part1b() const part1c = this.part1c() const part2a = this.part2a() const part2b = this.part2b() const part3 = this.part3() return [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2a(), this.l2b(), this.l2c(), this.l2d(), this.l3(), this.l4a(), this.l4b(), this.l4c(), this.l5(), this.l6(), this.l7(), this.l8(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13a(), this.l13b(), part1b.l14a, part1b.l14b, part1b.l14c, part1b.l14d, part1b.l14e, part1b.l14f, part1b.l14g, part1b.l14h, part1b.l14i, part1c.l15a, part1c.l15b, part1c.l15c, part1c.l15d, part1c.l15e, part1c.l15f, part1c.l15g, part1c.l15h, part2a.l16a, part2a.l16bdeps, part2a.l16b, part2a.l17, part2a.l18a, part2a.l18b, part2a.l19No, part2a.l19Yes, part2a.l19, part2a.l20, part2a.l20No, part2a.l20Yes, part2b.l21, part2b.l22, part2b.l23, part2b.l24, part2b.l25, part2b.l26, this.l27(), part3.l28a, part3.l28b, part3.l29, part3.l30, part3.l31, part3.l32, part3.l33, part3.l34, part3.l35, part3.l36, part3.l37, part3.l38, part3.l39, this.l40() ] } } ================================================ FILE: src/forms/Y2021/irsForms/ScheduleA.ts ================================================ import F1040Attachment from './F1040Attachment' import { FilingStatus, ItemizedDeductions } from 'ustaxes/core/data' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Field } from 'ustaxes/core/pdfFiller' import F1040 from './F1040' const blankItemizedDeductions = { medicalAndDental: 0, stateAndLocalTaxes: 0, isSalesTax: false, stateAndLocalRealEstateTaxes: 0, stateAndLocalPropertyTaxes: 0, interest8a: 0, interest8b: 0, interest8c: 0, interest8d: 0, investmentInterest: 0, charityCashCheck: 0, charityOther: 0 } export default class ScheduleA extends F1040Attachment { tag: FormTag = 'f1040sa' itemizedDeductions: ItemizedDeductions sequenceIndex = 7 constructor(f1040: F1040) { super(f1040) this.itemizedDeductions = { ...blankItemizedDeductions, ...(f1040.info.itemizedDeductions ?? {}) } } isNeeded = (): boolean => { if (this.f1040.info.itemizedDeductions !== undefined) { const standardDeduction = this.f1040.standardDeduction() const itemizedAmount = this.deductions() return ( standardDeduction === undefined || itemizedAmount > standardDeduction ) } return false } deductions(): number { return ( this.l4() + this.l7() + this.l10() + this.l14() + this.l15() + this.l16() ) } l1 = (): number => Number(this.itemizedDeductions.medicalAndDental) l2 = (): number => this.f1040.l11() l3 = (): number => this.l2() * 0.075 l4 = (): number => Math.max(0, this.l1() - this.l3()) l5aSalesTax = (): boolean => this.itemizedDeductions.isSalesTax l5a = (): number => Number(this.itemizedDeductions.stateAndLocalTaxes) l5b = (): number => Number(this.itemizedDeductions.stateAndLocalRealEstateTaxes) l5c = (): number => Number(this.itemizedDeductions.stateAndLocalPropertyTaxes) l5d = (): number => this.l5a() + this.l5b() + this.l5c() l5e = (): number => { const max = this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS ? 5000 : 10000 return Math.min(max, this.l5d()) } // TODO l6OtherTaxesTypeAndAmount1 = (): string | undefined => undefined l6OtherTaxesTypeAndAmount2 = (): string | undefined => undefined // TODO l6 = (): number | undefined => undefined l7 = (): number => this.l5e() + (this.l6() ?? 0) // TODO l8AllMortgageLoan = (): boolean => false l8a = (): number => Number(this.itemizedDeductions.interest8a) // TODO l8bUnreportedInterest1 = (): string | undefined => undefined // TODO l8bUnreportedInterest2 = (): string | undefined => undefined l8b = (): number => Number(this.itemizedDeductions.interest8b) l8c = (): number => Number(this.itemizedDeductions.interest8c) l8d = (): number => Number(this.itemizedDeductions.interest8d) l8e = (): number => this.l8a() + this.l8b() + this.l8c() + this.l8d() // Used in Form 8960 l9 = (): number | undefined => Number(this.itemizedDeductions.investmentInterest) l10 = (): number => this.l8e() + (this.l9() ?? 0) l11 = (): number => Number(this.itemizedDeductions.charityCashCheck) // TODO: form 8283 l12 = (): number => Number(this.itemizedDeductions.charityOther) l13 = (): number => 0 l14 = (): number => this.l11() + this.l12() + this.l13() l15 = (): number => 0 // TODO l16Other1 = (): string | undefined => undefined l16Other2 = (): string | undefined => undefined l16Other3 = (): string | undefined => undefined l16 = (): number => 0 l17 = (): number => this.l4() + this.l7() + this.l10() + this.l14() + this.l15() + this.l16() l18 = (): boolean => false fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, this.l1(), this.l2(), this.l3(), this.l4(), this.l5aSalesTax(), this.l5a(), this.l5b(), this.l5c(), this.l5d(), this.l5e(), this.l6OtherTaxesTypeAndAmount1(), this.l6OtherTaxesTypeAndAmount2(), this.l6(), this.l7(), this.l8AllMortgageLoan(), this.l8a(), this.l8bUnreportedInterest1(), this.l8bUnreportedInterest2(), this.l8b(), this.l8c(), this.l8d(), this.l8e(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16Other1(), this.l16Other2(), this.l16Other3(), this.l16(), this.l17(), this.l18() ] } ================================================ FILE: src/forms/Y2021/irsForms/ScheduleB.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import { Field } from 'ustaxes/core/pdfFiller' import F1040 from './F1040' interface PayerAmount { payer?: string amount?: number } export default class ScheduleB extends F1040Attachment { tag: FormTag = 'f1040sb' sequenceIndex = 8 readonly interestPayersLimit = 14 readonly dividendPayersLimit = 16 index = 0 constructor(f1040: F1040, index = 0) { super(f1040) this.index = index } copies = (): ScheduleB[] => { if (this.index === 0) { const numInterestPayers = this.l1Fields().length const numDivPayers = this.l5Fields().length const extraCopiesNeeded = Math.floor( Math.max( numInterestPayers / this.interestPayersLimit, numDivPayers / this.dividendPayersLimit ) ) return Array.from(Array(extraCopiesNeeded)).map( (_, i) => new ScheduleB(this.f1040, i + 1) ) } else { return [] } } isNeeded = (): boolean => this.l1Fields().length > 0 || this.l5Fields().length > 0 l1Fields = (): PayerAmount[] => this.f1040 .f1099Ints() .map((v) => ({ payer: v.payer, amount: v.form.income })) .concat( this.f1040.k1sWithInterest().map((v) => ({ payer: v.partnershipName, amount: v.interestIncome })) ) l1 = (): Array => { const payerValues = this.l1Fields().slice( this.index * this.interestPayersLimit, (this.index + 1) * this.interestPayersLimit ) const rightPad = 2 * (this.interestPayersLimit - payerValues.length) // ensure we return an array of length interestPayersLimit * 2. // This form may have multiple copies, only display the copies for this form return payerValues .flatMap(({ payer, amount }) => [payer, amount?.toString()]) .concat(Array(rightPad).fill(undefined)) } l2 = (): number => sumFields(this.f1040.f1099Ints().map((f) => f.form.income)) // TODO: Interest from tax exempt savings bonds l3 = (): number | undefined => undefined l4 = (): number => this.l2() - (this.l3() ?? 0) /** * Total interest on all schedule Bs. */ to1040l2b = (): number => [this, ...this.copies()].reduce((acc, f) => acc + f.l4(), 0) l5Fields = (): PayerAmount[] => this.f1040.f1099Divs().map((v) => ({ payer: v.payer, amount: v.form.dividends })) l5 = (): Array => { const payerValues = this.l5Fields().slice( this.index * this.dividendPayersLimit, (this.index + 1) * this.dividendPayersLimit ) const rightPad = 2 * (this.dividendPayersLimit - payerValues.length) return payerValues .flatMap(({ payer, amount }) => [payer, amount]) .concat(Array(rightPad).fill(undefined)) } l6 = (): number => sumFields(this.l5Fields().map(({ amount }) => amount)) /** * Total dividends on all schedule Bs. */ to1040l3b = (): number => [this, ...this.copies()].reduce((acc, f) => acc + f.l6(), 0) foreignAccount = (): boolean => this.f1040.info.questions.FOREIGN_ACCOUNT_EXISTS ?? false fincenForm = (): boolean => this.f1040.info.questions.FINCEN_114 ?? false fincenCountry = (): string | undefined => this.f1040.info.questions.FINCEN_114_ACCOUNT_COUNTRY foreignTrust = (): boolean => this.f1040.info.questions.FOREIGN_TRUST_RELATIONSHIP ?? false l7a = (): [boolean, boolean] => [ this.foreignAccount(), !this.foreignAccount() ] l7a2 = (): [boolean, boolean] => [this.fincenForm(), !this.fincenForm()] l7b = (): string | undefined => this.fincenCountry() l8 = (): [boolean, boolean] => [this.foreignTrust(), !this.foreignTrust()] fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, ...this.l1(), this.l2(), this.l3(), this.l4(), ...this.l5(), this.l6(), ...this.l7a(), ...this.l7a2(), this.l7b(), ...this.l8() ] } ================================================ FILE: src/forms/Y2021/irsForms/ScheduleC.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' /** * Not implemented */ export default class ScheduleC extends F1040Attachment { tag: FormTag = 'f1040sc' sequenceIndex = 9 // TODO: statutory employee income // shown on Schedule 8812, earned income l1 = (): number | undefined => undefined // TODO: net profit or loss // shown on Schedule 8812, earned income l31 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/ScheduleD.ts ================================================ import { F1099BData, FilingStatus } from 'ustaxes/core/data' import { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import SDRateGainWorksheet from './worksheets/SDRateGainWorksheet' import SDUnrecaptured1250 from './worksheets/SDUnrecaptured1250' import F8949 from './F8949' import F1040Attachment from './F1040Attachment' import F1040 from './F1040' import { Field } from 'ustaxes/core/pdfFiller' import SDTaxWorksheet from './worksheets/SDTaxWorksheet' import QualDivAndCGWorksheet from './worksheets/SDQualifiedAndCapGains' export default class ScheduleD extends F1040Attachment { tag: FormTag = 'f1040sd' sequenceIndex = 12 _aggregated?: F1099BData qualifiedDivAndCGWorksheet: QualDivAndCGWorksheet taxWorksheet: SDTaxWorksheet rateGainWorksheet: SDRateGainWorksheet unrecaptured1250: SDUnrecaptured1250 _f8949s?: F8949[] readonly l21MinMFS = 1500 readonly l21MinDefault = 3000 constructor(f1040: F1040) { super(f1040) this.rateGainWorksheet = new SDRateGainWorksheet() this.taxWorksheet = new SDTaxWorksheet(f1040) this.qualifiedDivAndCGWorksheet = new QualDivAndCGWorksheet(f1040) this.unrecaptured1250 = new SDUnrecaptured1250() } get aggregated(): F1099BData { if (this._aggregated === undefined) { const bs: F1099BData[] = this.f1040.f1099Bs().map((f) => f.form) this._aggregated = { shortTermProceeds: bs.reduce((l, r) => l + r.shortTermProceeds, 0), shortTermCostBasis: bs.reduce((l, r) => l + r.shortTermCostBasis, 0), longTermProceeds: bs.reduce((l, r) => l + r.longTermProceeds, 0), longTermCostBasis: bs.reduce((l, r) => l + r.longTermCostBasis, 0) } } return this._aggregated } isNeeded = (): boolean => this.f1040.f1099Bs().length > 0 || this.f1040.f8949.isNeeded() l21Min = (): number => { if (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFS) { return this.l21MinMFS } return this.l21MinDefault } l1ad = (): number | undefined => this.aggregated.shortTermProceeds l1ae = (): number | undefined => this.aggregated.shortTermCostBasis // This field is greyed out, but fillable l1ag = (): number | undefined => undefined l1ah = (): number => sumFields([this.l1ad(), 0 - (this.l1ae() ?? 0)]) l1f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxA()) l1bd = (): number => sumFields(this.l1f8949s().map((f) => f.shortTermTotalProceeds())) l1be = (): number => sumFields(this.l1f8949s().map((f) => f.shortTermTotalCost())) l1bg = (): number => sumFields(this.l1f8949s().map((f) => f.shortTermTotalAdjustments())) l1bh = (): number => sumFields(this.l1f8949s().map((f) => f.shortTermTotalGain())) l2f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxB()) l2d = (): number => sumFields(this.l2f8949s().map((f) => f.shortTermTotalProceeds())) l2e = (): number => sumFields(this.l2f8949s().map((f) => f.shortTermTotalCost())) l2g = (): number => sumFields(this.l2f8949s().map((f) => f.shortTermTotalAdjustments())) l2h = (): number => sumFields(this.l2f8949s().map((f) => f.shortTermTotalGain())) l3f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part1BoxC()) l3d = (): number => sumFields(this.l3f8949s().map((f) => f.shortTermTotalProceeds())) l3e = (): number => sumFields(this.l3f8949s().map((f) => f.shortTermTotalCost())) l3g = (): number => sumFields(this.l3f8949s().map((f) => f.shortTermTotalAdjustments())) l3h = (): number => sumFields(this.l3f8949s().map((f) => f.shortTermTotalGain())) l4 = (): number | undefined => undefined l5 = (): number | undefined => undefined l6 = (): number | undefined => undefined l7 = (): number => sumFields([ this.l1ah(), this.l1bh(), this.l2h(), this.l3h(), this.l4(), this.l5(), this.l6() ]) l8ad = (): number | undefined => this.aggregated.longTermProceeds l8ae = (): number | undefined => this.aggregated.longTermCostBasis // This field is greyed out, but fillable l8ag = (): number | undefined => undefined l8ah = (): number | undefined => sumFields([this.l8ad(), 0 - (this.l8ae() ?? 0)]) l8f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxD()) l8bd = (): number => sumFields(this.l8f8949s().map((f) => f.longTermTotalProceeds())) l8be = (): number => sumFields(this.l8f8949s().map((f) => f.longTermTotalCost())) l8bg = (): number => sumFields(this.l8f8949s().map((f) => f.longTermTotalAdjustments())) l8bh = (): number => sumFields(this.l8f8949s().map((f) => f.longTermTotalGain())) l9f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxE()) l9d = (): number => sumFields(this.l9f8949s().map((f) => f.longTermTotalProceeds())) l9e = (): number => sumFields(this.l9f8949s().map((f) => f.longTermTotalCost())) l9g = (): number => sumFields(this.l9f8949s().map((f) => f.longTermTotalAdjustments())) l9h = (): number => sumFields(this.l9f8949s().map((f) => f.longTermTotalGain())) l10f8949s = (): F8949[] => this.f1040.f8949s.filter((f) => f.part2BoxF()) l10d = (): number => sumFields(this.l10f8949s().map((f) => f.longTermTotalProceeds())) l10e = (): number => sumFields(this.l10f8949s().map((f) => f.longTermTotalCost())) l10g = (): number => sumFields(this.l10f8949s().map((f) => f.longTermTotalAdjustments())) l10h = (): number => sumFields(this.l10f8949s().map((f) => f.longTermTotalGain())) l11 = (): number | undefined => undefined l12 = (): number | undefined => undefined l13 = (): number | undefined => this.f1040 .f1099Divs() .reduce((s, f) => s + f.form.totalCapitalGainsDistributions, 0) l14 = (): number | undefined => undefined l15 = (): number => sumFields([ this.l8ah(), this.l8bh(), this.l9h(), this.l10h(), this.l11(), this.l12(), this.l13(), this.l14() ]) // L7 + L15 // If +, enter on L16 of F1040 // If -, go to L21 // If 0, go to L22 l16 = (): number => sumFields([this.l7(), this.l15()]) // Are L15 and L16 both gains? l17 = (): boolean => this.l15() > 0 && this.l16() > 0 l18 = (): number | undefined => { if (!this.l17()) { return undefined } return this.rateGainWorksheet.l7() } l19 = (): number | undefined => { if (!this.l17()) { return undefined } return this.unrecaptured1250.l18() } l20 = (): boolean | undefined => { if (!this.l17()) { return undefined } return (this.l18() ?? 0) === 0 && (this.l19() ?? 0) === 0 } fillL21 = (): boolean => !this.l20() && ((this.l16() > 0 && this.l17()) || this.l16() < 0) l21 = (): number | undefined => { if (this.fillL21()) { return Math.max(-this.l21Min(), this.l16()) } } haveQualifiedDividends = (): boolean => this.f1040.f1099Divs().some((f) => f.form.qualifiedDividends > 0) // TODO: Schedule D tax worksheet // neither box should be checked if this question was not required to be answered by l20. l22 = (): boolean | undefined => { if (this.l20() !== false) { return this.haveQualifiedDividends() } } lossCarryForward = (): number => { const amount = this.l16() + this.l21Min() if (amount < 0) { return -amount } return 0 } to1040 = (): number => this.l21() ?? this.l16() computeTaxOnQDWorksheet = (): boolean => (this.l20() ?? false) || (this.l22() ?? false) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, false, false, this.l1ad(), this.l1ae(), this.l1ag(), this.l1ah(), this.l1bd(), this.l1be(), this.l1bg(), this.l1bh(), this.l2d(), this.l2e(), this.l2g(), this.l2h(), this.l3d(), this.l3e(), this.l3g(), this.l3h(), this.l4(), this.l5(), this.l6(), this.l7(), this.l8ad(), this.l8ae(), this.l8ag(), this.l8ah(), this.l8bd(), this.l8be(), this.l8bg(), this.l8bh(), this.l9d(), this.l9e(), this.l9g(), this.l9h(), this.l10d(), this.l10e(), this.l10g(), this.l10h(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), !this.l17(), this.l18(), this.l19(), this.l20() === true, this.l20() === false, this.l21(), this.l22() === true, this.l22() === false ] } ================================================ FILE: src/forms/Y2021/irsForms/ScheduleE.ts ================================================ import { Address, Property, PropertyType, PropertyExpenseTypeName } from 'ustaxes/core/data' import { displayNegPos, sumFields } from 'ustaxes/core/irsForms/util' import _ from 'lodash' import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { Field } from 'ustaxes/core/pdfFiller' type Cell = number | undefined export type MatrixRow = [Cell, Cell, Cell] const fill = (values: number[]): MatrixRow => { const realValues = (values as Cell[]).slice(0, 3).map((v) => { if (v === 0) { return undefined } return v }) return [ ...realValues, ...Array.from(Array(3 - realValues.length)).map(() => undefined) ] as MatrixRow } const propTypeIndex = { [PropertyType.singleFamily]: 1, [PropertyType.multiFamily]: 2, [PropertyType.vacation]: 3, [PropertyType.commercial]: 4, [PropertyType.land]: 5, [PropertyType.selfRental]: 7, [PropertyType.other]: 8 } export default class ScheduleE extends F1040Attachment { tag: FormTag = 'f1040se' sequenceIndex = 13 isNeeded = (): boolean => this.f1040.info.realEstate.length > 0 || this.f1040.info.scheduleK1Form1065s.length > 0 addressString = (address: Address): string => [ address.address, address.city, address.state ?? address.province ?? '', address.zip ?? address.postalCode ?? '' ].join(', ') propForRow = (row: number): Property | undefined => { if (row < this.f1040.info.realEstate.length) { return this.f1040.info.realEstate[row] } } /** * Whether or not you can deduct expenses for the unit depends on whether or not you used * the unit as a home in 2020. You used the unit as a home if your personal use of the unit * was more than the greater of: * 14 days, or * 10% of the total days it was rented to others at a fair rental price. * @param p */ propertyUseTest = (p: Property): boolean => p.personalUseDays <= Math.max(14, 0.1 * p.rentalDays) l3 = (): MatrixRow => { const properties = this.f1040.info.realEstate return fill(properties.map((a) => a.rentReceived)) } // TODO: not implemented l4 = (): MatrixRow => { return [undefined, undefined, undefined] } getExpensesRow = (expType: PropertyExpenseTypeName): MatrixRow => fill( this.f1040.info.realEstate.map((p) => { if (this.propertyUseTest(p)) { return p.expenses[expType] ?? 0 } return 0 }) ) // Matching order of expenses in rows of form expenses: PropertyExpenseTypeName[] = [ 'advertising', 'auto', 'cleaning', 'commissions', 'insurance', 'legal', 'management', 'mortgage', 'otherInterest', 'repairs', 'supplies', 'taxes', 'utilities', 'depreciation' ] l19 = (): [string | undefined, MatrixRow] => { const expenseRow = this.getExpensesRow('other') const otherText = this.f1040.info.realEstate .flatMap((p) => p.otherExpenseType !== undefined ? [p.otherExpenseType] : [] ) .join(',') return [otherText, expenseRow] } allExpenses = (): MatrixRow[] => this.expenses.map((e) => this.getExpensesRow(e)) l12 = (): MatrixRow => this.getExpensesRow('mortgage') l18 = (): MatrixRow => this.getExpensesRow('depreciation') // TODO - required from pub 596 worksheet 1 royaltyExpenses = (): number | undefined => undefined l20 = (): MatrixRow => fill(_.unzip(this.allExpenses()).map((column) => sumFields(column))) l21 = (): MatrixRow => _.zipWith( this.l3(), this.l4(), this.l20(), (x, y, z) => (x ?? 0) + (y ?? 0) - (z ?? 0) ) as MatrixRow // Deductible real estate loss from 8582, as positive number l22 = (): MatrixRow => this.f1040.f8582?.deductibleRealEstateLossAfterLimitation() ?? [ undefined, undefined, undefined ] l23a = (): number => sumFields(this.l3()) l23b = (): number => sumFields(this.l4()) l23c = (): number => sumFields(this.l12()) l23d = (): number => sumFields(this.l18()) l23e = (): number => sumFields(this.l20()) rentalNet = (): MatrixRow => _.zipWith(this.l3(), this.l20(), (x, y) => (x ?? 0) - (y ?? 0)) as MatrixRow l24 = (): number => sumFields(this.l21().filter((x) => x !== undefined && x > 0)) // TODO: Royalty losses l25 = (): number => sumFields(this.l22()) l26 = (): number => sumFields([this.l24(), this.l25()]) // TODO: required from Pub 596 l29ah = (): number | undefined => this.f1040.info.scheduleK1Form1065s.reduce( (t, k1) => t + Math.max(0, k1.isPassive ? k1.ordinaryBusinessIncome : 0), 0 ) l29ak = (): number | undefined => this.f1040.info.scheduleK1Form1065s.reduce( (t, k1) => t + Math.max(0, k1.isPassive ? 0 : k1.ordinaryBusinessIncome), 0 ) l29bg = (): number | undefined => this.f1040.info.scheduleK1Form1065s.reduce( (t, k1) => t + Math.min(0, k1.isPassive ? k1.ordinaryBusinessIncome : 0), 0 ) l29bi = (): number | undefined => this.f1040.info.scheduleK1Form1065s.reduce( (t, k1) => t + Math.min(0, k1.isPassive ? 0 : k1.ordinaryBusinessIncome), 0 ) l29bj = (): number | undefined => undefined l30 = (): number | undefined => sumFields([this.l29ah(), this.l29ak()]) l31 = (): number | undefined => sumFields([this.l29bg(), this.l29bi(), this.l29bj()]) l32 = (): number | undefined => sumFields([this.l30(), this.l31()]) l34ad = (): number | undefined => undefined l34af = (): number | undefined => undefined l34bc = (): number | undefined => undefined l34be = (): number | undefined => undefined // TODO: Real estate trust income or loss l37 = (): number | undefined => undefined // TODO: REMICS income or loss l39 = (): number | undefined => undefined // TODO: Farm rental income or loss l40 = (): number | undefined => undefined l41 = (): number => sumFields([this.l26(), this.l32(), this.l37(), this.l39(), this.l40()]) fields = (): Field[] => { const [p0, p1, p2] = [0, 1, 2].map((i) => this.propForRow(i)) // TODO: Support more than 4 K1s const k1s = this.f1040.info.scheduleK1Form1065s const l28Fields: Field[] = [] l28Fields.push( ...k1s .slice(0, 4) .flatMap((k1) => [ k1.partnershipName, k1.partnerOrSCorp, k1.isForeign, k1.partnershipEin, false, false ]) ) l28Fields.push( ...Array(6 * Math.max(0, 4 - k1s.length)).fill(undefined) ) l28Fields.push( ...k1s.slice(0, 4).flatMap((k1) => { if (k1.isPassive) { if (k1.ordinaryBusinessIncome < 0) { return [k1.ordinaryBusinessIncome, 0, 0, 0, 0] } else { return [0, k1.ordinaryBusinessIncome, 0, 0, 0] } } else { if (k1.ordinaryBusinessIncome < 0) { return [0, 0, k1.ordinaryBusinessIncome, 0, 0] } else { return [0, 0, 0, 0, k1.ordinaryBusinessIncome] } } }) ) l28Fields.push( ...Array(5 * Math.max(0, 4 - k1s.length)).fill(undefined) ) return [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, false, false, false, false, ...[p0, p1, p2].map((p) => p === undefined ? undefined : this.addressString(p.address) ), p0 === undefined ? undefined : propTypeIndex[PropertyType[p0.propertyType]], p1 === undefined ? undefined : propTypeIndex[PropertyType[p1.propertyType]], p2 === undefined ? undefined : propTypeIndex[PropertyType[p2.propertyType]], p0?.rentalDays, p0?.personalUseDays, p0?.qualifiedJointVenture, p1?.rentalDays, p1?.personalUseDays, p1?.qualifiedJointVenture, p2?.rentalDays, p2?.personalUseDays, p2?.qualifiedJointVenture, [p0, p1, p2].find((p) => p?.propertyType === 'other') ?.otherPropertyType ?? undefined, ...this.l3(), ...this.l4(), ...this.allExpenses().flat(), ...this.l19().flat(), ...this.l20(), ...this.l21(), ...this.l22(), this.l23a(), this.l23b(), this.l23c(), this.l23d(), this.l23e(), this.l24(), Math.abs(this.l25()), displayNegPos(this.l26()), // Page 2 - TODO: Only part II implemented this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, ...[false, false], // l27 ...l28Fields, undefined, // grey this.l29ah(), undefined, // grey undefined, // grey this.l29ak(), this.l29bg(), undefined, // grey this.l29bi(), this.l29bj(), undefined, // grey this.l30(), this.l31(), this.l32(), // l32 ...Array(2 * 6).fill(undefined), // l33 undefined, this.l34ad(), undefined, this.l34af(), this.l34bc(), undefined, // grey this.l34be(), undefined, // grey undefined, // l35 undefined, // l36 this.l37(), // l37 ...Array(5).fill(undefined), // l38 this.l39(), // l39 this.l40(), // l40 this.l41(), // l41 undefined, undefined ] } } ================================================ FILE: src/forms/Y2021/irsForms/ScheduleEIC.ts ================================================ import F1040Attachment from './F1040Attachment' import { Dependent, FilingStatus } from 'ustaxes/core/data' import F1040 from './F1040' import { sumFields } from 'ustaxes/core/irsForms/util' import * as federal from '../data/federal' import F2555 from './F2555' import F4797 from './F4797' import F8814 from './F8814' import Pub596Worksheet1 from './worksheets/Pub596Worksheet1' import { FormTag } from 'ustaxes/core/irsForms/Form' import { evaluatePiecewise, Piecewise } from 'ustaxes/core/util' import _ from 'lodash' import { Field } from 'ustaxes/core/pdfFiller' type PrecludesEIC = (f: F) => boolean // TODO: check F2555 const checks2555: PrecludesEIC = (): boolean => false // TODO: check F4797 const checks4797: PrecludesEIC = (): boolean => false // TODO: check F8814 const checks8814: PrecludesEIC = (): boolean => false const checksPub596: PrecludesEIC = (f): boolean => f.precludesEIC() const precludesEIC = (p: PrecludesEIC) => (f: F | undefined): boolean => { if (f === undefined) { return false } return p(f) } export default class ScheduleEIC extends F1040Attachment { tag: FormTag = 'f1040sei' sequenceIndex = 43 pub596Worksheet1: Pub596Worksheet1 qualifyingStudentCutoffYear = 1996 qualifyingCutoffYear = 2001 investmentIncomeLimit = 3650 constructor(f1040: F1040) { super(f1040) this.pub596Worksheet1 = new Pub596Worksheet1(f1040) } isNeeded = (): boolean => this.allowed() // instructions step 1.1 passIncomeLimit = (): boolean => { const filingStatus = this.f1040.info.taxPayer.filingStatus const incomeLimits = federal.EIC.caps[filingStatus] if (incomeLimits !== undefined) { const limit = incomeLimits[ Math.min(this.qualifyingDependents().length, incomeLimits.length - 1) ] return this.f1040.l11() < limit } return false } // Step 1.2, todo, both spouses must have a SSN issued before 2020 due date // // TODO: ('Step 1.2 (valid SSNs) unchecked') and without work restriction and valid for eic purpos validSSNs = (): boolean => { return true } // Step 1.3 allowedFilingStatus = (): boolean => this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFS // Step 1.4 allowedFilling2555 = (): boolean => !precludesEIC(checks2555)(this.f1040.f2555) // // TODO: ('Step 1.5, Not checking non-resident alien') Step 1.5 nonResidentAli allowedNonresidentAlien = (): boolean => { return true } // step 2, question 1 investmentIncome = (): number => sumFields([ this.f1040.l2a(), this.f1040.l2b(), this.f1040.l3b(), Math.max(this.f1040.l7() ?? 0, 0) ]) passInvestmentIncomeLimit = (): boolean => this.investmentIncome() < federal.EIC.maxInvestmentIncome // Todo, step 2, question 3 f4797AllowsEIC = (): boolean => !precludesEIC(checks4797)(this.f1040.f4797) // Todo, instruction 2.4.1 filingScheduleE = (): boolean => this.f1040.scheduleE.isNeeded() // // TODO: ('Not checking personal property income') 2.4 passIncomeFromPersonalProperty = (): boolean => { return true } // 2.4.3 passForm8814 = (): boolean => !precludesEIC(checks8814)(this.f1040.f8814) // // TODO: ('Not checking passive activity') 2.4 incomeOrLossFromPassiveActivity = (): boolean => { return false } // 2.4.5 passPub596 = (): boolean => !precludesEIC(checksPub596)(this.pub596Worksheet1) // 3.1 atLeastOneChild = (): boolean => this.qualifyingDependents().length > 0 // 3.2, 4.4 jointReturn = (): boolean => this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ // // TODO: ('3.3: Not checking qualifying child of another') 3.3, 4 qualifyingChildOfAnother = (): boolean => { return false } // 4.1 - covered by income limit check // // TODO: ('4.2: Not checking taxpayer age') 4 over25Under65 = (): boolean => { return true } // // TODO: ('4.3: Not checking residency') 4 mainHomeInsideUsBothPeople = (): boolean => { return true } // 4.4 covered above // 4.5 covered above // 4.6 dependent of another dependentOfAnother = (): boolean => this.f1040.info.taxPayer.primaryPerson.isTaxpayerDependent || (this.f1040.info.taxPayer.spouse?.isTaxpayerDependent ?? false) // // TODO: ('5.1: Not checking church self-employment income') 5.1 - Filing schedule SE for chur filingSEChurchIncome = (): boolean => { return false } // // TODO: ('5.1.2: Not checking scholarship, grants') 5.1 taxableScholarshipIncome = (): number => { return 0 } // // TODO: ('5.1.3: Not checking prison income') 5.1 prisonIncome = (): number => { return 0 } // // TODO: ('5.1.4: Not checking pension income') 5.1 pensionPlanIncome = (): number => { return 0 } // // TODO: ('5.1.5: Not checking medicaid waiver') 5.1 medicaidWaiverPayment = (): number => { return 0 } // // TODO: ('5.1.8: Not checking nontaxable combat pay') 5.1 nontaxableCombatPay = (): number => { return 0 } // 5.1 - Earned income earnedIncome = (): number => { const l1 = this.f1040.l1() const l2 = this.taxableScholarshipIncome() const l3 = this.prisonIncome() const l4 = this.pensionPlanIncome() const l5 = this.medicaidWaiverPayment() const l6 = l2 + l3 + l4 + l5 const l7 = l1 - l6 const l8 = this.nontaxableCombatPay() const l9 = l7 + l8 return l9 } /** * The credit table in Publication 596 provides an * amount for each interval of $50, calculated from the * midpoint of the interval. * * @param income The earned income * @returns the earned income rounded to the nearest 25 */ roundIncome = (income: number): number => { if (income < 1) { return 0 } return Math.round(Math.round(income) / 50) * 50 + 25 } /** * Based on the earned income and filing status, calculate the * allowed EITC. * * For tax year 2020, IRS Rev. Proc. 2019-44 outlines the required * calculation for the EITC based on number of qualifying children * and filing status. * * https://www.irs.gov/pub/irs-drop/rp-19-44.pdf * * IRS publication 596 provides a table that can be used * to figure the EITC, and is the basis of online calculators published * by IRS. This table uses the formulas outlined in Rev Proc 2019-44 * but applies them to incomes lying in $50 intervals, with the midpoint * of those intervals used to calculate the credit for the entire window. * For example, if the taxpayer has an earned income of $5000, the amount * that is found in the table is calculated based on an income of $5025 and * comes out ahead. Conversely, someone with an earned income of $5049 finds * a credit in the table calculated off the same $5,025 and loses out. * * https://www.irs.gov/pub/irs-pdf/p596.pdf * * @param income The earned income * @returns */ calculateEICForIncome = (income: number): number => { const filingStatus = this.f1040.info.taxPayer.filingStatus const f: Piecewise[] | undefined = federal.EIC.formulas[filingStatus] if (f === undefined) { return 0 } return Math.max( 0, evaluatePiecewise( f[this.qualifyingDependents().length], this.roundIncome(income) ) ) } // // TODO: ('5.2: Not checking selfemployment') 5 selfEmployed = (): boolean => { return false } // 5.3 - covered by income check // 6.1 - We will figure the credit. // EIC worksheet A calculation credit = (): number => Math.min( this.calculateEICForIncome(this.earnedIncome()), this.calculateEICForIncome(this.f1040.l11()) ) allowed = (): boolean => { return ( // Step 1 this.passIncomeLimit() && this.validSSNs() && this.allowedFilingStatus() && this.allowedFilling2555() && this.allowedNonresidentAlien() && // Step 2 (this.passInvestmentIncomeLimit() || this.f4797AllowsEIC()) && (!( // Step 3 ( this.filingScheduleE() || !this.passIncomeFromPersonalProperty() || !this.passForm8814() || this.incomeOrLossFromPassiveActivity() ) ) || this.passPub596()) && !( // Step 4 ( this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFJ && this.dependentOfAnother() ) ) && this.credit() > 0 ) } qualifyingDependents = (): Dependent[] => this.f1040.info.taxPayer.dependents .filter( (d) => d.dateOfBirth.getFullYear() >= this.qualifyingCutoffYear || ((d.qualifyingInfo?.isStudent ?? false) && d.dateOfBirth.getFullYear() >= this.qualifyingStudentCutoffYear) ) .sort((d) => d.dateOfBirth.getFullYear()) .slice(0, 3) qualifyingDependentsFilled = (): Array => { const res = this.qualifyingDependents() return _.fill([...res], undefined, res.length, 3) } // EIC line 1 nameFields = (): Array => this.qualifyingDependentsFilled().map( (d) => `${d?.firstName ?? ''} ${d?.lastName ?? ''}` ) // EIC line 2 ssnFields = (): Array => this.qualifyingDependentsFilled().map((d) => d?.ssid) years = (): Array => this.qualifyingDependentsFilled().map((d) => d?.dateOfBirth.getFullYear()) // EIC line 3 birthYearFields = (): Array => this.years().flatMap((year) => { if (year !== undefined) { return String(year).split('') } return [undefined, undefined, undefined, undefined] }) // EIC line 4a: Not handling case of child older than taxpayer ageFields = (): Array => this.years().flatMap((year) => { if (year !== undefined) { const qualifies = year > 1996 return [qualifies, !qualifies] } return [undefined, undefined] }) // TODO: disability disabledFields = (): Array => this.years().flatMap((year) => { if (year === undefined || year < this.qualifyingCutoffYear) { return [undefined, undefined] } return [undefined, undefined] }) // Line 5 // TODO: Address eic relationships relationships = (): Array => this.qualifyingDependentsFilled().map((d) => d?.relationship) // Line 6 numberMonths = (): Array => this.qualifyingDependents().map((d) => d.qualifyingInfo?.numberOfMonths) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, ...this.nameFields(), // 6 ...this.ssnFields(), // 3 ...this.birthYearFields(), // 12 ...this.ageFields(), // 6 ...this.disabledFields(), // 6 ...this.relationships(), ...this.numberMonths() ] } ================================================ FILE: src/forms/Y2021/irsForms/ScheduleR.ts ================================================ import F1040Attachment from './F1040Attachment' import { Field } from 'ustaxes/core/pdfFiller' import { FormTag } from 'ustaxes/core/irsForms/Form' export default class ScheduleR extends F1040Attachment { tag: FormTag = 'f1040sr' sequenceIndex = 999 l22 = (): number | undefined => undefined fields = (): Field[] => [] } ================================================ FILE: src/forms/Y2021/irsForms/ScheduleSE.ts ================================================ import F1040Attachment from './F1040Attachment' import { FormTag } from 'ustaxes/core/irsForms/Form' import { sumFields } from 'ustaxes/core/irsForms/util' import { Field } from 'ustaxes/core/pdfFiller' export default class ScheduleSE extends F1040Attachment { tag: FormTag = 'f1040sse' sequenceIndex = 14 isNeeded = (): boolean => this.f1040.info.scheduleK1Form1065s .map( (k1) => k1.selfEmploymentEarningsA + k1.selfEmploymentEarningsB + k1.selfEmploymentEarningsC ) .reduce((a, b) => a + b, 0) > 0 postL4Field = (f: () => number | undefined): number | undefined => { if (this.l4c() < 400) { return undefined } return f() } l8aRelatedField = (f: () => number | undefined): number | undefined => { return this.postL4Field(() => { if ((this.l8a() ?? 0) >= 142800) { return undefined } return f() }) } l1a = (): number => { const schFL34 = 0 // TODO: Net farm profit or (loss) from Schedule F, line 34 const k1B14 = 0 // TODO: If a farm partnership return schFL34 + k1B14 } l1b = (): number => 0 l2 = (): number => { const schFL34 = 0 // TODO: Net farm profit or (loss) from Schedule F, line 34 const k1B14 = this.f1040.info.scheduleK1Form1065s.reduce( (c, k1) => c + k1.selfEmploymentEarningsA, 0 ) return schFL34 + k1B14 } l3 = (): number => sumFields([this.l1a(), this.l1b(), this.l2()]) l4a = (): number => { const l3 = this.l3() if (l3 > 0) { return l3 * 0.9235 } return l3 } l4b = (): number | undefined => undefined l4c = (): number => sumFields([this.l4a(), this.l4b()]) l5a = (): number | undefined => this.postL4Field(() => 0) l5b = (): number | undefined => this.postL4Field(() => { const l5a = this.l5a() if (l5a === undefined) { return undefined } return l5a * 0.9235 }) l6 = (): number | undefined => this.postL4Field((): number => sumFields([this.l4c(), this.l5b()])) l7 = (): number => 142800 l8a = (): number | undefined => this.postL4Field((): number => this.f1040.validW2s().reduce((c, w2) => c + w2.ssWages, 0) ) l8b = (): number | undefined => this.l8aRelatedField((): number | undefined => undefined) l8c = (): number | undefined => this.l8aRelatedField((): number | undefined => undefined) l8d = (): number | undefined => this.l8aRelatedField((): number => sumFields([this.l8a(), this.l8b(), this.l8c()]) ) l9 = (): number | undefined => this.l8aRelatedField((): number => Math.max(0, this.l7() - (this.l8d() ?? 0)) ) l10 = (): number | undefined => this.l8aRelatedField( (): number => Math.min(this.l6() ?? 0, this.l9() ?? 0) * 0.124 ) l11 = (): number | undefined => this.postL4Field((): number => (this.l6() ?? 0) * 0.029) l12 = (): number | undefined => this.postL4Field((): number => sumFields([this.l10(), this.l11()])) l13 = (): number | undefined => this.postL4Field((): number => (this.l12() ?? 0) * 0.5) fields = (): Field[] => [ this.f1040.namesString(), this.f1040.info.taxPayer.primaryPerson.ssid, false, // Minister this.l1a(), this.l1b(), this.l2(), this.l3(), this.l4a(), this.l4b(), this.l4c(), this.l5a(), this.l5b(), this.l6(), this.l7(), this.l8a(), this.l8b(), this.l8c(), this.l8d(), this.l9(), this.l10(), this.l11(), this.l12(), this.l13() ] } ================================================ FILE: src/forms/Y2021/irsForms/TaxTable.ts ================================================ import federalBrackets from '../data/federal' import { FilingStatus } from 'ustaxes/core/data' import _ from 'lodash' const computeTax = (brackets: (status: FilingStatus) => number[], rates: number[]) => (filingStatus: FilingStatus, income: number): number => _.chain([0, ...brackets(filingStatus)]) // Low end of each bracket .zipWith( [...brackets(filingStatus), undefined], // top end of each bracket rates.map((r) => r / 100), // rate for each bracket (low, high, rate) => { if (income < low) { // this bracket is above income, no tax here return 0 } else if (high === undefined) { // This is the top bracket return Math.max(0, income - low) * rate } else if (income >= high) { // Taxable income is above the top of this bracket // so add the max tax for this bracket return (high - low) * rate } // Otherwise max income is inside this bracket, // add the tax on the amount falling in this bracket if (income < 5) { return 0 } // If income is between $5 and $25, tax table computes rate at midpoint of $5 ranges if (income >= 5 && income < 25) { income = Math.floor(income) const over5 = income % 5 income += 2.5 - over5 } // If income is between $25 and $3,000, tax table computes rate at midpoint of $25 ranges else if (income >= 25 && income < 3000) { income = Math.floor(income) const over25 = income % 25 income += 12.5 - over25 } // If income is between $3,000 and $100,000, tax table computes rate at midpoint of $50 ranges else if (income >= 3000 && income < 100000) { income = Math.floor(income) const over50 = income % 50 income += 25 - over50 } return (income - low) * rate } ) .sum() .value() export const computeOrdinaryTax = computeTax( (status) => federalBrackets.ordinary.status[status].brackets, federalBrackets.ordinary.rates ) export const computeLongTermCapGainsTax = computeTax( (status) => federalBrackets.longTermCapGains.status[status].brackets, federalBrackets.longTermCapGains.rates ) ================================================ FILE: src/forms/Y2021/irsForms/index.ts ================================================ import { PDFDocument } from 'pdf-lib' import { create1040 } from '../irsForms/Main' import { Either, isLeft, isRight, right } from 'ustaxes/core/util' import log from 'ustaxes/core/log' import { combinePdfs, PDFDownloader } from 'ustaxes/core/pdfFiller/pdfHandler' import { Information, Asset } from 'ustaxes/core/data' import { insertFormDataToPdfs } from 'ustaxes/core/irsForms' import { F1040Error } from 'ustaxes/forms/errors' export { create1040 } export const create1040PDFs = (state: Information, assets: Asset[]) => async ( downloader: PDFDownloader ): Promise> => { if (state.taxPayer.primaryPerson !== undefined) { const f1040Result = create1040(state, assets) // Get data and pdf links applicable to the model state if (isLeft(f1040Result)) { return Promise.reject(f1040Result) } const [, forms] = f1040Result.right return right(await insertFormDataToPdfs(forms, downloader)) } log.error('Attempt to create pdf with no data, will be empty') return right([]) } export const create1040PDF = (state: Information, assets: Asset[]) => async ( downloader: PDFDownloader ): Promise> => { const pdfResult = await create1040PDFs(state, assets)(downloader) if (isRight(pdfResult)) { const pdf = await combinePdfs(pdfResult.right) const bytes = await pdf.save() return right(bytes) } else { return Promise.resolve(pdfResult) } } ================================================ FILE: src/forms/Y2021/irsForms/worksheets/Pub596Worksheet1.ts ================================================ import { EIC } from '../../data/federal' import { ifNegative, ifPositive } from 'ustaxes/core/util' import F1040 from '../../irsForms/F1040' import { sumFields } from 'ustaxes/core/irsForms/util' export default class Pub596Worksheet1 { f1040: F1040 constructor(f1040: F1040) { this.f1040 = f1040 } l1 = (): number | undefined => this.f1040.l2b() l2 = (): number | undefined => sumFields([this.f1040.l2a(), this.f1040.f8814?.l1b()]) l3 = (): number | undefined => this.f1040.l3b() l4 = (): number | undefined => { return this.f1040.schedule1.l8f() } l5 = (): number => ((this.f1040.l7() ?? 0) < 0 ? 0 : this.f1040.l7() ?? 0) l6 = (): number => { const l7 = this.f1040.f4797?.l7() const l9 = this.f1040.f4797?.l9() if (l7 !== undefined && l7 < 0) { return l9 ?? 0 } return l7 ?? 0 } l7 = (): number => { const diff = this.l5() - this.l6() return diff < 0 ? 0 : diff } l8 = (): number | undefined => sumFields([this.f1040.scheduleE.l23b(), this.f1040.schedule1.l8k()]) l9 = (): number | undefined => sumFields([ this.f1040.scheduleE.royaltyExpenses(), this.f1040.schedule1.l24b() ]) l10 = (): number => Math.max(0, (this.l9() ?? 0) - (this.l8() ?? 0)) l11 = (): number | undefined => sumFields([ ifPositive(this.f1040.scheduleE.l26()), this.f1040.scheduleE.l29ah(), this.f1040.scheduleE.l34ad(), this.f1040.scheduleE.l40() // todo: FPA form 4797 line 10 ]) l12 = (): number | undefined => sumFields( [ this.f1040.scheduleE.l26(), this.f1040.scheduleE.l29bg() ?? 0, this.f1040.scheduleE.l34bc() ?? 0, this.f1040.scheduleE.l40() ?? 0 // TODO: PAL Loss form 4797 ].map((x) => ifNegative(x)) ) l13 = (): number | undefined => ifPositive(sumFields([this.l11(), this.l12()])) l14 = (): number | undefined => sumFields([ this.l1(), this.l2(), this.l3(), this.l4(), this.l7(), this.l10(), this.l13() ]) l15 = (): boolean => (this.l14() ?? 0) > EIC.maxInvestmentIncome precludesEIC = (): boolean => this.l15() } ================================================ FILE: src/forms/Y2021/irsForms/worksheets/QualifyingDependents.ts ================================================ import F1040 from '../F1040' import { Dependent } from 'ustaxes/core/data' import * as federal from '../../data/federal' /** * As of TY2021, the Child Tax Credit worksheet * is no longer published. This just implements * the qualifying dependent logic. */ export default class QualifyingDependents { f1040: F1040 year = 2021 constructor(f1040: F1040) { this.f1040 = f1040 } qualifiesChild = (d: Dependent): boolean => this.year - d.dateOfBirth.getFullYear() < federal.QualifyingDependents.childMaxAge qualifiesOther = (d: Dependent): boolean => d.qualifyingInfo !== undefined && !this.qualifiesChild(d) && this.year - d.dateOfBirth.getFullYear() < (d.qualifyingInfo.isStudent ? federal.QualifyingDependents.qualifyingDependentMaxAge : federal.QualifyingDependents.qualifyingStudentMaxAge) qualifyingChildren = (): Dependent[] => this.f1040.info.taxPayer.dependents.filter((dep) => this.qualifiesChild(dep) ) } ================================================ FILE: src/forms/Y2021/irsForms/worksheets/SDQualifiedAndCapGains.ts ================================================ // Reference implementation for ltcg and cap gains worksheet import { WorksheetData } from 'ustaxes/components/SummaryData' import { FilingStatus } from 'ustaxes/core/data' import federalBrackets from '../../data/federal' import { computeOrdinaryTax } from '../../irsForms/TaxTable' import { Worksheet } from '../F1040Attachment' type Bracket = [number, number] type Cutoffs = { [key in FilingStatus]: Bracket } const cutoffAmounts: Cutoffs = { [FilingStatus.S]: [40400, 445850], [FilingStatus.MFJ]: [80800, 501600], [FilingStatus.MFS]: [40400, 250800], [FilingStatus.W]: [80800, 501600], [FilingStatus.HOH]: [54100, 473750] } export default class QualDivAndCGWorksheet extends Worksheet { // 1. Enter the amount from Form 1040 or 1040-SR, line 15. // However, if you are filing Form 2555(relating to foreign earned income), // enter the amount from line 3 of the Foreign Earned Income Tax Worksheet l1 = (): number => { if (this.f1040.f2555 !== undefined) { return this.f1040.f2555.l3() ?? 0 } return this.f1040.l15() } // 2. Enter the amount from Form 1040 or 1040-SR, line 3a* l2 = (): number => this.f1040.l3a() ?? 0 // 3. Are you filing Schedule D?* // Yes. Enter the smaller of line 15 or 16 of Schedule D. // If either line 15 or 16 is blank or a loss, enter - 0 -. 3. // No. Enter the amount from Form 1040 or 1040-SR, line 7. l3 = (): number => { if (this.f1040.scheduleD.isNeeded()) { return Math.min( Math.max(this.f1040.scheduleD.l15(), 0), Math.max(this.f1040.scheduleD.l16(), 0) ) } return this.f1040.l7() ?? 0 } // 4. Add lines 2 and 3: LTCG + QDIV l4 = (): number => this.l2() + this.l3() // 5. Subtract line 4 from line 1. If zero or less, enter -0- l5 = (): number => Math.max(this.l1() - this.l4(), 0) // 6. Enter: // $40,400 if single or married filing separately, // $80,800 if married filing jointly or qualifying widow(er), $54,100 if head of household. l6 = (): number => cutoffAmounts[this.f1040.info.taxPayer.filingStatus][0] // 7. Enter the smaller of line 1 or line 6 l7 = (): number => Math.min(this.l1(), this.l6()) // 8. Enter the smaller of line 5 or line 7 l8 = (): number => Math.min(this.l5(), this.l7()) // 9. Subtract line 8 from line 7. This amount is taxed at 0% l9 = (): number => this.l7() - this.l8() // 10. Enter the smaller of line 1 or line 4 l10 = (): number => Math.min(this.l1(), this.l4()) // 11. Enter the amount from line 9 l11 = (): number => this.l9() // 12. Subtract line 11 from line 10 l12 = (): number => this.l10() - this.l11() // 13. Enter: // $445,850 if single, $250,800 if married filing separately, $501,600 if married filing jointly or qualifying widow(er), $473,750 if head of household. // l13 = (): number => cutoffAmounts[this.f1040.info.taxPayer.filingStatus][1] // 14. Enter the smaller of line 1 or line 13 l14 = (): number => Math.min(this.l1(), this.l13()) // 15. Add lines 5 and 9 l15 = (): number => this.l5() + this.l9() // 16. Subtract line 15 from line 14. If zero or less, enter -0- l16 = (): number => Math.max(this.l14() - this.l15(), 0) // 17. Enter the smaller of line 12 or line 16 l17 = (): number => Math.min(this.l12(), this.l16()) // 18. Multiply line 17 by 15% (0.15) l18 = (): number => (this.l17() * federalBrackets.longTermCapGains.rates[1]) / 100 // 19. Add lines 9 and 17 l19 = (): number => this.l9() + this.l17() // 20. Subtract line 19 from line 10 l20 = (): number => this.l10() - this.l19() // 21. Multiply line 20 by 20% (0.20) l21 = (): number => (this.l20() * federalBrackets.longTermCapGains.rates[2]) / 100 // 22. Figure the tax on the amount on line 5. If the amount on line 5 is less than $100,000, use the Tax Table to figure the tax. If the amount on line 5 is $100,000 or more, use the Tax Computation Worksheet l22 = (): number => computeOrdinaryTax(this.f1040.info.taxPayer.filingStatus, this.l5()) // 23. Add lines 18, 21, and 22 l23 = (): number => this.l18() + this.l21() + this.l22() // 24. Figure the tax on the amount on line 1. If the amount on line 1 is less than $100,000, use the Tax Table to figure the tax. If the amount on line 1 is $100,000 or more, use the Tax Computation Worksheet l24 = (): number => computeOrdinaryTax(this.f1040.info.taxPayer.filingStatus, this.l1()) // 25. Tax on all taxable income. Enter the smaller of line 23 or 24. Also include this amount on the entry space on Form 1040 or 1040-SR, line 16. If you are filing Form 2555, don’t enter this amount on the entry space on Form 1040 or 1040-SR, line 16. Instead, enter it on line 4 of the Foreign Earned Income Tax Worksheet l25 = (): number => Math.min(this.l23(), this.l24()) tax = (): number => this.l25() getSummaryData = (): WorksheetData => { return { name: 'Qualified Dividends and Capital Gains Worksheet — Line 16', lines: [ { line: 1, value: this.l1() }, { line: 2, value: this.l2() }, { line: 3, value: this.l3() }, { line: 4, value: this.l4() }, { line: 5, value: this.l5() }, { line: 6, value: this.l6() }, { line: 7, value: this.l7() }, { line: 8, value: this.l8() }, { line: 9, value: this.l9() }, { line: 10, value: this.l10() }, { line: 11, value: this.l11() }, { line: 12, value: this.l12() }, { line: 13, value: this.l13() }, { line: 14, value: this.l14() }, { line: 15, value: this.l15() }, { line: 16, value: this.l16() }, { line: 17, value: this.l17() }, { line: 18, value: this.l18() }, { line: 19, value: this.l19() }, { line: 20, value: this.l20() }, { line: 21, value: this.l21() }, { line: 22, value: this.l22() }, { line: 23, value: this.l23() }, { line: 24, value: this.l24() }, { line: 25, value: this.l25() } ] } } } ================================================ FILE: src/forms/Y2021/irsForms/worksheets/SDRateGainWorksheet.ts ================================================ export default class SDRateGainWorksheet { l7 = (): number | undefined => undefined l10 = (): number | undefined => undefined l13 = (): number | undefined => undefined l14 = (): number | undefined => undefined l21 = (): number | undefined => undefined } ================================================ FILE: src/forms/Y2021/irsForms/worksheets/SDTaxWorksheet.ts ================================================ import { Worksheet } from '../F1040Attachment' export default class SDTaxWorksheet extends Worksheet { /** * Complete this worksheet only if line 18 or line 19 of Schedule D is more than zero * and lines 15 and 16 of Schedule D are gains or if you file Form 4952 and you have * an amount on line 4g, even if you don’t need to file Schedule D. Otherwise, * complete the Qualified Dividends and Capital Gain Tax Worksheet in the instructions * for Forms 1040 and 1040-SR, line 16 (or in the instructions for Form 1040-NR, line 16) * to figure your tax. Before completing this worksheet, complete Form 1040, 1040-SR, or * 1040-NR through line 15. */ isNeeded = (): boolean => { const sd = this.f1040.scheduleD const f4952 = this.f1040.f4952 const sdCondition = sd.isNeeded() && ((sd.l18() ?? 0) > 0 || (sd.l19() ?? 0) > 0) && sd.l15() > 0 && sd.l16() > 0 const f4952Condition = f4952 !== undefined && (f4952.l4g() ?? 0) > 0 return sdCondition || f4952Condition } // TODO - Required by 6251, // Might be refigured for AMT l10 = (): number | undefined => undefined // TODO - Required by 6251, l13 = (): number | undefined => undefined // TODO - Required by 6251, l14 = (): number | undefined => undefined // TODO - Required by 6251, l21 = (): number | undefined => undefined } ================================================ FILE: src/forms/Y2021/irsForms/worksheets/SDUnrecaptured1250.ts ================================================ export default class SDUnrecaptured1250 { l18 = (): number | undefined => undefined } ================================================ FILE: src/forms/Y2021/irsForms/worksheets/ScheduleDTaxWorksheet.ts ================================================ // Reference implementation for Schedule D Tax Worksheet import { FilingStatus } from 'ustaxes/core/data' import { computeOrdinaryTax } from '../../irsForms/TaxTable' export interface TestData { qualDiv: number taxableIncome: number f4952l4g: number f4952l4e: number sdl15: number sdl16: number sdl18: number sdl19: number filingStatus: FilingStatus } type Bracket = [number, number, number] type Cutoffs = { [key in FilingStatus]: Bracket } const cutoffAmounts: Cutoffs = { [FilingStatus.S]: [40000, 163300, 441450], [FilingStatus.MFJ]: [80000, 326600, 496600], [FilingStatus.MFS]: [40000, 163300, 441450], [FilingStatus.W]: [80000, 326600, 496600], [FilingStatus.HOH]: [53600, 163300, 469050] } export default class LTCGQualDivReference { [k: string]: TestData | (() => number) data: TestData constructor(data: TestData) { this.data = data } // 1. Enter your taxable income from Form 1040, 1040-SR, or 1040-NR, line 15. (However, if you are filing Form 2555 (relating to foreign earned income), enter instead the amount from line 3 of the Foreign Earned Income Tax Worksheet in the instructions for Forms 1040 and 1040-SR, line 16) l1 = (): number => this.data.taxableIncome // 2. Enter your qualified dividends from Form 1040, 1040-SR, or 1040-NR, line 3a l2 = (): number => this.data.qualDiv // 3. Enter the amount from Form 4952 (used to figure investment interest expense deduction), line 4g l3 = (): number => this.data.f4952l4g // 4. Enter the amount from Form 4952, line 4e* l4 = (): number => this.data.f4952l4e // 5. Subtract line 4 from line 3. If zero or less, enter -0- l5 = (): number => Math.max(0, this.l3() - this.l4()) // 6. Subtract line 5 from line 2. If zero or less, enter -0-** l6 = (): number => Math.max(0, this.l2() - this.l5()) // 7. Enter the smaller of line 15 or line 16 of Schedule D l7 = (): number => Math.min(this.data.sdl15, this.data.sdl16) // 8. Enter the smaller of line 3 or line 4 l8 = (): number => Math.min(this.l3(), this.l4()) // 9. Subtract line 8 from line 7. If zero or less, enter -0-** l9 = (): number => Math.max(0, this.l7() - this.l8()) // 10. Add lines 6 and 9 l10 = (): number => this.l6() + this.l9() // 11. Add lines 18 and 19 of Schedule D** l11 = (): number => this.data.sdl18 + this.data.sdl19 // 12. Enter the smaller of line 9 or line 11 l12 = (): number => Math.min(this.l9(), this.l11()) // 13. Subtract line 12 from line 10 l13 = (): number => this.l10() - this.l12() // 14. Subtract line 13 from line 1. If zero or less, enter -0- l14 = (): number => Math.max(0, this.l1() - this.l13()) // 15. Enter: l15 = (): number => cutoffAmounts[this.data.filingStatus][0] // 16. Enter the smaller of line 1 or line 15 l16 = (): number => Math.min(this.l1(), this.l15()) // 17. Enter the smaller of line 14 or line 16 l17 = (): number => Math.min(this.l14(), this.l16()) // 18. Subtract line 10 from line 1. If zero or less, enter -0- l18 = (): number => Math.max(0, this.l1() - this.l10()) // 19. Enter the smaller of line 1 or [ltcg bracket 2] l19 = (): number => Math.min(this.l1(), cutoffAmounts[this.data.filingStatus][1]) // 20. Enter the smaller of line 14 or line 19 l20 = (): number => Math.min(this.l14(), this.l19()) // 21. Enter the larger of line 18 or line 20 l21 = (): number => Math.max(this.l18(), this.l20()) // 22. Subtract line 17 from line 16. This amount is taxed at 0%. l22 = (): number => Math.max(0, this.l16() - this.l17()) // If lines 1 and 16 are the same, skip lines 23 through 43 and go to line 44. Otherwise, go to line 23. // 23. Enter the smaller of line 1 or line 13 l23 = (): number => Math.min(this.l1(), this.l13()) // 24. Enter the amount from line 22. (If line 22 is blank, enter -0-.) l24 = (): number => this.l22() // 25. Subtract line 24 from line 23. If zero or less, enter -0- l25 = (): number => Math.max(0, this.l23() - this.l24()) // 26. Enter top bracket amount l26 = (): number => cutoffAmounts[this.data.filingStatus][1] // 27. Enter the smaller of line 1 or line 26 l27 = (): number => Math.min(this.l1(), this.l26()) // 28. Add lines 21 and 22 l28 = (): number => this.l21() + this.l22() // 29. Subtract line 28 from line 27. If zero or less, enter -0- l29 = (): number => Math.max(0, this.l27() - this.l28()) // 30. Enter the smaller of line 25 or line 29 l30 = (): number => Math.min(this.l25(), this.l29()) // 31. Multiply line 30 by 15% (0.15) l31 = (): number => this.l30() * 0.15 // 32. Add lines 24 and 30 l32 = (): number => this.l24() + this.l30() // If lines 1 and 32 are the same, skip lines 33 through 43 and go to line 44. Otherwise, go to line 33. // 33. Subtract line 32 from line 23 l33 = (): number => Math.max(0, this.l23() - this.l32()) // 34. Multiply line 33 by 20% (0.20) l34 = (): number => this.l33() * 0.2 // If Schedule D, line 19, is zero or blank, skip lines 35 through 40 and go to line 41. Otherwise, go to line 35. // 35. Enter the smaller of line 9 above or Schedule D, line 19 l35 = (): number => Math.min(this.l9(), this.data.sdl19) // 36. Add lines 10 and 21 l36 = (): number => this.l10() + this.l21() // 37. Enter the amount from line 1 above l37 = (): number => this.l1() // 38. Subtract line 37 from line 36. If zero or less, enter -0- l38 = (): number => Math.max(0, this.l36() - this.l37()) // 39. Subtract line 38 from line 35. If zero or less, enter -0- l39 = (): number => Math.max(0, this.l35() - this.l38()) // 40. Multiply line 39 by 25% (0.25) l40 = (): number => this.l39() * 0.25 // If Schedule D, line 18, is zero or blank, skip lines 41 through 43 and go to line 44. Otherwise, go to line 41. // 41. Add lines 21, 22, 30, 33, and 39 l41 = (): number => this.l21() + this.l22() + this.l30() + this.l33() + this.l39() // 42. Subtract line 41 from line 1 l42 = (): number => Math.max(0, this.l1() - this.l41()) // 43. Multiply line 42 by 28% (0.28) l43 = (): number => this.l42() * 0.28 // 44. Figure the tax on the amount on line 21. If the amount on line 21 is less than $100,000, use the Tax Table to l44 = (): number => computeOrdinaryTax(this.data.filingStatus, this.l21()) // 45. Add lines 31, 34, 40, 43, and 44 l45 = (): number => this.l31() + this.l34() + this.l40() + this.l43() + this.l44() // 46. Figure the tax on the amount on line 1. If the amount on line 1 is less than $100,000, use the Tax Table to l46 = (): number => computeOrdinaryTax(this.data.filingStatus, this.l1()) // figure the tax. If the amount on line 1 is $100,000 or more, use the Tax Computation Worksheet // 47. Tax on all taxable income (including capital gains and qualified dividends). Enter the smaller of line 45 // or line 46. Also, include this amount on Form 1040, 1040-SR, or 1040-NR, line 16. (If you are filing Form // 2555, don't enter this amount on Form 1040 or 1040-SR, line 16. Instead, enter it on line 4 of the Foreign // Earned Income Tax Worksheet in the Instructions for Forms 1040 and 1040-SR) l47 = (): number => Math.round(Math.min(this.l45(), this.l46())) } export const showReference = (r: LTCGQualDivReference): string => Array.from(Array(47)) .map((_, i) => `l${i + 1}`) .map((x) => [x, (r[x] as () => number)()]) .map(([l, v]) => `${l}: ${v}`) .join('\n') ================================================ FILE: src/forms/Y2021/irsForms/worksheets/SocialSecurityBenefits.ts ================================================ import { FilingStatus } from 'ustaxes/core/data' import { sumFields } from 'ustaxes/core/irsForms/util' import { SSBenefits } from '../../data/federal' import { Worksheet } from '../F1040Attachment' export default class SocialSecurityBenefitsWorksheet extends Worksheet { totalNetBenefits = (): number => this.f1040 .f1099ssas() .map((f) => f.form.netBenefits) .reduce((l, r) => l + r, 0) /* Enter the total amount from box 5 of all your Forms SSA-1099 and RRB-1099. Also enter this amount on Form 1040 or 1040-SR, line 6a */ l1 = (): number => this.totalNetBenefits() // Multiply line 1 by 50% (0.50) l2 = (): number => this.l1() / 2 /* If you are not excluding unemployment compensation from income, combine the amounts from Form 1040 or 1040-SR, lines 1, 2b, 3b, 4b, 5b, 7, and 8. If you are excluding unemployment compensation from income, combine the amounts from Form 1040 or 1040-SR , lines 1, 2b, 3b, 4b, 5b, 7, Schedule 1, lines 1 through 7, and line 3 of the Unemployment Compensation Exclusion Worksheet */ l3 = (): number => sumFields([ this.f1040.l1(), this.f1040.l2b(), this.f1040.l3b(), this.f1040.l4b(), this.f1040.l5b(), this.f1040.l7(), this.f1040.l8() ]) // Enter the amount, if any, from Form 1040 or 1040-SR, line 2a l4 = (): number | undefined => this.f1040.l2a() // Combine lines 2, 3, and 4 l5 = (): number => sumFields([this.l2(), this.l3(), this.l4()]) /* Enter the total of the amounts from Form 1040 or 1040-SR, line 10b, Schedule 1, lines 10 through 19, plus any write-in adjustments you entered on the dotted line next to Schedule 1, line 22 */ l6 = (): number => sumFields([ this.f1040.l12b(), this.f1040.schedule1.l10(), this.f1040.schedule1.l11(), this.f1040.schedule1.l12(), this.f1040.schedule1.l13(), this.f1040.schedule1.l14(), this.f1040.schedule1.l15(), this.f1040.schedule1.l16(), this.f1040.schedule1.l17(), this.f1040.schedule1.l18(), this.f1040.schedule1.l19a(), this.f1040.schedule1.l20() ]) /* Line 7: Is the amount on line 6 less than the amount on line 5? If No, None of your social security benefits are taxable. Enter -0- on Form 1040 or 1040-SR, line 6b. If Yes, Subtract line 6 from line 5 */ l7 = (): number => { if (this.l6() < this.l5()) { return this.l5() - this.l6() } else { return 0 } } /* If you are: Married filing jointly, enter $32,000 Single, head of household, qualifying widow(er), or married filing separately and you lived apart from your spouse for all of the year, enter $25,000 Married filing separately and you lived with your spouse at any time in the year, skip lines 8 through 15; multiply line 7 by 85% (0.85) and enter the result on line 16. Then, go to line 17 */ l8 = (): number => { if (this.f1040.info.taxPayer.filingStatus == FilingStatus.MFS) { // treat Married filing separately specially due to the extra question below // and resulting logic in the worksheet if (this.f1040.info.questions.LIVE_APART_FROM_SPOUSE) { return SSBenefits.caps[this.f1040.info.taxPayer.filingStatus].l8 } else { // Note that this value won't be taken into account. Instead, // the line 16 function will also check for this and perform // the right math. return 0 } } else { return SSBenefits.caps[this.f1040.info.taxPayer.filingStatus].l8 } } /* Is the amount on line 8 less than the amount on line 7? If No, None of your social security benefits are taxable. Enter -0- on Form 1040 or 1040-SR, line 6b. If you are married filing separately and you lived apart from your spouse for all of 2020, be sure you entered "D" to the right of the word "benefits" on line 6a. If Yes, Subtract line 8 from line 7. */ l9 = (): number => { if (this.l8() < this.l7()) { return this.l7() - this.l8() } else { return 0 } } /* Enter: $12,000 if married filing jointly; $9,000 if single, head of household, qualifying widow(er), or married filing separately and you lived apart from your spouse for all of 2020 */ l10 = (): number => SSBenefits.caps[this.f1040.info.taxPayer.filingStatus].l10 // Subtract line 10 from line 9. If zero or less, enter -0- l11 = (): number => { const tmp = this.l9() - this.l10() if (tmp < 0) { return 0 } else { return tmp } } // Enter the smaller of line 9 or line 10 l12 = (): number => Math.min(this.l9(), this.l10()) // Enter one-half of line 12 l13 = (): number => this.l12() / 2 // Enter the smaller of line 2 or line 13 l14 = (): number => Math.min(this.l13(), this.l2()) // Multiply line 11 by 85% (0.85). If line 11 is zero, enter -0- l15 = (): number => { if (this.l11() == 0) { return 0 } else { return this.l11() * 0.85 } } // Add lines 14 and 15 l16 = (): number => { // From line 7 instructions: // Married filing separately and you lived with your spouse at any time // in 2020, skip lines 8 through 15; multiply line 7 by 85% (0.85) and // enter the result on line 16. Then, go to line 17 if ( this.f1040.info.taxPayer.filingStatus == FilingStatus.MFS && !this.f1040.info.questions.LIVE_APART_FROM_SPOUSE ) { return this.l7() * 0.85 } else { return sumFields([this.l14(), this.l15()]) } } // Multiply line 1 by 85% (0.85) l17 = (): number => this.l1() * 0.85 // Taxable social security benefits. Enter the smaller of line 16 or line 17. // Also enter this amount on Form 1040 or 1040-SR, line 6b l18 = (): number => Math.min(this.l16(), this.l17()) // This is the function used to return the taxable amount of the social security // benefits to be entered in line 6b of 1040. It takes into account the various // stopping points in the worksheet. taxableAmount = (): number => { const line7 = this.l7() if (line7 == 0) { return line7 } const line9 = this.l9() if (line9 == 0) { return line9 } return this.l18() } } ================================================ FILE: src/forms/Y2021/irsForms/worksheets/StudentLoanInterestWorksheet.ts ================================================ import { F1098e, FilingStatus } from 'ustaxes/core/data' import F1040 from '../../irsForms/F1040' import { sumFields } from 'ustaxes/core/irsForms/util' export default class StudentLoanInterestWorksheet { f1040: F1040 f1098es: F1098e[] constructor(f1040: F1040, f1098es: F1098e[]) { this.f1040 = f1040 this.f1098es = f1098es } // Can't take deduction if filling Married Filling Seperate notMFS = (): boolean => this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFS // Can't take deduction if MFJ and spouse is a dependent isNotDependentSpouse = (): boolean => this.f1040.info.taxPayer.filingStatus !== FilingStatus.MFJ || this.f1040.info.taxPayer.spouse === undefined || !this.f1040.info.taxPayer.spouse.isTaxpayerDependent // Can't take deduction if someone else claims you as a dependent isNotDependentSelf = (): boolean => !this.f1040.info.taxPayer.primaryPerson.isTaxpayerDependent isNotDependent = (): boolean => this.isNotDependentSpouse() && this.isNotDependentSelf() // Sum interest, but maximum of 2500 can be deducted l1 = (): number => Math.min( this.f1098es.map((f1098e) => f1098e.interest).reduce((l, r) => l + r, 0), 2500 ) // Currently do not support unemployment compensation exclusion // TO DO: add unemployment compensation exclusion l2 = (): number => this.f1040.l9() // Schedule 1 deductions l3 = (): number => sumFields([ this.f1040.l12b(), this.f1040.schedule1.l11(), this.f1040.schedule1.l12(), this.f1040.schedule1.l13(), this.f1040.schedule1.l14(), this.f1040.schedule1.l15(), this.f1040.schedule1.l16(), this.f1040.schedule1.l17(), this.f1040.schedule1.l18(), this.f1040.schedule1.l19a(), this.f1040.schedule1.l20() // TODO: missing write-in deduction ? ]) l4 = (): number => Math.max(0, this.l2() - this.l3()) l5 = (): number => this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ ? 140000 : 70000 l6 = (): number => Math.max(0, this.l4() - this.l5()) l7 = (): number => Math.min(this.l6() / 15000, 1) l8 = (): number => this.l1() * this.l7() l9 = (): number | undefined => this.notMFS() && this.isNotDependent() ? Math.max(0, this.l1() - this.l8()) : undefined } ================================================ FILE: src/forms/Y2021/stateForms/AK/Form.ts ================================================ export default class AKForm {} ================================================ FILE: src/forms/Y2021/stateForms/AL/Form.ts ================================================ export default class ALForm {} ================================================ FILE: src/forms/Y2021/stateForms/AR/Form.ts ================================================ export default class ARForm {} ================================================ FILE: src/forms/Y2021/stateForms/AZ/Form.ts ================================================ export default class AZForm {} ================================================ FILE: src/forms/Y2021/stateForms/CA/Form.ts ================================================ export default class CAForm {} ================================================ FILE: src/forms/Y2021/stateForms/CO/Form.ts ================================================ export default class COForm {} ================================================ FILE: src/forms/Y2021/stateForms/CT/Form.ts ================================================ export default class CTForm {} ================================================ FILE: src/forms/Y2021/stateForms/DC/Form.ts ================================================ export default class DCForm {} ================================================ FILE: src/forms/Y2021/stateForms/DE/Form.ts ================================================ export default class DEForm {} ================================================ FILE: src/forms/Y2021/stateForms/FL/Form.ts ================================================ export default class FLForm {} ================================================ FILE: src/forms/Y2021/stateForms/GA/Form.ts ================================================ export default class GAForm {} ================================================ FILE: src/forms/Y2021/stateForms/HI/Form.ts ================================================ export default class HIForm {} ================================================ FILE: src/forms/Y2021/stateForms/IA/Form.ts ================================================ export default class IAForm {} ================================================ FILE: src/forms/Y2021/stateForms/ID/Form.ts ================================================ export default class IDForm {} ================================================ FILE: src/forms/Y2021/stateForms/IL/IL1040.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field, RadioSelect } from 'ustaxes/core/pdfFiller' import { sumFields } from 'ustaxes/core/irsForms/util' import { AccountType, FilingStatus, State } from 'ustaxes/core/data' import parameters from './Parameters' import { IL1040scheduleileeic } from './IL1040ScheduleILEIC' import IL1040V from './IL1040V' import { ILWIT } from './ILWit' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class IL1040 extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State scheduleEIC: IL1040scheduleileeic il1040V: IL1040V formOrder = 0 methods: FormMethods constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'IL-1040' this.state = 'IL' this.scheduleEIC = new IL1040scheduleileeic(f1040) this.il1040V = new IL1040V(f1040, this) this.methods = new FormMethods(this) } attachments = (): Form[] => { const pmt = this.payment() const result: Form[] = [] if ((pmt ?? 0) > 0) { result.push(this.il1040V) } if (this.scheduleEIC.isRequired()) { result.push(this.scheduleEIC) } if (this.methods.stateWithholding() > 0) { const ilwit = new ILWIT(this.f1040) result.push(ilwit) ilwit.attachments().forEach((f) => result.push(f)) } return result } /** * Index 0: Help */ Help = (): string | undefined => { return undefined } f0 = (): string | undefined => this.Help() /** * Index 1: month */ month = (): string | undefined => { return undefined } f1 = (): string | undefined => this.month() /** * Index 2: year */ year = (): string | undefined => { return undefined } f2 = (): string | undefined => this.year() /** * Index 3: name2 * Primary First Name */ name2 = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.firstName f3 = (): string | undefined => this.name2() /** * Index 4: YoB */ YoB = (): string | undefined => { return undefined } f4 = (): string | undefined => this.YoB() /** * Index 5: ssn1 */ ssn1 = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.ssid f5 = (): string | undefined => this.ssn1() /** * Primary last name? * Index 6: name3 */ name3 = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.lastName f6 = (): string | undefined => this.name3() /** * Spouse Fist name? * Index 7: name4 */ name4 = (): string | undefined => this.f1040.info.taxPayer.spouse?.firstName f7 = (): string | undefined => this.name4() /** * Index 8: SpYoB */ SpYoB = (): string | undefined => undefined f8 = (): string | undefined => this.SpYoB() /** * Spouse SSN * Index 9: ssn4 */ ssn4 = (): string | undefined => this.f1040.info.taxPayer.spouse?.ssid f9 = (): string | undefined => this.ssn4() /** * Index 10: address */ address = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.address.address f10 = (): string | undefined => this.address() /** * Index 11: apt */ apt = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.address.aptNo f11 = (): string | undefined => this.apt() /** * Index 12: County */ County = (): string | undefined => undefined f12 = (): string | undefined => this.County() /** * Index 13: city */ city = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.address.city f13 = (): string | undefined => this.city() /** * Index 14: st */ st = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.address.state ?? this.f1040.info.taxPayer.primaryPerson.address.province f14 = (): string | undefined => this.st() /** * Index 15: zip */ zip = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.address.zip f15 = (): string | undefined => this.zip() /** * Index 16: foreign */ foreign = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.address.foreignCountry f16 = (): string | undefined => this.foreign() /** * Index 17: Check Box1 * This is actually a radio group, so indicate the correct selection * by index. */ CheckBox1 = (): RadioSelect | undefined => ({ select: [ FilingStatus.S, FilingStatus.MFJ, FilingStatus.MFS, FilingStatus.W, FilingStatus.HOH ].findIndex((x) => x === this.f1040.info.taxPayer.filingStatus) }) f17 = (): RadioSelect | undefined => this.CheckBox1() /** * Index 18: Check Box1c */ CheckBox1c = (): boolean | undefined => this.f1040.info.taxPayer.primaryPerson.isTaxpayerDependent f18 = (): boolean | undefined => this.CheckBox1c() /** * Index 19: Check Box1cc */ CheckBox1cc = (): boolean | undefined => this.f1040.info.taxPayer.spouse?.isTaxpayerDependent f19 = (): boolean | undefined => this.CheckBox1cc() /** * Index 20: Check Box7 * TODO - nonresident, part year */ CheckBox7 = (): boolean | undefined => { return undefined } f20 = (): boolean | undefined => this.CheckBox7() /** * Spouse last name ! * Index 21: name1 */ name1 = (): string | undefined => this.f1040.info.taxPayer.spouse?.lastName f21 = (): string | undefined => this.name1() /** * Index 22: emailadd */ emailadd = (): string | undefined => this.f1040.info.taxPayer.contactEmail f22 = (): string | undefined => this.emailadd() /** * Index 23: 1 */ l1 = (): number | undefined => this.f1040.l11() /** * Index 24: 2 */ l2 = (): number | undefined => this.f1040.l2a() /** * Index 25: 3 * TODO: Schedule M, other additions */ l3 = (): number | undefined => undefined /** * Index 26: 4 */ l4 = (): number => sumFields([this.l1(), this.l2(), this.l3()]) /** * Index 27: 5 * TODO - ss benefits and certain retirement plan income received if included in line 1, attach p1 of federal return */ l5 = (): number | undefined => undefined /** * Index 28: 6 * TODO IL income tax overpayment included in federal form 1040 S1 L1 */ l6 = (): number | undefined => undefined /** * Index 29: 7 * TODO: other subtractions, attach Schedule M */ l7 = (): number | undefined => undefined /** * Index 30: Check Box2 * Check if L7 includes any amount from Schedule 1299-C */ CheckBox2 = (): boolean | undefined => undefined f30 = (): boolean | undefined => this.CheckBox2() /** * Index 31: 8 */ l8 = (): number => sumFields([this.l5(), this.l6(), this.l7()]) /** * Index 32: 9 */ l9 = (): number => Math.max(0, this.l4() - this.l8()) /** * Index 33: 10a */ l10a = (): number => { if (this.f1040.info.taxPayer.filingStatus === FilingStatus.MFJ) return parameters.exemptions.MFJ.exemptionAmount return parameters.exemptions.S.exemptionAmount } /** * Index 34: Check Box3 * Check if TP senior */ primarySenior = (): boolean | undefined => undefined f34 = (): boolean | undefined => this.primarySenior() /** * Index 35: Check Box4 * Check if spouse senior */ spouseSenior = (): boolean | undefined => { return undefined } f35 = (): boolean | undefined => this.spouseSenior() /** * Index 36: 10b */ l10b = (): number => [this.primarySenior() ?? false, this.spouseSenior() ?? false].filter( (x) => x ).length * parameters.seniorExemption /** * Index 37: Check Box5 * TODO: TP blind */ primaryBlind = (): boolean | undefined => undefined f37 = (): boolean | undefined => this.primaryBlind() /** * Index 38: Check Box6 * TODO: Spouse blind */ spouseBlind = (): boolean | undefined => undefined f38 = (): boolean | undefined => this.spouseBlind() /** * Index 39: 10c */ l10c = (): number | undefined => [this.primaryBlind(), this.spouseBlind()].filter((x) => x).length * parameters.blindExemption /** * Index 40: 10d * TODO: Schedule EC step 2, line 1 */ l10d = (): number | undefined => undefined /** * Index 41: 10 * Net income */ l10 = (): number => sumFields([this.l10a(), this.l10b(), this.l10c(), this.l10d()]) /** * Index 42: 11 * TODO: handle non-residents, part year residents */ l11 = (): number => Math.max(0, this.l9() - this.l10()) /** * Index 43: 12 */ l12 = (): number => this.l11() * parameters.taxRate /** * Index 44: 13 * TODO: recapture investment tax credits, schedule 4255 */ l13 = (): number | undefined => undefined /** * Index 45: 14 * Income tax */ l14 = (): number => sumFields([this.l12(), this.l13()]) /** * Index 46: 15 * TODO: income tax paid to another state while IL resident */ l15 = (): number | undefined => undefined /** * Index 47: 16 * Property tax and K12 education expense credit amount from Schedule ICR */ l16 = (): number | undefined => undefined /** * Index 48: 17 * TODO: Credit amount from Schedule 1299-C Attach 1299-C */ l17 = (): number | undefined => undefined /** * Index 49: 18 * Total credits */ l18 = (): number => sumFields([this.l15(), this.l16(), this.l17()]) /** * Index 50: 19 */ l19 = (): number => Math.max(0, this.l14() - this.l18()) /** * Index 51: 20 * TODO: Household employment tax */ l20 = (): number | undefined => undefined /** * Index 52: 21 * TODO: Use tax on internet, mail order, or other out-of-state purchases */ l21 = (): number | undefined => undefined /** * Index 53: 22 * TODO: Compassionate use of medical cannabis program act and sale of assets by gaming licensee */ l22 = (): number | undefined => undefined /** * Index 54: 23 */ l23 = (): number => sumFields([this.l19(), this.l20(), this.l21(), this.l22()]) /** * Index 55: 24 */ l24 = (): number => this.l23() /** * Index 56: 25 */ l25 = (): number | undefined => this.methods.witholdingForState('IL') /** * Index 57: 26 * TODO: Estimated tax payments */ l26 = (): number | undefined => undefined /** * Index 58: 27 * Pass-through withholding */ l27 = (): number | undefined => undefined // After line 27, PDF form skips to Step 11 (after line 35) /** * Index 59: Check Box8a * TODO: 2/3 of income from farming */ CheckBox8a = (): boolean | undefined => undefined f59 = (): boolean | undefined => this.CheckBox8a() /** * Index 60: Check Box8b ?? what * TODO: You or spouse is 65 or older and permanently in nursing home */ CheckBox8b = (): boolean | undefined => undefined f60 = (): boolean | undefined => this.CheckBox8b() /** * Index 61: Check Box8c ?? what * TODO: Income not received evenly during the year and you annualized your income on IL-2210 */ CheckBox8c = (): boolean | undefined => undefined f61 = (): boolean | undefined => this.CheckBox8c() /** * Index 62: rn1 */ rn1 = (): string | undefined => this.f1040.info.refund?.routingNumber f62 = (): string | undefined => this.rn1() /** * Index 63: Check Box11 * TODO: support savings account checkbox - radio field issue */ CheckBox11 = (): boolean | undefined => this.f1040.info.refund?.accountType === AccountType.checking f63 = (): boolean | undefined => this.CheckBox11() /** * Index 64: ac1 */ ac1 = (): string | undefined => this.f1040.info.refund?.accountNumber f64 = (): string | undefined => this.ac1() /** * Index 65: Refund Method * TODO: not supporting credit forward */ RefundMethod = (): boolean => true f65 = (): boolean | undefined => this.RefundMethod() /** * Index 66: YourSignatureDate */ YourSignatureDate = (): string | undefined => undefined f66 = (): string | undefined => this.YourSignatureDate() /** * Index 67: SpouseSignatureDate */ SpouseSignatureDate = (): string | undefined => undefined f67 = (): string | undefined => this.SpouseSignatureDate() /** * Index 68: DaytimeAreaCode */ DaytimeAreaCode = (): string | undefined => this.f1040.info.taxPayer.contactPhoneNumber?.slice(0, 3) f68 = (): string | undefined => this.DaytimeAreaCode() /** * Index 69: DaytimePhoneNumber */ DaytimePhoneNumber = (): string | undefined => this.f1040.info.taxPayer.contactPhoneNumber?.slice(3) f69 = (): string | undefined => this.DaytimePhoneNumber() /** * Index 70: PreparerName */ PreparerName = (): string | undefined => undefined f70 = (): string | undefined => this.PreparerName() /** * Index 71: PreparerSignatureDate */ PreparerSignatureDate = (): string | undefined => undefined f71 = (): string | undefined => this.PreparerSignatureDate() /** * Index 72: CheckBoxSelfEmployed */ CheckBoxSelfEmployed = (): undefined => undefined f72 = (): boolean | undefined => this.CheckBoxSelfEmployed() /** * Index 73: PreparerPTIN */ PreparerPTIN = (): string | undefined => undefined f73 = (): string | undefined => this.PreparerPTIN() /** * Index 74: PreparerFirmName */ PreparerFirmName = (): string | undefined => undefined f74 = (): string | undefined => this.PreparerFirmName() /** * Index 75: PreparerFirmFEIN */ PreparerFirmFEIN = (): string | undefined => undefined f75 = (): string | undefined => this.PreparerFirmFEIN() /** * Index 76: PreparerFirmAddress */ PreparerFirmAddress = (): string | undefined => undefined f76 = (): string | undefined => this.PreparerFirmAddress() /** * Index 77: PreparerFirmAreaCode */ PreparerFirmAreaCode = (): string | undefined => undefined f77 = (): string | undefined => this.PreparerFirmAreaCode() /** * Index 78: PreparerFirmPhoneNumber */ PreparerFirmPhoneNumber = (): string | undefined => undefined f78 = (): string | undefined => this.PreparerFirmPhoneNumber() /** * Index 79: ThirdPartyName */ ThirdPartyName = (): string | undefined => undefined f79 = (): string | undefined => this.ThirdPartyName() /** * Index 80: ThirdPartyAreaCode */ ThirdPartyAreaCode = (): string | undefined => undefined f80 = (): string | undefined => this.ThirdPartyAreaCode() /** * Index 81: ThirdPartyPhoneNumber */ ThirdPartyPhoneNumber = (): string | undefined => undefined f81 = (): string | undefined => this.ThirdPartyPhoneNumber() /** * Index 82: CheckBoxDiscuss */ CheckBoxDiscuss = (): boolean | undefined => undefined f82 = (): boolean | undefined => this.CheckBoxDiscuss() /** * Index 83: Reset */ Reset = (): string | undefined => undefined f83 = (): string | undefined => this.Reset() /** * Index 84: Print */ Print = (): string | undefined => undefined f84 = (): string | undefined => this.Print() /** * Index 85: 28 * TODO: Pass-through entity tax credit, Schedule K-1-P / Schedule K-1-T */ l28 = (): number | undefined => undefined /** * Index 86: 29 * Earned income credit from Schedule IL-E/EIC, Step 4, line 8 */ l29 = (): number | undefined => this.scheduleEIC.earnedIncomeCredit() /** * Index 87: 31 */ l31 = (): number => Math.max(0, this.l30() - this.l24()) /** * Index 88: 30 */ l30 = (): number => sumFields([this.l25(), this.l26(), this.l27(), this.l28(), this.l29()]) /** * Index 89: 32 */ l32 = (): number => Math.max(0, this.l24() - this.l30()) /** * Index 90: 33 * TODO: late payment penalty for underpayment of estimated tax */ l33 = (): number | undefined => undefined /** * Index 91: 34 * TODO: Voluntary charitable contributions */ l34 = (): number | undefined => undefined /** * Index 92: 35 */ l35 = (): number => sumFields([this.l33(), this.l34()]) /** * Index 93: 36 * Overpayment */ l36 = (): number => Math.max(this.l31() - this.l35()) /** * Index 94: 37 * TODO: not supporting credit forward */ l37 = (): number => this.l36() /** * Index 95: 39 * TODO: not supporting credit forward */ l39 = (): number | undefined => undefined /** * Index 96: 40 */ l40 = (): number | undefined => { const l31 = this.l31() const l32 = this.l32() const l35 = this.l35() if (l32 > 0) return l32 + l35 if (l31 > 0) return Math.max(0, l35 - l31) } payment = (): number | undefined => this.l40() /** * This box is left over from line 33 * Index 97: CheckBox8d * TODO: Check if you were not required to file an IL individual tax return in the previous tax year. */ CheckBox8d = (): boolean | undefined => undefined f97 = (): boolean | undefined => this.CheckBox8d() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.l1(), this.l2(), this.l3(), this.l4(), this.l5(), this.l6(), this.l7(), this.f30(), this.l8(), this.l9(), this.l10a(), this.f34(), this.f35(), this.l10b(), this.f37(), this.f38(), this.l10c(), this.l10d(), this.l10(), this.l11(), this.l12(), this.l13(), this.l14(), this.l15(), this.l16(), this.l17(), this.l18(), this.l19(), this.l20(), this.l21(), this.l22(), this.l23(), this.l24(), this.l25(), this.l26(), this.l27(), this.f59(), this.f60(), this.f61(), this.f62(), this.f63(), this.f64(), this.f65(), this.f66(), this.f67(), this.f68(), this.f69(), this.f70(), this.f71(), this.f72(), this.f73(), this.f74(), this.f75(), this.f76(), this.f77(), this.f78(), this.f79(), this.f80(), this.f81(), this.f82(), this.f83(), this.f84(), this.l28(), this.l29(), this.l31(), this.l30(), this.l32(), this.l33(), this.l34(), this.l35(), this.l36(), this.l37(), this.l39(), this.l40(), this.f97() ] } const makeIL1040 = (f1040: F1040): IL1040 => new IL1040(f1040) export default makeIL1040 ================================================ FILE: src/forms/Y2021/stateForms/IL/IL1040ScheduleILEIC.ts ================================================ import Form from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { Dependent, PrimaryPerson, Spouse, State } from 'ustaxes/core/data' import parameters from './Parameters' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class IL1040scheduleileeic extends Form { f1040: F1040 info: ValidatedInformation formName: string state: State formOrder = 1 attachments: () => Form[] = () => [] qualifyingDependents: Dependent[] constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'il-1040-schedule-il-e-eic' this.state = 'IL' this.qualifyingDependents = this.f1040.scheduleEIC.qualifyingDependents() } get primary(): PrimaryPerson | undefined { return this.f1040.info.taxPayer.primaryPerson } get spouse(): Spouse | undefined { return this.f1040.info.taxPayer.spouse } isRequired = (): boolean => (this.earnedIncomeCredit() ?? 0) > 0 /** * Index 0: Help */ Help = (): string | undefined => { return undefined } f0 = (): string | undefined => this.Help() /** * Index 1: X 2325 */ X2325 = (): string | undefined => { return undefined } f1 = (): string | undefined => this.X2325() /** * Index 2: Reset */ Reset = (): string | undefined => { return undefined } f2 = (): string | undefined => this.Reset() /** * Index 3: Print */ Print = (): string | undefined => { return undefined } f3 = (): string | undefined => this.Print() /** * Index 4: Your name */ Yourname = (): string | undefined => [this.primary?.firstName, this.primary?.lastName].flat().join(' ') f4 = (): string | undefined => this.Yourname() /** * Index 5: Dependents first name - 2 */ Dependentsfirstname2 = (): string | undefined => this.f1040.info.taxPayer.dependents[1]?.firstName f5 = (): string | undefined => this.Dependentsfirstname2() /** * Index 6: Dependent's first name - 1 */ Dependentsfirstname1 = (): string | undefined => this.f1040.info.taxPayer.dependents[0]?.firstName f6 = (): string | undefined => this.Dependentsfirstname1() /** * Index 7: Dependent's first name - 3 */ Dependentsfirstname3 = (): string | undefined => this.f1040.info.taxPayer.dependents[2]?.firstName f7 = (): string | undefined => this.Dependentsfirstname3() /** * Index 8: Dependent's first name - 4 */ Dependentsfirstname4 = (): string | undefined => this.f1040.info.taxPayer.dependents[3]?.firstName f8 = (): string | undefined => this.Dependentsfirstname4() /** * Index 9: Dependent's first name - 5 */ Dependentsfirstname5 = (): string | undefined => this.f1040.info.taxPayer.dependents[4]?.firstName f9 = (): string | undefined => this.Dependentsfirstname5() /** * Index 10: Dependent's first name - 6 */ Dependentsfirstname6 = (): string | undefined => this.f1040.info.taxPayer.dependents[5]?.firstName f10 = (): string | undefined => this.Dependentsfirstname6() /** * Index 11: Dependent's first name - 7 */ Dependentsfirstname7 = (): string | undefined => this.f1040.info.taxPayer.dependents[6]?.firstName f11 = (): string | undefined => this.Dependentsfirstname7() /** * Index 12: Dependent's first name - 8 */ Dependentsfirstname8 = (): string | undefined => this.f1040.info.taxPayer.dependents[7]?.firstName f12 = (): string | undefined => this.Dependentsfirstname8() /** * Index 13: Dependent's first name - 9 */ Dependentsfirstname9 = (): string | undefined => this.f1040.info.taxPayer.dependents[8]?.firstName f13 = (): string | undefined => this.Dependentsfirstname9() /** * Index 14: Dependent's first name - 10 */ Dependentsfirstname10 = (): string | undefined => this.f1040.info.taxPayer.dependents[9]?.firstName f14 = (): string | undefined => this.Dependentsfirstname10() /** * Index 15: Dependent's last name - 2 */ Dependentslastname2 = (): string | undefined => this.f1040.info.taxPayer.dependents[1]?.lastName f15 = (): string | undefined => this.Dependentslastname2() /** * Index 16: Dependent's last name - 1 */ Dependentslastname1 = (): string | undefined => this.f1040.info.taxPayer.dependents[0]?.lastName f16 = (): string | undefined => this.Dependentslastname1() /** * Index 17: Dependent's last name - 3 */ Dependentslastname3 = (): string | undefined => this.f1040.info.taxPayer.dependents[2]?.lastName f17 = (): string | undefined => this.Dependentslastname3() /** * Index 18: Dependent's last name - 4 */ Dependentslastname4 = (): string | undefined => this.f1040.info.taxPayer.dependents[3]?.lastName f18 = (): string | undefined => this.Dependentslastname4() /** * Index 19: Dependent's last name - 5 */ Dependentslastname5 = (): string | undefined => this.f1040.info.taxPayer.dependents[4]?.lastName f19 = (): string | undefined => this.Dependentslastname5() /** * Index 20: Dependent's last name - 6 */ Dependentslastname6 = (): string | undefined => this.f1040.info.taxPayer.dependents[5]?.lastName f20 = (): string | undefined => this.Dependentslastname6() /** * Index 21: Dependent's last name - 7 */ Dependentslastname7 = (): string | undefined => this.f1040.info.taxPayer.dependents[6]?.lastName f21 = (): string | undefined => this.Dependentslastname7() /** * Index 22: Dependent's last name - 8 */ Dependentslastname8 = (): string | undefined => this.f1040.info.taxPayer.dependents[7]?.lastName f22 = (): string | undefined => this.Dependentslastname8() /** * Index 23: Dependent's last name - 9 */ Dependentslastname9 = (): string | undefined => this.f1040.info.taxPayer.dependents[8]?.lastName f23 = (): string | undefined => this.Dependentslastname9() /** * Index 24: Dependent's last name - 10 */ Dependentslastname10 = (): string | undefined => this.f1040.info.taxPayer.dependents[9]?.lastName f24 = (): string | undefined => this.Dependentslastname10() /** * Index 25: Social Security number - 2 */ SocialSecuritynumber2 = (): string | undefined => this.f1040.info.taxPayer.dependents[1]?.ssid f25 = (): string | undefined => this.SocialSecuritynumber2() /** * Index 26: Social Security number - 3 */ SocialSecuritynumber3 = (): string | undefined => this.f1040.info.taxPayer.dependents[2]?.ssid f26 = (): string | undefined => this.SocialSecuritynumber3() /** * Index 27: Social Security number - 4 */ SocialSecuritynumber4 = (): string | undefined => this.f1040.info.taxPayer.dependents[3]?.ssid f27 = (): string | undefined => this.SocialSecuritynumber4() /** * Index 28: Social Security number - 5 */ SocialSecuritynumber5 = (): string | undefined => this.f1040.info.taxPayer.dependents[4]?.ssid f28 = (): string | undefined => this.SocialSecuritynumber5() /** * Index 29: Social Security number - 6 */ SocialSecuritynumber6 = (): string | undefined => this.f1040.info.taxPayer.dependents[5]?.ssid f29 = (): string | undefined => this.SocialSecuritynumber6() /** * Index 30: Social Security number - 7 */ SocialSecuritynumber7 = (): string | undefined => this.f1040.info.taxPayer.dependents[6]?.ssid f30 = (): string | undefined => this.SocialSecuritynumber7() /** * Index 31: Social Security number - 8 */ SocialSecuritynumber8 = (): string | undefined => this.f1040.info.taxPayer.dependents[7]?.ssid f31 = (): string | undefined => this.SocialSecuritynumber8() /** * Index 32: Social Security number - 9 */ SocialSecuritynumber9 = (): string | undefined => this.f1040.info.taxPayer.dependents[8]?.ssid f32 = (): string | undefined => this.SocialSecuritynumber9() /** * Index 33: Social Security number - 10 */ SocialSecuritynumber10 = (): string | undefined => this.f1040.info.taxPayer.dependents[9]?.ssid f33 = (): string | undefined => this.SocialSecuritynumber10() /** * Index 34: Dependent's relationship to you - 1 */ Dependentsrelationshiptoyou1 = (): string | undefined => { return undefined } f34 = (): string | undefined => this.Dependentsrelationshiptoyou1() /** * Index 35: Dependent's relationship to you - 2 */ Dependentsrelationshiptoyou2 = (): string | undefined => { return undefined } f35 = (): string | undefined => this.Dependentsrelationshiptoyou2() /** * Index 36: Dependent's relationship to you - 3 */ Dependentsrelationshiptoyou3 = (): string | undefined => { return undefined } f36 = (): string | undefined => this.Dependentsrelationshiptoyou3() /** * Index 37: Dependent's relationship to you - 4 */ Dependentsrelationshiptoyou4 = (): string | undefined => { return undefined } f37 = (): string | undefined => this.Dependentsrelationshiptoyou4() /** * Index 38: Dependent's relationship to you - 5 */ Dependentsrelationshiptoyou5 = (): string | undefined => { return undefined } f38 = (): string | undefined => this.Dependentsrelationshiptoyou5() /** * Index 39: Dependent's relationship to you - 6 */ Dependentsrelationshiptoyou6 = (): string | undefined => { return undefined } f39 = (): string | undefined => this.Dependentsrelationshiptoyou6() /** * Index 40: Dependent's relationship to you - 7 */ Dependentsrelationshiptoyou7 = (): string | undefined => { return undefined } f40 = (): string | undefined => this.Dependentsrelationshiptoyou7() /** * Index 41: Dependent's relationship to you - 8 */ Dependentsrelationshiptoyou8 = (): string | undefined => { return undefined } f41 = (): string | undefined => this.Dependentsrelationshiptoyou8() /** * Index 42: Dependent's relationship to you - 9 */ Dependentsrelationshiptoyou9 = (): string | undefined => { return undefined } f42 = (): string | undefined => this.Dependentsrelationshiptoyou9() /** * Index 43: Dependent's relationship to you - 10 */ Dependentsrelationshiptoyou10 = (): string | undefined => { return undefined } f43 = (): string | undefined => this.Dependentsrelationshiptoyou10() /** * Index 44: Dependent's date of birth (mm/dd/yyyy) - 1 */ Dependentsdateofbirthmmddyyyy1 = (): string | undefined => { return undefined } f44 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy1() /** * Index 45: Dependent's date of birth (mm/dd/yyyy) - 2 */ Dependentsdateofbirthmmddyyyy2 = (): string | undefined => { return undefined } f45 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy2() /** * Index 46: Dependent's date of birth (mm/dd/yyyy) - 3 */ Dependentsdateofbirthmmddyyyy3 = (): string | undefined => { return undefined } f46 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy3() /** * Index 47: Dependent's date of birth (mm/dd/yyyy) - 4 */ Dependentsdateofbirthmmddyyyy4 = (): string | undefined => { return undefined } f47 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy4() /** * Index 48: Dependent's date of birth (mm/dd/yyyy) - 5 */ Dependentsdateofbirthmmddyyyy5 = (): string | undefined => { return undefined } f48 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy5() /** * Index 49: Dependent's date of birth (mm/dd/yyyy) - 6 */ Dependentsdateofbirthmmddyyyy6 = (): string | undefined => { return undefined } f49 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy6() /** * Index 50: Dependent's date of birth (mm/dd/yyyy) - 7 */ Dependentsdateofbirthmmddyyyy7 = (): string | undefined => { return undefined } f50 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy7() /** * Index 51: Dependent's date of birth (mm/dd/yyyy) - 8 */ Dependentsdateofbirthmmddyyyy8 = (): string | undefined => { return undefined } f51 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy8() /** * Index 52: Dependent's date of birth (mm/dd/yyyy) - 9 */ Dependentsdateofbirthmmddyyyy9 = (): string | undefined => { return undefined } f52 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy9() /** * Index 53: Dependent's date of birth (mm/dd/yyyy) - 10 */ Dependentsdateofbirthmmddyyyy10 = (): string | undefined => { return undefined } f53 = (): string | undefined => this.Dependentsdateofbirthmmddyyyy10() /** * Index 54: Full Time Student - 1 */ FullTimeStudent1 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[0]?.qualifyingInfo?.isStudent f54 = (): boolean | undefined => this.FullTimeStudent1() /** * Index 55: Full Time Student - 2 */ FullTimeStudent2 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[1]?.qualifyingInfo?.isStudent f55 = (): boolean | undefined => this.FullTimeStudent2() /** * Index 56: Full Time Student - 3 */ FullTimeStudent3 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[2]?.qualifyingInfo?.isStudent f56 = (): boolean | undefined => this.FullTimeStudent3() /** * Index 57: Full Time Student - 4 */ FullTimeStudent4 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[3]?.qualifyingInfo?.isStudent f57 = (): boolean | undefined => this.FullTimeStudent4() /** * Index 58: Full Time Student - 5 */ FullTimeStudent5 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[4]?.qualifyingInfo?.isStudent f58 = (): boolean | undefined => this.FullTimeStudent5() /** * Index 59: Full Time Student - 6 */ FullTimeStudent6 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[5]?.qualifyingInfo?.isStudent f59 = (): boolean | undefined => this.FullTimeStudent6() /** * Index 60: Full Time Student - 7 */ FullTimeStudent7 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[6]?.qualifyingInfo?.isStudent f60 = (): boolean | undefined => this.FullTimeStudent7() /** * Index 61: Full Time Student - 8 */ FullTimeStudent8 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[7]?.qualifyingInfo?.isStudent f61 = (): boolean | undefined => this.FullTimeStudent8() /** * Index 62: Full Time Student - 9 */ FullTimeStudent9 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[8]?.qualifyingInfo?.isStudent f62 = (): boolean | undefined => this.FullTimeStudent9() /** * Index 63: Full Time Student - 10 */ FullTimeStudent10 = (): boolean | undefined => this.f1040.info.taxPayer.dependents[9]?.qualifyingInfo?.isStudent f63 = (): boolean | undefined => this.FullTimeStudent10() /** * Index 64: Person with disability - 1 * TODO: Handle disabilities */ Personwithdisability1 = (): boolean | undefined => undefined f64 = (): boolean | undefined => this.Personwithdisability1() /** * Index 65: Person with disability - 2 * TODO: Handle disabilities */ Personwithdisability2 = (): boolean | undefined => undefined f65 = (): boolean | undefined => this.Personwithdisability2() /** * Index 66: Person with disability - 3 * TODO: Handle disabilities */ Personwithdisability3 = (): boolean | undefined => undefined f66 = (): boolean | undefined => this.Personwithdisability3() /** * Index 67: Person with disability - 4 * TODO: Handle disabilities */ Personwithdisability4 = (): boolean | undefined => undefined f67 = (): boolean | undefined => this.Personwithdisability4() /** * Index 68: Person with disability - 5 * TODO: Handle disabilities */ Personwithdisability5 = (): boolean | undefined => undefined f68 = (): boolean | undefined => this.Personwithdisability5() /** * Index 69: Person with disability - 6 * TODO: Handle disabilities */ Personwithdisability6 = (): boolean | undefined => undefined f69 = (): boolean | undefined => this.Personwithdisability6() /** * Index 70: Person with disability - 7 * TODO: Handle disabilities */ Personwithdisability7 = (): boolean | undefined => undefined f70 = (): boolean | undefined => this.Personwithdisability7() /** * Index 71: Person with disability - 8 * TODO: Handle disabilities */ Personwithdisability8 = (): boolean | undefined => undefined f71 = (): boolean | undefined => this.Personwithdisability8() /** * Index 72: Person with disability - 9 * TODO: Handle disabilities */ Personwithdisability9 = (): boolean | undefined => undefined f72 = (): boolean | undefined => this.Personwithdisability9() /** * Index 73: Person with disability - 10 * TODO: Handle disabilities */ Personwithdisability10 = (): boolean | undefined => undefined f73 = (): boolean | undefined => this.Personwithdisability10() /** * Index 74: Number of months living with you - 1 */ Numberofmonthslivingwithyou1 = (): number | undefined => this.f1040.info.taxPayer.dependents[0]?.qualifyingInfo?.numberOfMonths f74 = (): number | undefined => this.Numberofmonthslivingwithyou1() /** * Index 75: Number of months living with you - 2 */ Numberofmonthslivingwithyou2 = (): number | undefined => this.f1040.info.taxPayer.dependents[1]?.qualifyingInfo?.numberOfMonths f75 = (): number | undefined => this.Numberofmonthslivingwithyou2() /** * Index 76: Number of months living with you - 3 */ Numberofmonthslivingwithyou3 = (): number | undefined => this.f1040.info.taxPayer.dependents[2]?.qualifyingInfo?.numberOfMonths f76 = (): number | undefined => this.Numberofmonthslivingwithyou3() /** * Index 77: Number of months living with you - 4 */ Numberofmonthslivingwithyou4 = (): number | undefined => this.f1040.info.taxPayer.dependents[3]?.qualifyingInfo?.numberOfMonths f77 = (): number | undefined => this.Numberofmonthslivingwithyou4() /** * Index 78: Number of months living with you - 5 */ Numberofmonthslivingwithyou5 = (): number | undefined => this.f1040.info.taxPayer.dependents[4]?.qualifyingInfo?.numberOfMonths f78 = (): number | undefined => this.Numberofmonthslivingwithyou5() /** * Index 79: Number of months living with you - 6 */ Numberofmonthslivingwithyou6 = (): number | undefined => this.f1040.info.taxPayer.dependents[5]?.qualifyingInfo?.numberOfMonths f79 = (): number | undefined => this.Numberofmonthslivingwithyou6() /** * Index 80: Number of months living with you - 7 */ Numberofmonthslivingwithyou7 = (): number | undefined => this.f1040.info.taxPayer.dependents[6]?.qualifyingInfo?.numberOfMonths f80 = (): number | undefined => this.Numberofmonthslivingwithyou7() /** * Index 81: Number of months living with you - 8 */ Numberofmonthslivingwithyou8 = (): number | undefined => this.f1040.info.taxPayer.dependents[7]?.qualifyingInfo?.numberOfMonths f81 = (): number | undefined => this.Numberofmonthslivingwithyou8() /** * Index 82: Number of months living with you - 9 */ Numberofmonthslivingwithyou9 = (): number | undefined => this.f1040.info.taxPayer.dependents[8]?.qualifyingInfo?.numberOfMonths f82 = (): number | undefined => this.Numberofmonthslivingwithyou9() /** * Index 83: Number of months living with you - 10 */ Numberofmonthslivingwithyou10 = (): number | undefined => this.f1040.info.taxPayer.dependents[9]?.qualifyingInfo?.numberOfMonths f83 = (): number | undefined => this.Numberofmonthslivingwithyou10() /** * Index 84: Eligible for Earned Income Credit - 1 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit1 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 0 f84 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit1() /** * Index 85: Eligible for Earned Income Credit - 2 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit2 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 1 f85 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit2() /** * Index 86: Eligible for Earned Income Credit - 3 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit3 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 2 f86 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit3() /** * Index 87: Eligible for Earned Income Credit - 4 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit4 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 3 f87 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit4() /** * Index 88: Eligible for Earned Income Credit - 5 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit5 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 4 f88 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit5() /** * Index 89: Eligible for Earned Income Credit - 6 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit6 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 5 f89 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit6() /** * Index 90: Eligible for Earned Income Credit - 7 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit7 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 6 f90 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit7() /** * Index 91: Eligible for Earned Income Credit - 8 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit8 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 7 f91 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit8() /** * Index 92: Eligible for Earned Income Credit - 9 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit9 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 8 f92 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit9() /** * Index 93: Eligible for Earned Income Credit - 10 * TODO: Confirm this checkbox */ EligibleforEarnedIncomeCredit10 = (): boolean | undefined => this.f1040.info.taxPayer.dependents.length > 9 f93 = (): boolean | undefined => this.EligibleforEarnedIncomeCredit10() /** * Index 94: Mutiplied total number of dependents */ Mutipliedtotalnumberofdependents = (): number | undefined => this.qualifyingDependents.length * parameters.eicDependentCredit f94 = (): number | undefined => this.Mutipliedtotalnumberofdependents() /** * Index 95: Child's first name -1 * TODO: non-dependent children not handled. */ Childsfirstname1 = (): string | undefined => { return undefined } f95 = (): string | undefined => this.Childsfirstname1() /** * Index 96: Child's first name -2 * TODO: non-dependent children not handled. */ Childsfirstname2 = (): string | undefined => { return undefined } f96 = (): string | undefined => this.Childsfirstname2() /** * Index 97: Child's first name -3 * TODO: non-dependent children not handled. */ Childsfirstname3 = (): string | undefined => { return undefined } f97 = (): string | undefined => this.Childsfirstname3() /** * Index 98: Child's first name - 4 * TODO: non-dependent children not handled. */ Childsfirstname4 = (): string | undefined => { return undefined } f98 = (): string | undefined => this.Childsfirstname4() /** * Index 99: Child's first name - 5 * TODO: non-dependent children not handled. */ Childsfirstname5 = (): string | undefined => { return undefined } f99 = (): string | undefined => this.Childsfirstname5() /** * Index 100: Child's first name - 6 * TODO: non-dependent children not handled. */ Childsfirstname6 = (): string | undefined => { return undefined } f100 = (): string | undefined => this.Childsfirstname6() /** * Index 101: Child's first name - 7 * TODO: non-dependent children not handled. */ Childsfirstname7 = (): string | undefined => { return undefined } f101 = (): string | undefined => this.Childsfirstname7() /** * Index 102: Child's first name - 8 * TODO: non-dependent children not handled. */ Childsfirstname8 = (): string | undefined => { return undefined } f102 = (): string | undefined => this.Childsfirstname8() /** * Index 103: Child's last name - 1 * TODO: non-dependent children not handled. */ Childslastname1 = (): string | undefined => { return undefined } f103 = (): string | undefined => this.Childslastname1() /** * Index 104: Child's last name - 2 * TODO: non-dependent children not handled. */ Childslastname2 = (): string | undefined => { return undefined } f104 = (): string | undefined => this.Childslastname2() /** * Index 105: Child's last name - 3 * TODO: non-dependent children not handled. */ Childslastname3 = (): string | undefined => { return undefined } f105 = (): string | undefined => this.Childslastname3() /** * Index 106: Child's last name - 4 * TODO: non-dependent children not handled. */ Childslastname4 = (): string | undefined => { return undefined } f106 = (): string | undefined => this.Childslastname4() /** * Index 107: Child's last name - 5 * TODO: non-dependent children not handled. */ Childslastname5 = (): string | undefined => { return undefined } f107 = (): string | undefined => this.Childslastname5() /** * Index 108: Child's last name - 6 * TODO: non-dependent children not handled. */ Childslastname6 = (): string | undefined => { return undefined } f108 = (): string | undefined => this.Childslastname6() /** * Index 109: Child's last name - 7 * TODO: non-dependent children not handled. */ Childslastname7 = (): string | undefined => { return undefined } f109 = (): string | undefined => this.Childslastname7() /** * Index 110: Child's last name - 8 * TODO: non-dependent children not handled. */ Childslastname8 = (): string | undefined => { return undefined } f110 = (): string | undefined => this.Childslastname8() /** * Index 111: Social Security number - 1 */ SocialSecuritynumber1 = (): string | undefined => this.f1040.info.taxPayer.dependents[0]?.ssid f111 = (): string | undefined => this.SocialSecuritynumber1() /** * Index 112: Child's Social Security number - 1 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber1 = (): string | undefined => { return undefined } f112 = (): string | undefined => this.ChildsSocialSecuritynumber1() /** * Index 113: Child's Social Security number - 2 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber2 = (): string | undefined => { return undefined } f113 = (): string | undefined => this.ChildsSocialSecuritynumber2() /** * Index 114: Child's Social Security number - 3 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber3 = (): string | undefined => { return undefined } f114 = (): string | undefined => this.ChildsSocialSecuritynumber3() /** * Index 115: Child's Social Security number - 4 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber4 = (): string | undefined => { return undefined } f115 = (): string | undefined => this.ChildsSocialSecuritynumber4() /** * Index 116: Child's Social Security number - 5 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber5 = (): string | undefined => { return undefined } f116 = (): string | undefined => this.ChildsSocialSecuritynumber5() /** * Index 117: Child's Social Security number - 6 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber6 = (): string | undefined => { return undefined } f117 = (): string | undefined => this.ChildsSocialSecuritynumber6() /** * Index 118: Child's Social Security number - 7 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber7 = (): string | undefined => { return undefined } f118 = (): string | undefined => this.ChildsSocialSecuritynumber7() /** * Index 119: Child's Social Security number - 8 * TODO: non-dependent children not handled. */ ChildsSocialSecuritynumber8 = (): string | undefined => { return undefined } f119 = (): string | undefined => this.ChildsSocialSecuritynumber8() /** * Index 120: Child's relationship to you - 1 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou1 = (): string | undefined => { return undefined } f120 = (): string | undefined => this.Childsrelationshiptoyou1() /** * Index 121: Child's relationship to you - 2 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou2 = (): string | undefined => { return undefined } f121 = (): string | undefined => this.Childsrelationshiptoyou2() /** * Index 122: Child's relationship to you - 3 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou3 = (): string | undefined => { return undefined } f122 = (): string | undefined => this.Childsrelationshiptoyou3() /** * Index 123: Child's relationship to you - 4 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou4 = (): string | undefined => { return undefined } f123 = (): string | undefined => this.Childsrelationshiptoyou4() /** * Index 124: Child's relationship to you - 5 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou5 = (): string | undefined => { return undefined } f124 = (): string | undefined => this.Childsrelationshiptoyou5() /** * Index 125: Child's relationship to you - 6 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou6 = (): string | undefined => { return undefined } f125 = (): string | undefined => this.Childsrelationshiptoyou6() /** * Index 126: Child's relationship to you - 7 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou7 = (): string | undefined => { return undefined } f126 = (): string | undefined => this.Childsrelationshiptoyou7() /** * Index 127: Child's relationship to you - 8 * TODO: non-dependent children not handled. */ Childsrelationshiptoyou8 = (): string | undefined => { return undefined } f127 = (): string | undefined => this.Childsrelationshiptoyou8() /** * Index 128: Child's date of birth (mm/dd/yyyy) - 1 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy1 = (): string | undefined => { return undefined } f128 = (): string | undefined => this.Childsdateofbirthmmddyyyy1() /** * Index 129: Child's date of birth (mm/dd/yyyy) - 2 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy2 = (): string | undefined => { return undefined } f129 = (): string | undefined => this.Childsdateofbirthmmddyyyy2() /** * Index 130: Child's date of birth (mm/dd/yyyy) - 3 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy3 = (): string | undefined => { return undefined } f130 = (): string | undefined => this.Childsdateofbirthmmddyyyy3() /** * Index 131: Child's date of birth (mm/dd/yyyy) - 4 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy4 = (): string | undefined => { return undefined } f131 = (): string | undefined => this.Childsdateofbirthmmddyyyy4() /** * Index 132: Child's date of birth (mm/dd/yyyy) - 5 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy5 = (): string | undefined => { return undefined } f132 = (): string | undefined => this.Childsdateofbirthmmddyyyy5() /** * Index 133: Child's date of birth (mm/dd/yyyy) - 6 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy6 = (): string | undefined => { return undefined } f133 = (): string | undefined => this.Childsdateofbirthmmddyyyy6() /** * Index 134: Child's date of birth (mm/dd/yyyy) - 7 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy7 = (): string | undefined => { return undefined } f134 = (): string | undefined => this.Childsdateofbirthmmddyyyy7() /** * Index 135: Child's date of birth (mm/dd/yyyy) - 8 * TODO: non-dependent children not handled. */ Childsdateofbirthmmddyyyy8 = (): string | undefined => { return undefined } f135 = (): string | undefined => this.Childsdateofbirthmmddyyyy8() /** * Index 136: Child - Full Time Student - 1 */ ChildFullTimeStudent1 = (): boolean | undefined => { return undefined } f136 = (): boolean | undefined => this.ChildFullTimeStudent1() /** * Index 137: Child - Full Time Student - 2 */ ChildFullTimeStudent2 = (): boolean | undefined => { return undefined } f137 = (): boolean | undefined => this.ChildFullTimeStudent2() /** * Index 138: Child - Full Time Student - 3 */ ChildFullTimeStudent3 = (): boolean | undefined => { return undefined } f138 = (): boolean | undefined => this.ChildFullTimeStudent3() /** * Index 139: Child - Full Time Student - 4 */ ChildFullTimeStudent4 = (): boolean | undefined => { return undefined } f139 = (): boolean | undefined => this.ChildFullTimeStudent4() /** * Index 140: Child - Full Time Student - 5 */ ChildFullTimeStudent5 = (): boolean | undefined => { return undefined } f140 = (): boolean | undefined => this.ChildFullTimeStudent5() /** * Index 141: Child - Full Time Student - 6 */ ChildFullTimeStudent6 = (): boolean | undefined => { return undefined } f141 = (): boolean | undefined => this.ChildFullTimeStudent6() /** * Index 142: Child - Full Time Student - 7 */ ChildFullTimeStudent7 = (): boolean | undefined => { return undefined } f142 = (): boolean | undefined => this.ChildFullTimeStudent7() /** * Index 143: Child - Full Time Student - 8 */ ChildFullTimeStudent8 = (): boolean | undefined => { return undefined } f143 = (): boolean | undefined => this.ChildFullTimeStudent8() /** * Index 144: Child - Person with disability - 2 */ ChildPersonwithdisability2 = (): boolean | undefined => { return undefined } f144 = (): boolean | undefined => this.ChildPersonwithdisability2() /** * Index 145: Child - Person with disability - 1 */ ChildPersonwithdisability1 = (): boolean | undefined => { return undefined } f145 = (): boolean | undefined => this.ChildPersonwithdisability1() /** * Index 146: Child - Person with disability - 3 */ ChildPersonwithdisability3 = (): boolean | undefined => { return undefined } f146 = (): boolean | undefined => this.ChildPersonwithdisability3() /** * Index 147: Child - Person with disability - 4 */ ChildPersonwithdisability4 = (): boolean | undefined => { return undefined } f147 = (): boolean | undefined => this.ChildPersonwithdisability4() /** * Index 148: Child - Person with disability - 5 */ ChildPersonwithdisability5 = (): boolean | undefined => { return undefined } f148 = (): boolean | undefined => this.ChildPersonwithdisability5() /** * Index 149: Child - Person with disability - 6 */ ChildPersonwithdisability6 = (): boolean | undefined => { return undefined } f149 = (): boolean | undefined => this.ChildPersonwithdisability6() /** * Index 150: Child - Person with disability - 7 */ ChildPersonwithdisability7 = (): boolean | undefined => { return undefined } f150 = (): boolean | undefined => this.ChildPersonwithdisability7() /** * Index 151: Child - Person with disability - 8 */ ChildPersonwithdisability8 = (): boolean | undefined => { return undefined } f151 = (): boolean | undefined => this.ChildPersonwithdisability8() /** * Index 152: Child - Number of months living with you - 1 */ ChildNumberofmonthslivingwithyou1 = (): string | undefined => { return undefined } f152 = (): string | undefined => this.ChildNumberofmonthslivingwithyou1() /** * Index 153: Child - Number of months living with you - 2 */ ChildNumberofmonthslivingwithyou2 = (): string | undefined => { return undefined } f153 = (): string | undefined => this.ChildNumberofmonthslivingwithyou2() /** * Index 154: Child - Number of months living with you - 3 */ ChildNumberofmonthslivingwithyou3 = (): string | undefined => { return undefined } f154 = (): string | undefined => this.ChildNumberofmonthslivingwithyou3() /** * Index 155: Child - Number of months living with you - 4 */ ChildNumberofmonthslivingwithyou4 = (): string | undefined => { return undefined } f155 = (): string | undefined => this.ChildNumberofmonthslivingwithyou4() /** * Index 156: Child - Number of months living with you - 5 */ ChildNumberofmonthslivingwithyou5 = (): string | undefined => { return undefined } f156 = (): string | undefined => this.ChildNumberofmonthslivingwithyou5() /** * Index 157: Child - Number of months living with you - 6 */ ChildNumberofmonthslivingwithyou6 = (): string | undefined => { return undefined } f157 = (): string | undefined => this.ChildNumberofmonthslivingwithyou6() /** * Index 158: Child - Number of months living with you - 7 */ ChildNumberofmonthslivingwithyou7 = (): string | undefined => { return undefined } f158 = (): string | undefined => this.ChildNumberofmonthslivingwithyou7() /** * Index 159: Child - Number of months living with you - 8 */ ChildNumberofmonthslivingwithyou8 = (): string | undefined => { return undefined } f159 = (): string | undefined => this.ChildNumberofmonthslivingwithyou8() /** * Index 160: Wages - Salaries - Tips */ WagesSalariesTips = (): number | undefined => this.f1040.l1() f160 = (): number | undefined => this.WagesSalariesTips() /** * Index 161: Business income/loss */ Businessincomeloss = (): number | undefined => this.f1040.schedule1.l3() f161 = (): number | undefined => this.Businessincomeloss() /** * Index 162: Occupation requirement * TODO: jurisdictional license question */ Occupationrequirement = (): boolean | undefined => undefined f162 = (): boolean | undefined => this.Occupationrequirement() /** * Index 163: Issuing agency - 1 */ Issuingagency1 = (): string | undefined => undefined f163 = (): string | undefined => this.Issuingagency1() /** * Index 164: Issuing agency - 2 */ Issuingagency2 = (): string | undefined => undefined f164 = (): string | undefined => this.Issuingagency2() /** * Index 165: Issuing agency - 3 */ Issuingagency3 = (): string | undefined => undefined f165 = (): string | undefined => this.Issuingagency3() /** * Index 166: Issuing agency - 5 */ Issuingagency5 = (): string | undefined => undefined f166 = (): string | undefined => this.Issuingagency5() /** * Index 167: Issuing agency - 4 */ Issuingagency4 = (): string | undefined => undefined f167 = (): string | undefined => this.Issuingagency4() /** * Index 168: License/Registration/Certification - 1 */ LicenseRegistrationCertification1 = (): string | undefined => undefined f168 = (): string | undefined => this.LicenseRegistrationCertification1() /** * Index 169: License/Registration/Certification - 2 */ LicenseRegistrationCertification2 = (): string | undefined => undefined f169 = (): string | undefined => this.LicenseRegistrationCertification2() /** * Index 170: License/Registration/Certification - 3 */ LicenseRegistrationCertification3 = (): string | undefined => undefined f170 = (): string | undefined => this.LicenseRegistrationCertification3() /** * Index 171: License/Registration/Certification - 4 */ LicenseRegistrationCertification4 = (): string | undefined => undefined f171 = (): string | undefined => this.LicenseRegistrationCertification4() /** * Index 172: License/Registration/Certification - 5 */ LicenseRegistrationCertification5 = (): string | undefined => undefined f172 = (): string | undefined => this.LicenseRegistrationCertification5() /** * Index 173: Filing - married filing jointly * TODO: Handle MFJ federal / MFS state issue. */ Filingmarriedfilingjointly = (): string | undefined => { return undefined } f173 = (): string | undefined => this.Filingmarriedfilingjointly() /** * Index 174: Spouse' s SSN3-2 * TODO - only applicable for MFS state and MFJ federal return */ SpousesSSN32 = (): string | undefined => undefined f174 = (): string | undefined => this.SpousesSSN32() /** * Index 175: Spouse's SSN2-2 */ SpousesSSN22 = (): string | undefined => undefined f175 = (): string | undefined => this.SpousesSSN22() /** * Index 176: Spouse's SSN4-2 */ SpousesSSN42 = (): string | undefined => undefined f176 = (): string | undefined => this.SpousesSSN42() /** * Index 177: Statutory employee box marked */ Statutoryemployeeboxmarked = (): boolean | undefined => { return undefined } f177 = (): boolean | undefined => this.Statutoryemployeeboxmarked() /** * Index 178: Federal Earned Income Credit amount */ FederalEarnedIncomeCreditamount = (): number | undefined => this.f1040.l27a() f178 = (): number | undefined => this.FederalEarnedIncomeCreditamount() /** * Index 179: Multiply L5 */ MultiplyL5 = (): number => (this.FederalEarnedIncomeCreditamount() ?? 0) * parameters.earnedIncomeCreditFactor f179 = (): number => Math.round(this.MultiplyL5()) /** * Index 180: Residents / Non-Residents rate - 1 * TODO: Handle non-residents rate */ ResidentsNonResidentsrate1 = (): number => 1 f180 = (): number => this.ResidentsNonResidentsrate1() /** * Index 181: Residents / Non-Residents rate - 2 * TODO: Handle non-residents rate and Schedule NR */ ResidentsNonResidentsrate2 = (): number => 0 f181 = (): number => this.ResidentsNonResidentsrate2() /** * Index 182: Multiply L6 - L7 */ MultiplyL6L7 = (): number | undefined => this.MultiplyL5() * (this.ResidentsNonResidentsrate1() + this.ResidentsNonResidentsrate2() / 100) earnedIncomeCredit = (): number | undefined => this.MultiplyL6L7() f182 = (): number => Math.round(this.MultiplyL6L7() ?? 0) /** * Index 183: Your SSN3 */ YourSSN3 = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.ssid.slice(0, 3) f183 = (): string | undefined => this.YourSSN3() /** * Index 184: Your SSN2 */ YourSSN2 = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.ssid.slice(3, 5) f184 = (): string | undefined => this.YourSSN2() /** * Index 185: Your SSN4 */ YourSSN4 = (): string | undefined => this.f1040.info.taxPayer.primaryPerson.ssid.slice(5) f185 = (): string | undefined => this.YourSSN4() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.f26(), this.f27(), this.f28(), this.f29(), this.f30(), this.f31(), this.f32(), this.f33(), this.f34(), this.f35(), this.f36(), this.f37(), this.f38(), this.f39(), this.f40(), this.f41(), this.f42(), this.f43(), this.f44(), this.f45(), this.f46(), this.f47(), this.f48(), this.f49(), this.f50(), this.f51(), this.f52(), this.f53(), this.f54(), this.f55(), this.f56(), this.f57(), this.f58(), this.f59(), this.f60(), this.f61(), this.f62(), this.f63(), this.f64(), this.f65(), this.f66(), this.f67(), this.f68(), this.f69(), this.f70(), this.f71(), this.f72(), this.f73(), this.f74(), this.f75(), this.f76(), this.f77(), this.f78(), this.f79(), this.f80(), this.f81(), this.f82(), this.f83(), this.f84(), this.f85(), this.f86(), this.f87(), this.f88(), this.f89(), this.f90(), this.f91(), this.f92(), this.f93(), this.f94(), this.f95(), this.f96(), this.f97(), this.f98(), this.f99(), this.f100(), this.f101(), this.f102(), this.f103(), this.f104(), this.f105(), this.f106(), this.f107(), this.f108(), this.f109(), this.f110(), this.f111(), this.f112(), this.f113(), this.f114(), this.f115(), this.f116(), this.f117(), this.f118(), this.f119(), this.f120(), this.f121(), this.f122(), this.f123(), this.f124(), this.f125(), this.f126(), this.f127(), this.f128(), this.f129(), this.f130(), this.f131(), this.f132(), this.f133(), this.f134(), this.f135(), this.f136(), this.f137(), this.f138(), this.f139(), this.f140(), this.f141(), this.f142(), this.f143(), this.f144(), this.f145(), this.f146(), this.f147(), this.f148(), this.f149(), this.f150(), this.f151(), this.f152(), this.f153(), this.f154(), this.f155(), this.f156(), this.f157(), this.f158(), this.f159(), this.f160(), this.f161(), this.f162(), this.f163(), this.f164(), this.f165(), this.f166(), this.f167(), this.f168(), this.f169(), this.f170(), this.f171(), this.f172(), this.f173(), this.f174(), this.f175(), this.f176(), this.f177(), this.f178(), this.f179(), this.f180(), this.f181(), this.f182(), this.f183(), this.f184(), this.f185() ] } const makeil1040scheduleileeic = (f1040: F1040): IL1040scheduleileeic => new IL1040scheduleileeic(f1040) export default makeil1040scheduleileeic ================================================ FILE: src/forms/Y2021/stateForms/IL/IL1040V.ts ================================================ import Form from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { IL1040 } from './IL1040' import { Field } from 'ustaxes/core/pdfFiller' import { State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export default class IL1040V extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State il1040: IL1040 formOrder = -1 attachments: () => Form[] = () => [] constructor(f1040: F1040, il1040: IL1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'IL-1040-V' this.state = 'IL' this.il1040 = il1040 } /** * Index 0: Help */ Help = (): string | undefined => { return undefined } f0 = (): string | undefined => this.Help() /** * Index 1: PrimarySSN1 */ PrimarySSN1 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(0, 3) f1 = (): string | undefined => this.PrimarySSN1() /** * Index 2: PrimarySSN2 */ PrimarySSN2 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(3, 5) f2 = (): string | undefined => this.PrimarySSN2() /** * Index 3: PrimarySSN3 */ PrimarySSN3 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(5) f3 = (): string | undefined => this.PrimarySSN3() /** * Index 4: SpouseSSN1 */ SpouseSSN1 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(0, 3) f4 = (): string | undefined => this.SpouseSSN1() /** * Index 5: SpouseSSN2 */ SpouseSSN2 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(3, 5) f5 = (): string | undefined => this.SpouseSSN2() /** * Index 6: SpouseSSN3 */ SpouseSSN3 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(5) f6 = (): string | undefined => this.SpouseSSN3() /** * Index 7: FirstName */ FirstName = (): string | undefined => this.info.taxPayer.primaryPerson.firstName f7 = (): string | undefined => this.FirstName() /** * Index 8: SpouseFirstName */ SpouseFirstName = (): string | undefined => this.info.taxPayer.spouse?.firstName f8 = (): string | undefined => this.SpouseFirstName() /** * Index 9: LastName */ LastName = (): string | undefined => this.info.taxPayer.primaryPerson.lastName f9 = (): string | undefined => this.LastName() /** * Index 10: Address */ Address = (): string | undefined => this.info.taxPayer.primaryPerson.address.address f10 = (): string | undefined => this.Address() /** * Index 11: City */ City = (): string | undefined => this.info.taxPayer.primaryPerson.address.city f11 = (): string | undefined => this.City() /** * Index 12: State */ State = (): string | undefined => this.info.taxPayer.primaryPerson.address.state f12 = (): string | undefined => this.State() /** * Index 13: ZIP */ ZIP = (): string | undefined => this.info.taxPayer.primaryPerson.address.zip f13 = (): string | undefined => this.ZIP() /** * Index 14: PaymentAmount */ PaymentAmount = (): number | undefined => { const amount = this.il1040.payment() if (amount !== undefined) { return Math.trunc(amount) } } f14 = (): number | undefined => this.PaymentAmount() /** * Index 15: PaymentAmountCents */ PaymentAmountCents = (): number | undefined => { const amount = this.il1040.payment() if (amount !== undefined) { return Math.round((amount - Math.trunc(amount)) * 100) } } f15 = (): number | undefined => this.PaymentAmountCents() /** * Index 16: Reset */ Reset = (): string | undefined => { return undefined } f16 = (): string | undefined => this.Reset() /** * Index 17: Print */ Print = (): string | undefined => { return undefined } f17 = (): string | undefined => this.Print() /** * Index 18: scanline */ scanline = (): string | undefined => { return undefined } f18 = (): string | undefined => this.scanline() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18() ] } ================================================ FILE: src/forms/Y2021/stateForms/IL/ILWit.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { IncomeW2, PersonRole, State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' type FormType = | 'W' // W-2 | 'WG' // W2-G | 'R' // 1099-R | 'G' // 1099-G | 'M' // 1099-MISC | 'O' // 1099-OID | 'D' // 1099-DIV | 'I' // 1099-INT | 'S' // 1042-S | 'B' // 1099-B | 'K' // 1099-K | 'N' // 1099-NEC interface WithholdingForm { formType: [FormType, FormType | undefined] role: PersonRole ein: string federalWages: number ilWages: number ilTax: number } const toWithholdingForm = (w2: IncomeW2): WithholdingForm | undefined => { if ( w2.stateWages !== undefined && w2.stateWithholding !== undefined && w2.employer?.EIN !== undefined ) { return { formType: ['W', undefined], ein: w2.employer.EIN, federalWages: w2.income, ilWages: w2.stateWages, ilTax: w2.stateWithholding, role: w2.personRole } } } /** * Each ILWIT form supports 5 withholding forms for * primary taxpayer and 5 for spouse * TODO: support more than 5 for each */ export class ILWIT extends Form { info: ValidatedInformation f1040: F1040 formName = 'IL-WIT' state: State = 'IL' formOrder = 31 methods: FormMethods formIndex: number static WITHHOLDING_FORMS_PER_PAGE = 5 constructor(f1040: F1040, subFormIndex = 0) { super() this.info = f1040.info this.f1040 = f1040 this.methods = new FormMethods(this) this.formIndex = subFormIndex } attachments = (): Form[] => { // If this is the head form, see if we need // more copies. For example if the SSIDs have 4 and 11 forms, // we will need 2 extra copies. this one will have 4 + 5, // next will have 0 + 5, last will have 0 + 1 if (this.formIndex === 0) { const copiesNeeded = Math.ceil( Math.max( ...[PersonRole.PRIMARY, PersonRole.SPOUSE].map( (r) => this.methods .stateW2s() .filter((w2) => (w2.stateWithholding ?? 0) > 0) .filter((w2) => w2.personRole === r).length ) ) / ILWIT.WITHHOLDING_FORMS_PER_PAGE ) - 1 return Array(copiesNeeded) .fill(undefined) .map((x, i) => new ILWIT(this.f1040, i + 1)) } return [] } /** * Index 0: Help */ Help = (): string | undefined => { return undefined } f0 = (): string | undefined => this.Help() /** * Index 1: Your name */ Yourname = (): string | undefined => { const person = this.info.taxPayer.primaryPerson return [person.firstName, person.lastName].flat().join(' ') } f1 = (): string | undefined => this.Yourname() /** * Index 2: Your SSN-3 */ YourSSN3 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(0, 3) f2 = (): string | undefined => this.YourSSN3() /** * Index 3: Your SSN-2 */ YourSSN2 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(3, 5) f3 = (): string | undefined => this.YourSSN2() /** * Index 4: Your SSN-4 */ YourSSN4 = (): string | undefined => this.info.taxPayer.primaryPerson.ssid.slice(5) f4 = (): string | undefined => this.YourSSN4() allWithholdingForms = (): WithholdingForm[] => this.methods .stateW2s() .map((w2) => toWithholdingForm(w2)) .filter((x) => x !== undefined) as WithholdingForm[] formsByRole = (role: PersonRole): WithholdingForm[] => this.allWithholdingForms().filter((w2) => w2.role === role) // TODO: handle more than 5 withholding forms. primaryForms = (): WithholdingForm[] => this.formsByRole(PersonRole.PRIMARY).slice(0, 5) // TODO: handle more than 5 withholding forms. spouseForms = (): WithholdingForm[] => this.formsByRole(PersonRole.SPOUSE).slice(0, 5) /** * 6 x 5 grid for primary taxpayer, rowwise. */ formGrid = (forms: WithholdingForm[]): (string | number | undefined)[] => [ ...forms.flatMap((form) => [ ...form.formType, form.ein, form.federalWages, form.ilWages, form.ilTax ]), ...Array.from(Array(5 - forms.length)).flatMap(() => Array(5).fill(undefined) ) ] /** * Primary ssid withholding, Column B -> Column E */ f5tof34 = (): (string | number | undefined)[] => this.formGrid(this.primaryForms()) /** * Index 35: Spouse's name */ Spousesname = (): string | undefined => { const spouse = this.info.taxPayer.spouse if (spouse !== undefined) { return `${spouse.firstName} ${spouse.lastName}` } } f35 = (): string | undefined => this.Spousesname() /** * Index 28: Spouse's SSN-3 */ SpousesSSN3 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(0, 3) f36 = (): string | undefined => this.SpousesSSN3() /** * Index 29: Spouse's SSN-2 */ SpousesSSN2 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(3, 5) f37 = (): string | undefined => this.SpousesSSN2() /** * Index 30: Spouse's SSN-4 */ SpousesSSN4 = (): string | undefined => this.info.taxPayer.spouse?.ssid.slice(5) f38 = (): string | undefined => this.SpousesSSN4() /** * Spouse ssid forms, Column B -> Column E */ f39tof68 = (): (string | number | undefined)[] => this.formGrid(this.spouseForms()) /** * Index 51: Total amount */ Totalamount = (): number | undefined => { if (this.formIndex === 0) { return this.allWithholdingForms().reduce((s, f) => s + f.ilTax, 0) } } f69 = (): number | undefined => this.Totalamount() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), ...this.f5tof34(), this.f35(), this.f36(), this.f37(), this.f38(), ...this.f39tof68() ] } const makeILWIT = (f1040: F1040): ILWIT => new ILWIT(f1040) export default makeILWIT ================================================ FILE: src/forms/Y2021/stateForms/IL/Parameters.ts ================================================ import { FilingStatus } from 'ustaxes/core/data' const parameters = { exemptions: { [FilingStatus.S]: { incomeLowerLimit: 2375, incomeUpperLimit: 250000, exemptionAmount: 2375 }, [FilingStatus.MFJ]: { incomeLowerLimit: 4750, incomeUpperLimit: 500000, exemptionAmount: 4750 } }, taxRate: 0.0495, seniorExemption: 1000, blindExemption: 1000, earnedIncomeCreditFactor: 0.18, eicDependentCredit: 2325 } export default parameters ================================================ FILE: src/forms/Y2021/stateForms/IN/Form.ts ================================================ export default class INForm {} ================================================ FILE: src/forms/Y2021/stateForms/KS/Form.ts ================================================ export default class KSForm {} ================================================ FILE: src/forms/Y2021/stateForms/KY/Form.ts ================================================ export default class KYForm {} ================================================ FILE: src/forms/Y2021/stateForms/LA/Form.ts ================================================ export default class LAForm {} ================================================ FILE: src/forms/Y2021/stateForms/MA/Form.ts ================================================ export default class MAForm {} ================================================ FILE: src/forms/Y2021/stateForms/MD/Form.ts ================================================ export default class MDForm {} ================================================ FILE: src/forms/Y2021/stateForms/ME/Form.ts ================================================ export default class MEForm {} ================================================ FILE: src/forms/Y2021/stateForms/MI/Form.ts ================================================ export default class MIForm {} ================================================ FILE: src/forms/Y2021/stateForms/MN/Form.ts ================================================ export default class MNForm {} ================================================ FILE: src/forms/Y2021/stateForms/MO/Form.ts ================================================ export default class MOForm {} ================================================ FILE: src/forms/Y2021/stateForms/MS/Form.ts ================================================ export default class MSForm {} ================================================ FILE: src/forms/Y2021/stateForms/MT/Form.ts ================================================ export default class MTForm {} ================================================ FILE: src/forms/Y2021/stateForms/NC/Form.ts ================================================ export default class NCForm {} ================================================ FILE: src/forms/Y2021/stateForms/ND/Form.ts ================================================ export default class NDForm {} ================================================ FILE: src/forms/Y2021/stateForms/NE/Form.ts ================================================ export default class NEForm {} ================================================ FILE: src/forms/Y2021/stateForms/NH/Form.ts ================================================ export default class NHForm {} ================================================ FILE: src/forms/Y2021/stateForms/NJ/Form.ts ================================================ export default class NJForm {} ================================================ FILE: src/forms/Y2021/stateForms/NM/Form.ts ================================================ export default class NMForm {} ================================================ FILE: src/forms/Y2021/stateForms/NV/Form.ts ================================================ export default class NVForm {} ================================================ FILE: src/forms/Y2021/stateForms/NY/Form.ts ================================================ export default class NYForm {} ================================================ FILE: src/forms/Y2021/stateForms/OH/Form.ts ================================================ export default class OHForm {} ================================================ FILE: src/forms/Y2021/stateForms/OK/Form.ts ================================================ export default class OKForm {} ================================================ FILE: src/forms/Y2021/stateForms/OR/Form.ts ================================================ export default class ORForm {} ================================================ FILE: src/forms/Y2021/stateForms/OR/OR40.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' // import { sumFields } from 'ustaxes/core/irsForms/util' import { State } from 'ustaxes/core/data' // import parameters from './Parameters' import { ORWFHDC } from './ORWFHDC' import OR40V from './OR40V' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class OR40 extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State scheduleWFHDC: ORWFHDC or40V: OR40V formOrder = 0 methods: FormMethods constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'OR-40' this.state = 'OR' this.scheduleWFHDC = new ORWFHDC(f1040) this.or40V = new OR40V(f1040, this) this.methods = new FormMethods(this) } attachments = (): Form[] => { // const pmt = this.payment() const result: Form[] = [] // if ((pmt ?? 0) > 0) { // result.push(this.il1040V) // } // if (this.scheduleEIC.isRequired()) { // result.push(this.scheduleEIC) // } // if (this.methods.stateWithholding() > 0) { // const ilwit = new ILWIT(this.info, this.f1040) // result.push(ilwit) // ilwit.attachments().forEach((f) => result.push(f)) // } return result } /** * Index 0: Button - Clear form */ ButtonClearform = (): string | undefined => { return undefined } f0 = (): string | undefined => this.ButtonClearform() /** * Index 1: or-40-p1-1 */ or40p11 = (): string | undefined => { return undefined } f1 = (): string | undefined => this.or40p11() /** * Index 2: or-40-p1-2 */ or40p12 = (): boolean | undefined => { return undefined } f2 = (): boolean | undefined => this.or40p12() /** * Index 3: or-40-p1-4 */ or40p14 = (): boolean | undefined => { return undefined } f3 = (): boolean | undefined => this.or40p14() /** * Index 4: or-40-p1-5 */ or40p15 = (): boolean | undefined => { return undefined } f4 = (): boolean | undefined => this.or40p15() /** * Index 5: or-40-p1-6 */ or40p16 = (): boolean | undefined => { return undefined } f5 = (): boolean | undefined => this.or40p16() /** * Index 6: or-40-p1-7 */ or40p17 = (): boolean | undefined => { return undefined } f6 = (): boolean | undefined => this.or40p17() /** * Index 7: or-40-p1-8 */ or40p18 = (): boolean | undefined => { return undefined } f7 = (): boolean | undefined => this.or40p18() /** * Index 8: or-40-p1-9 */ or40p19 = (): boolean | undefined => { return undefined } f8 = (): boolean | undefined => this.or40p19() /** * Index 9: or-40-p1-10 */ or40p110 = (): boolean | undefined => { return undefined } f9 = (): boolean | undefined => this.or40p110() /** * Index 10: or-40-p1-11 */ or40p111 = (): string | undefined => { return undefined } f10 = (): string | undefined => this.or40p111() /** * Index 11: or-40-p1-12 */ or40p112 = (): string | undefined => { return undefined } f11 = (): string | undefined => this.or40p112() /** * Index 12: or-40-p1-13 */ or40p113 = (): string | undefined => { return undefined } f12 = (): string | undefined => this.or40p113() /** * Index 13: or-40-p1-16 */ or40p116 = (): boolean | undefined => { return undefined } f13 = (): boolean | undefined => this.or40p116() /** * Index 14: or-40-p1-17 */ or40p117 = (): boolean | undefined => { return undefined } f14 = (): boolean | undefined => this.or40p117() /** * Index 15: or-40-p1-18 */ or40p118 = (): boolean | undefined => { return undefined } f15 = (): boolean | undefined => this.or40p118() /** * Index 16: or-40-p1-14 */ or40p114 = (): string | undefined => { return undefined } f16 = (): string | undefined => this.or40p114() /** * Index 17: or-40-p1-15 */ or40p115 = (): string | undefined => { return undefined } f17 = (): string | undefined => this.or40p115() /** * Index 18: or-40-p2-2 */ or40p22 = (): boolean | undefined => { return undefined } f18 = (): boolean | undefined => this.or40p22() /** * Index 19: or-40-p2-3 */ or40p23 = (): boolean | undefined => { return undefined } f19 = (): boolean | undefined => this.or40p23() /** * Index 20: or-40-p2-4 */ or40p24 = (): boolean | undefined => { return undefined } f20 = (): boolean | undefined => this.or40p24() /** * Index 21: or-40-p2-5 */ or40p25 = (): string | undefined => { return undefined } f21 = (): string | undefined => this.or40p25() /** * Index 22: or-40-p2-9 */ or40p29 = (): boolean | undefined => { return undefined } f22 = (): boolean | undefined => this.or40p29() /** * Index 23: or-40-p2-12 */ or40p212 = (): string | undefined => { return undefined } f23 = (): string | undefined => this.or40p212() /** * Index 24: or-40-p2-13 */ or40p213 = (): string | undefined => { return undefined } f24 = (): string | undefined => this.or40p213() /** * Index 25: or-40-p2-14 */ or40p214 = (): string | undefined => { return undefined } f25 = (): string | undefined => this.or40p214() /** * Index 26: or-40-p2-15 */ or40p215 = (): string | undefined => { return undefined } f26 = (): string | undefined => this.or40p215() /** * Index 27: or-40-p2-17 */ or40p217 = (): boolean | undefined => { return undefined } f27 = (): boolean | undefined => this.or40p217() /** * Index 28: or-40-p2-18 */ or40p218 = (): string | undefined => { return undefined } f28 = (): string | undefined => this.or40p218() /** * Index 29: or-40-p2-19 */ or40p219 = (): string | undefined => { return undefined } f29 = (): string | undefined => this.or40p219() /** * Index 30: or-40-p2-20 */ or40p220 = (): string | undefined => { return undefined } f30 = (): string | undefined => this.or40p220() /** * Index 31: or-40-p2-21 */ or40p221 = (): string | undefined => { return undefined } f31 = (): string | undefined => this.or40p221() /** * Index 32: or-40-p2-22 */ or40p222 = (): string | undefined => { return undefined } f32 = (): string | undefined => this.or40p222() /** * Index 33: or-40-p2-24 */ or40p224 = (): boolean | undefined => { return undefined } f33 = (): boolean | undefined => this.or40p224() /** * Index 34: or-40-p2-25 */ or40p225 = (): string | undefined => { return undefined } f34 = (): string | undefined => this.or40p225() /** * Index 35: or-40-p2-26 */ or40p226 = (): string | undefined => { return undefined } f35 = (): string | undefined => this.or40p226() /** * Index 36: or-40-p2-27 */ or40p227 = (): string | undefined => { return undefined } f36 = (): string | undefined => this.or40p227() /** * Index 37: or-40-p2-29 */ or40p229 = (): string | undefined => { return undefined } f37 = (): string | undefined => this.or40p229() /** * Index 38: or-40-p2-31 */ or40p231 = (): boolean | undefined => { return undefined } f38 = (): boolean | undefined => this.or40p231() /** * Index 39: or-40-p2-33 */ or40p233 = (): string | undefined => { return undefined } f39 = (): string | undefined => this.or40p233() /** * Index 40: or-40-p2-34 */ or40p234 = (): string | undefined => { return undefined } f40 = (): string | undefined => this.or40p234() /** * Index 41: or-40-p2-6 */ or40p26 = (): boolean | undefined => { return undefined } f41 = (): boolean | undefined => this.or40p26() /** * Index 42: or-40-p2-7 */ or40p27 = (): boolean | undefined => { return undefined } f42 = (): boolean | undefined => this.or40p27() /** * Index 43: or-40-p2-8 */ or40p28 = (): boolean | undefined => { return undefined } f43 = (): boolean | undefined => this.or40p28() /** * Index 44: or-40-p2-32 */ or40p232 = (): string | undefined => { return undefined } f44 = (): string | undefined => this.or40p232() /** * Index 45: or-40-p2-1 */ or40p21 = (): string | undefined => { return undefined } f45 = (): string | undefined => this.or40p21() /** * Index 46: or-40-p2-28 */ or40p228 = (): string | undefined => { return undefined } f46 = (): string | undefined => this.or40p228() /** * Index 47: or-40-p1-19 */ or40p119 = (): string | undefined => { return undefined } f47 = (): string | undefined => this.or40p119() /** * Index 48: or-40-p1-20 */ or40p120 = (): string | undefined => { return undefined } f48 = (): string | undefined => this.or40p120() /** * Index 49: or-40-p1-21 */ or40p121 = (): string | undefined => { return undefined } f49 = (): string | undefined => this.or40p121() /** * Index 50: or-40-p1-22 */ or40p122 = (): string | undefined => { return undefined } f50 = (): string | undefined => this.or40p122() /** * Index 51: or-40-p1-23 */ or40p123 = (): string | undefined => { return undefined } f51 = (): string | undefined => this.or40p123() /** * Index 52: or-40-p1-24 */ or40p124 = (): boolean | undefined => { return undefined } f52 = (): boolean | undefined => this.or40p124() /** * Index 53: or-40-p1-25 */ or40p125 = (): boolean | undefined => { return undefined } f53 = (): boolean | undefined => this.or40p125() /** * Index 54: or-40-p1-26 */ or40p126 = (): boolean | undefined => { return undefined } f54 = (): boolean | undefined => this.or40p126() /** * Index 55: or-40-p1-27 */ or40p127 = (): string | undefined => { return undefined } f55 = (): string | undefined => this.or40p127() /** * Index 56: or-40-p1-28 */ or40p128 = (): string | undefined => { return undefined } f56 = (): string | undefined => this.or40p128() /** * Index 57: or-40-p1-31 */ or40p131 = (): string | undefined => { return undefined } f57 = (): string | undefined => this.or40p131() /** * Index 58: or-40-p1-32 */ or40p132 = (): string | undefined => { return undefined } f58 = (): string | undefined => this.or40p132() /** * Index 59: or-40-p1-3 */ or40p13 = (): string | undefined => { return undefined } f59 = (): string | undefined => this.or40p13() /** * Index 60: or-40-p2-11 */ or40p211 = (): string | undefined => { return undefined } f60 = (): string | undefined => this.or40p211() /** * Index 61: or-40-p3-12 */ or40p312 = (): boolean | undefined => { return undefined } f61 = (): boolean | undefined => this.or40p312() /** * Index 62: or-40-p3-13 */ or40p313 = (): boolean | undefined => { return undefined } f62 = (): boolean | undefined => this.or40p313() /** * Index 63: or-40-p3-14 */ or40p314 = (): boolean | undefined => { return undefined } f63 = (): boolean | undefined => this.or40p314() /** * Index 64: or-40-p3-15 */ or40p315 = (): boolean | undefined => { return undefined } f64 = (): boolean | undefined => this.or40p315() /** * Index 65: or-40-p4-2 */ or40p42 = (): boolean | undefined => { return undefined } f65 = (): boolean | undefined => this.or40p42() /** * Index 66: or-40-p4-3 */ or40p43 = (): boolean | undefined => { return undefined } f66 = (): boolean | undefined => this.or40p43() /** * Index 67: or-40-p4-4 */ or40p44 = (): boolean | undefined => { return undefined } f67 = (): boolean | undefined => this.or40p44() /** * Index 68: or-40-p5-12 */ or40p512 = (): string | undefined => { return undefined } f68 = (): string | undefined => this.or40p512() /** * Index 69: or-40-p5-13 */ or40p513 = (): boolean | undefined => { return undefined } f69 = (): boolean | undefined => this.or40p513() /** * Index 70: or-40-p6-6 */ or40p66 = (): string | undefined => { return undefined } f70 = (): string | undefined => this.or40p66() /** * Index 71: or-40-p6-7 */ or40p67 = (): string | undefined => { return undefined } f71 = (): string | undefined => this.or40p67() /** * Index 72: or-40-p6-11 */ or40p611 = (): boolean | undefined => { return undefined } f72 = (): boolean | undefined => this.or40p611() /** * Index 73: or-40-p6-14 */ or40p614 = (): string | undefined => { return undefined } f73 = (): string | undefined => this.or40p614() /** * Index 74: or-40-p6-15 */ or40p615 = (): string | undefined => { return undefined } f74 = (): string | undefined => this.or40p615() /** * Index 75: or-40-p6-16 */ or40p616 = (): boolean | undefined => { return undefined } f75 = (): boolean | undefined => this.or40p616() /** * Index 76: or-40-p7-1 */ or40p71 = (): string | undefined => { return undefined } f76 = (): string | undefined => this.or40p71() /** * Index 77: or-40-p7-2 */ or40p72 = (): string | undefined => { return undefined } f77 = (): string | undefined => this.or40p72() /** * Index 78: or-40-p7-3 */ or40p73 = (): string | undefined => { return undefined } f78 = (): string | undefined => this.or40p73() /** * Index 79: or-40-p7-4 */ or40p74 = (): string | undefined => { return undefined } f79 = (): string | undefined => this.or40p74() /** * Index 80: or-40-p7-5 */ or40p75 = (): string | undefined => { return undefined } f80 = (): string | undefined => this.or40p75() /** * Index 81: or-40-p7-6 */ or40p76 = (): string | undefined => { return undefined } f81 = (): string | undefined => this.or40p76() /** * Index 82: or-40-p7-7 */ or40p77 = (): string | undefined => { return undefined } f82 = (): string | undefined => this.or40p77() /** * Index 83: or-40-p7-8 */ or40p78 = (): string | undefined => { return undefined } f83 = (): string | undefined => this.or40p78() /** * Index 84: or-40-p7-9 */ or40p79 = (): string | undefined => { return undefined } f84 = (): string | undefined => this.or40p79() /** * Index 85: or-40-p7-10 */ or40p710 = (): string | undefined => { return undefined } f85 = (): string | undefined => this.or40p710() /** * Index 86: or-40-p7-11 */ or40p711 = (): string | undefined => { return undefined } f86 = (): string | undefined => this.or40p711() /** * Index 87: or-40-p8-1 */ or40p81 = (): string | undefined => { return undefined } f87 = (): string | undefined => this.or40p81() /** * Index 88: or-40-p3-1 */ or40p31 = (): string | undefined => { return undefined } f88 = (): string | undefined => this.or40p31() /** * Index 89: or-40-p3-2 */ or40p32 = (): string | undefined => { return undefined } f89 = (): string | undefined => this.or40p32() /** * Index 90: or-40-p3-3 */ or40p33 = (): string | undefined => { return undefined } f90 = (): string | undefined => this.or40p33() /** * Index 91: or-40-p3-10 */ or40p310 = (): string | undefined => { return undefined } f91 = (): string | undefined => this.or40p310() /** * Index 92: or-40-p3-11 */ or40p311 = (): string | undefined => { return undefined } f92 = (): string | undefined => this.or40p311() /** * Index 93: or-40-p3-16 */ or40p316 = (): string | undefined => { return undefined } f93 = (): string | undefined => this.or40p316() /** * Index 94: or-40-p3-17 */ or40p317 = (): string | undefined => { return undefined } f94 = (): string | undefined => this.or40p317() /** * Index 95: or-40-p3-4 */ or40p34 = (): string | undefined => { return undefined } f95 = (): string | undefined => this.or40p34() /** * Index 96: or-40-p3-5 */ or40p35 = (): string | undefined => { return undefined } f96 = (): string | undefined => this.or40p35() /** * Index 97: or-40-p3-6 */ or40p36 = (): string | undefined => { return undefined } f97 = (): string | undefined => this.or40p36() /** * Index 98: or-40-p3-7 */ or40p37 = (): string | undefined => { return undefined } f98 = (): string | undefined => this.or40p37() /** * Index 99: or-40-p3-8 */ or40p38 = (): string | undefined => { return undefined } f99 = (): string | undefined => this.or40p38() /** * Index 100: or-40-p3-9 */ or40p39 = (): string | undefined => { return undefined } f100 = (): string | undefined => this.or40p39() /** * Index 101: or-40-p4-1 */ or40p41 = (): string | undefined => { return undefined } f101 = (): string | undefined => this.or40p41() /** * Index 102: or-40-p4-5 */ or40p45 = (): string | undefined => { return undefined } f102 = (): string | undefined => this.or40p45() /** * Index 103: or-40-p4-6 */ or40p46 = (): string | undefined => { return undefined } f103 = (): string | undefined => this.or40p46() /** * Index 104: or-40-p4-7 */ or40p47 = (): string | undefined => { return undefined } f104 = (): string | undefined => this.or40p47() /** * Index 105: or-40-p4-8 */ or40p48 = (): string | undefined => { return undefined } f105 = (): string | undefined => this.or40p48() /** * Index 106: or-40-p4-9 */ or40p49 = (): string | undefined => { return undefined } f106 = (): string | undefined => this.or40p49() /** * Index 107: or-40-p4-10 */ or40p410 = (): string | undefined => { return undefined } f107 = (): string | undefined => this.or40p410() /** * Index 108: or-40-p5-1 */ or40p51 = (): string | undefined => { return undefined } f108 = (): string | undefined => this.or40p51() /** * Index 109: or-40-p5-2 */ or40p52 = (): string | undefined => { return undefined } f109 = (): string | undefined => this.or40p52() /** * Index 110: or-40-p5-3 */ or40p53 = (): string | undefined => { return undefined } f110 = (): string | undefined => this.or40p53() /** * Index 111: or-40-p5-4 */ or40p54 = (): string | undefined => { return undefined } f111 = (): string | undefined => this.or40p54() /** * Index 112: or-40-p5-5 */ or40p55 = (): string | undefined => { return undefined } f112 = (): string | undefined => this.or40p55() /** * Index 113: or-40-p5-6 */ or40p56 = (): string | undefined => { return undefined } f113 = (): string | undefined => this.or40p56() /** * Index 114: or-40-p5-7 */ or40p57 = (): string | undefined => { return undefined } f114 = (): string | undefined => this.or40p57() /** * Index 115: or-40-p5-8 */ or40p58 = (): string | undefined => { return undefined } f115 = (): string | undefined => this.or40p58() /** * Index 116: or-40-p5-9 */ or40p59 = (): string | undefined => { return undefined } f116 = (): string | undefined => this.or40p59() /** * Index 117: or-40-p5-10 */ or40p510 = (): string | undefined => { return undefined } f117 = (): string | undefined => this.or40p510() /** * Index 118: or-40-p5-11 */ or40p511 = (): string | undefined => { return undefined } f118 = (): string | undefined => this.or40p511() /** * Index 119: or-40-p5-14 */ or40p514 = (): string | undefined => { return undefined } f119 = (): string | undefined => this.or40p514() /** * Index 120: or-40-p4-11 */ or40p411 = (): string | undefined => { return undefined } f120 = (): string | undefined => this.or40p411() /** * Index 121: or-40-p4-12 */ or40p412 = (): string | undefined => { return undefined } f121 = (): string | undefined => this.or40p412() /** * Index 122: or-40-p4-13 */ or40p413 = (): string | undefined => { return undefined } f122 = (): string | undefined => this.or40p413() /** * Index 123: or-40-p4-14 */ or40p414 = (): string | undefined => { return undefined } f123 = (): string | undefined => this.or40p414() /** * Index 124: or-40-p4-15 */ or40p415 = (): string | undefined => { return undefined } f124 = (): string | undefined => this.or40p415() /** * Index 125: or-40-p6-1 */ or40p61 = (): string | undefined => { return undefined } f125 = (): string | undefined => this.or40p61() /** * Index 126: or-40-p6-2 */ or40p62 = (): string | undefined => { return undefined } f126 = (): string | undefined => this.or40p62() /** * Index 127: or-40-p6-3 */ or40p63 = (): string | undefined => { return undefined } f127 = (): string | undefined => this.or40p63() /** * Index 128: or-40-p6-4 */ or40p64 = (): string | undefined => { return undefined } f128 = (): string | undefined => this.or40p64() /** * Index 129: or-40-p6-5 */ or40p65 = (): string | undefined => { return undefined } f129 = (): string | undefined => this.or40p65() /** * Index 130: or-40-p6-8 */ or40p68 = (): string | undefined => { return undefined } f130 = (): string | undefined => this.or40p68() /** * Index 131: or-40-p6-9 */ or40p69 = (): string | undefined => { return undefined } f131 = (): string | undefined => this.or40p69() /** * Index 132: or-40-p6-17 */ or40p617 = (): string | undefined => { return undefined } f132 = (): string | undefined => this.or40p617() /** * Index 133: or-40-p6-10 */ or40p610 = (): string | undefined => { return undefined } f133 = (): string | undefined => this.or40p610() /** * Index 134: or-40_p1_33 */ or40p133 = (): string | undefined => { return undefined } f134 = (): string | undefined => this.or40p133() /** * Index 135: or-40-p1-29 */ or40p129 = (): string | undefined => { return undefined } f135 = (): string | undefined => this.or40p129() /** * Index 136: or-40-p2-16 */ or40p216 = (): string | undefined => { return undefined } f136 = (): string | undefined => this.or40p216() /** * Index 137: or-40-p2-23 */ or40p223 = (): string | undefined => { return undefined } f137 = (): string | undefined => this.or40p223() /** * Index 138: or-40-p2-30 */ or40p230 = (): string | undefined => { return undefined } f138 = (): string | undefined => this.or40p230() /** * Index 139: or-40-p6-12 */ or40p612 = (): string | undefined => { return undefined } f139 = (): string | undefined => this.or40p612() /** * Index 140: or-40-p7-13 */ or40p713 = (): string | undefined => { return undefined } f140 = (): string | undefined => this.or40p713() /** * Index 141: or-40-p7-14 */ or40p714 = (): string | undefined => { return undefined } f141 = (): string | undefined => this.or40p714() /** * Index 142: or-40-p1-30a */ or40p130a = (): string | undefined => { return undefined } f142 = (): string | undefined => this.or40p130a() /** * Index 143: or-40-p1-30b */ or40p130b = (): string | undefined => { return undefined } f143 = (): string | undefined => this.or40p130b() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.f26(), this.f27(), this.f28(), this.f29(), this.f30(), this.f31(), this.f32(), this.f33(), this.f34(), this.f35(), this.f36(), this.f37(), this.f38(), this.f39(), this.f40(), this.f41(), this.f42(), this.f43(), this.f44(), this.f45(), this.f46(), this.f47(), this.f48(), this.f49(), this.f50(), this.f51(), this.f52(), this.f53(), this.f54(), this.f55(), this.f56(), this.f57(), this.f58(), this.f59(), this.f60(), this.f61(), this.f62(), this.f63(), this.f64(), this.f65(), this.f66(), this.f67(), this.f68(), this.f69(), this.f70(), this.f71(), this.f72(), this.f73(), this.f74(), this.f75(), this.f76(), this.f77(), this.f78(), this.f79(), this.f80(), this.f81(), this.f82(), this.f83(), this.f84(), this.f85(), this.f86(), this.f87(), this.f88(), this.f89(), this.f90(), this.f91(), this.f92(), this.f93(), this.f94(), this.f95(), this.f96(), this.f97(), this.f98(), this.f99(), this.f100(), this.f101(), this.f102(), this.f103(), this.f104(), this.f105(), this.f106(), this.f107(), this.f108(), this.f109(), this.f110(), this.f111(), this.f112(), this.f113(), this.f114(), this.f115(), this.f116(), this.f117(), this.f118(), this.f119(), this.f120(), this.f121(), this.f122(), this.f123(), this.f124(), this.f125(), this.f126(), this.f127(), this.f128(), this.f129(), this.f130(), this.f131(), this.f132(), this.f133(), this.f134(), this.f135(), this.f136(), this.f137(), this.f138(), this.f139(), this.f140(), this.f141(), this.f142(), this.f143() ] } const makeOR40 = (f1040: F1040): OR40 => new OR40(f1040) export default makeOR40 ================================================ FILE: src/forms/Y2021/stateForms/OR/OR40N.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class OR40N extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State formOrder = 0 methods: FormMethods constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'OR-40-N' this.state = 'OR' this.methods = new FormMethods(this) } attachments = (): Form[] => { // const pmt = this.payment() const result: Form[] = [] // if ((pmt ?? 0) > 0) { // result.push(this.il1040V) // } // if (this.scheduleEIC.isRequired()) { // result.push(this.scheduleEIC) // } // if (this.methods.stateWithholding() > 0) { // const ilwit = new ILWIT(this.info, this.f1040) // result.push(ilwit) // ilwit.attachments().forEach((f) => result.push(f)) // } return result } /** * Index 0: Button - Clear form */ ButtonClearform = (): string | undefined => { return undefined } f0 = (): string | undefined => this.ButtonClearform() /** * Index 1: or-40-n-p1-1 */ or40np11 = (): string | undefined => { return undefined } f1 = (): string | undefined => this.or40np11() /** * Index 2: or-40-n-p1-2 */ or40np12 = (): boolean | undefined => { return undefined } f2 = (): boolean | undefined => this.or40np12() /** * Index 3: or-40-n-p1-3 */ or40np13 = (): string | undefined => { return undefined } f3 = (): string | undefined => this.or40np13() /** * Index 4: or-40-n-p1-4 */ or40np14 = (): boolean | undefined => { return undefined } f4 = (): boolean | undefined => this.or40np14() /** * Index 5: or-40-n-p1-5 */ or40np15 = (): boolean | undefined => { return undefined } f5 = (): boolean | undefined => this.or40np15() /** * Index 6: or-40-n-p1-6 */ or40np16 = (): boolean | undefined => { return undefined } f6 = (): boolean | undefined => this.or40np16() /** * Index 7: or-40-n-p1-7 */ or40np17 = (): boolean | undefined => { return undefined } f7 = (): boolean | undefined => this.or40np17() /** * Index 8: or-40-n-p1-8 */ or40np18 = (): boolean | undefined => { return undefined } f8 = (): boolean | undefined => this.or40np18() /** * Index 9: or-40-n-p1-9 */ or40np19 = (): boolean | undefined => { return undefined } f9 = (): boolean | undefined => this.or40np19() /** * Index 10: or-40-n-p1-10 */ or40np110 = (): boolean | undefined => { return undefined } f10 = (): boolean | undefined => this.or40np110() /** * Index 11: or-40-n-p1-11 */ or40np111 = (): boolean | undefined => { return undefined } f11 = (): boolean | undefined => this.or40np111() /** * Index 12: or-40-n-p1-12 */ or40np112 = (): boolean | undefined => { return undefined } f12 = (): boolean | undefined => this.or40np112() /** * Index 13: or-40-n-p1-13 */ or40np113 = (): string | undefined => { return undefined } f13 = (): string | undefined => this.or40np113() /** * Index 14: or-40-n-p1-14 */ or40np114 = (): string | undefined => { return undefined } f14 = (): string | undefined => this.or40np114() /** * Index 15: or-40-n-p1-15 */ or40np115 = (): string | undefined => { return undefined } f15 = (): string | undefined => this.or40np115() /** * Index 16: or-40-n-p1-18 */ or40np118 = (): boolean | undefined => { return undefined } f16 = (): boolean | undefined => this.or40np118() /** * Index 17: or-40-n-p1-19 */ or40np119 = (): boolean | undefined => { return undefined } f17 = (): boolean | undefined => this.or40np119() /** * Index 18: or-40-n-p1-20 */ or40np120 = (): boolean | undefined => { return undefined } f18 = (): boolean | undefined => this.or40np120() /** * Index 19: or-40-n-p1-21 */ or40np121 = (): string | undefined => { return undefined } f19 = (): string | undefined => this.or40np121() /** * Index 20: or-40-n-p1-22 */ or40np122 = (): string | undefined => { return undefined } f20 = (): string | undefined => this.or40np122() /** * Index 21: or-40-n-p1-23 */ or40np123 = (): string | undefined => { return undefined } f21 = (): string | undefined => this.or40np123() /** * Index 22: or-40-n-p1-24 */ or40np124 = (): string | undefined => { return undefined } f22 = (): string | undefined => this.or40np124() /** * Index 23: or-40-n-p1-25 */ or40np125 = (): string | undefined => { return undefined } f23 = (): string | undefined => this.or40np125() /** * Index 24: or-40-n-p1-26 */ or40np126 = (): boolean | undefined => { return undefined } f24 = (): boolean | undefined => this.or40np126() /** * Index 25: or-40-n-p1-27 */ or40np127 = (): boolean | undefined => { return undefined } f25 = (): boolean | undefined => this.or40np127() /** * Index 26: or-40-n-p1-28 */ or40np128 = (): boolean | undefined => { return undefined } f26 = (): boolean | undefined => this.or40np128() /** * Index 27: or-40-n-p1-29 */ or40np129 = (): string | undefined => { return undefined } f27 = (): string | undefined => this.or40np129() /** * Index 28: or-40-n-p1-30 */ or40np130 = (): string | undefined => { return undefined } f28 = (): string | undefined => this.or40np130() /** * Index 29: or-40-n-p1-33 */ or40np133 = (): string | undefined => { return undefined } f29 = (): string | undefined => this.or40np133() /** * Index 30: or-40-n-p1-34 */ or40np134 = (): string | undefined => { return undefined } f30 = (): string | undefined => this.or40np134() /** * Index 31: or-40-n-p1-16 */ or40np116 = (): string | undefined => { return undefined } f31 = (): string | undefined => this.or40np116() /** * Index 32: or-40-n-p1-17 */ or40np117 = (): string | undefined => { return undefined } f32 = (): string | undefined => this.or40np117() /** * Index 33: or-40-n-p2-1 */ or40np21 = (): string | undefined => { return undefined } f33 = (): string | undefined => this.or40np21() /** * Index 34: or-40-n-p2-2 */ or40np22 = (): boolean | undefined => { return undefined } f34 = (): boolean | undefined => this.or40np22() /** * Index 35: or-40-n-p2-3 */ or40np23 = (): boolean | undefined => { return undefined } f35 = (): boolean | undefined => this.or40np23() /** * Index 36: or-40-n-p2-4 */ or40np24 = (): boolean | undefined => { return undefined } f36 = (): boolean | undefined => this.or40np24() /** * Index 37: or-40-n-p2-5 */ or40np25 = (): string | undefined => { return undefined } f37 = (): string | undefined => this.or40np25() /** * Index 38: or-40-n-p2-6 */ or40np26 = (): boolean | undefined => { return undefined } f38 = (): boolean | undefined => this.or40np26() /** * Index 39: or-40-n-p2-7 */ or40np27 = (): boolean | undefined => { return undefined } f39 = (): boolean | undefined => this.or40np27() /** * Index 40: or-40-n-p2-8 */ or40np28 = (): boolean | undefined => { return undefined } f40 = (): boolean | undefined => this.or40np28() /** * Index 41: or-40-n-p2-9 */ or40np29 = (): boolean | undefined => { return undefined } f41 = (): boolean | undefined => this.or40np29() /** * Index 42: or-40-n-p2-10 */ or40np210 = (): string | undefined => { return undefined } f42 = (): string | undefined => this.or40np210() /** * Index 43: or-40-n-p2-11 */ or40np211 = (): string | undefined => { return undefined } f43 = (): string | undefined => this.or40np211() /** * Index 44: or-40-n-p2-12 */ or40np212 = (): string | undefined => { return undefined } f44 = (): string | undefined => this.or40np212() /** * Index 45: or-40-n-p2-13 */ or40np213 = (): string | undefined => { return undefined } f45 = (): string | undefined => this.or40np213() /** * Index 46: or-40-n-p2-14 */ or40np214 = (): string | undefined => { return undefined } f46 = (): string | undefined => this.or40np214() /** * Index 47: or-40-n-p2-16 */ or40np216 = (): boolean | undefined => { return undefined } f47 = (): boolean | undefined => this.or40np216() /** * Index 48: or-40-n-p2-17 */ or40np217 = (): string | undefined => { return undefined } f48 = (): string | undefined => this.or40np217() /** * Index 49: or-40-n-p2-18 */ or40np218 = (): string | undefined => { return undefined } f49 = (): string | undefined => this.or40np218() /** * Index 50: or-40-n-p2-19 */ or40np219 = (): string | undefined => { return undefined } f50 = (): string | undefined => this.or40np219() /** * Index 51: or-40-n-p2-20 */ or40np220 = (): string | undefined => { return undefined } f51 = (): string | undefined => this.or40np220() /** * Index 52: or-40-n-p2-21 */ or40np221 = (): string | undefined => { return undefined } f52 = (): string | undefined => this.or40np221() /** * Index 53: or-40-n-p2-23 */ or40np223 = (): boolean | undefined => { return undefined } f53 = (): boolean | undefined => this.or40np223() /** * Index 54: or-40-n-p2-24 */ or40np224 = (): string | undefined => { return undefined } f54 = (): string | undefined => this.or40np224() /** * Index 55: or-40-n-p2-25 */ or40np225 = (): string | undefined => { return undefined } f55 = (): string | undefined => this.or40np225() /** * Index 56: or-40-n-p2-26 */ or40np226 = (): string | undefined => { return undefined } f56 = (): string | undefined => this.or40np226() /** * Index 57: or-40-n-p2-27 */ or40np227 = (): string | undefined => { return undefined } f57 = (): string | undefined => this.or40np227() /** * Index 58: or-40-n-p2-28 */ or40np228 = (): string | undefined => { return undefined } f58 = (): string | undefined => this.or40np228() /** * Index 59: or-40-n-p2-30 */ or40np230 = (): boolean | undefined => { return undefined } f59 = (): boolean | undefined => this.or40np230() /** * Index 60: or-40-n-p2-31 */ or40np231 = (): string | undefined => { return undefined } f60 = (): string | undefined => this.or40np231() /** * Index 61: or-40-n-p2-32 */ or40np232 = (): string | undefined => { return undefined } f61 = (): string | undefined => this.or40np232() /** * Index 62: or-40-n-p2-33 */ or40np233 = (): string | undefined => { return undefined } f62 = (): string | undefined => this.or40np233() /** * Index 63: or-40-n-p3-1 */ or40np31 = (): string | undefined => { return undefined } f63 = (): string | undefined => this.or40np31() /** * Index 64: or-40-n-p3-2 */ or40np32 = (): string | undefined => { return undefined } f64 = (): string | undefined => this.or40np32() /** * Index 65: or-40-n-p3-3 */ or40np33 = (): string | undefined => { return undefined } f65 = (): string | undefined => this.or40np33() /** * Index 66: or-40-n-p3-4 */ or40np34 = (): string | undefined => { return undefined } f66 = (): string | undefined => this.or40np34() /** * Index 67: or-40-n-p3-5 */ or40np35 = (): string | undefined => { return undefined } f67 = (): string | undefined => this.or40np35() /** * Index 68: or-40-n-p3-6 */ or40np36 = (): string | undefined => { return undefined } f68 = (): string | undefined => this.or40np36() /** * Index 69: or-40-n-p3-7 */ or40np37 = (): string | undefined => { return undefined } f69 = (): string | undefined => this.or40np37() /** * Index 70: or-40-n-p3-8 */ or40np38 = (): string | undefined => { return undefined } f70 = (): string | undefined => this.or40np38() /** * Index 71: or-40-n-p3-9 */ or40np39 = (): string | undefined => { return undefined } f71 = (): string | undefined => this.or40np39() /** * Index 72: or-40-n-p3-10 */ or40np310 = (): string | undefined => { return undefined } f72 = (): string | undefined => this.or40np310() /** * Index 73: or-40-n-p3-11 */ or40np311 = (): string | undefined => { return undefined } f73 = (): string | undefined => this.or40np311() /** * Index 74: or-40-n-p3-12 */ or40np312 = (): string | undefined => { return undefined } f74 = (): string | undefined => this.or40np312() /** * Index 75: or-40-n-p3-13 */ or40np313 = (): string | undefined => { return undefined } f75 = (): string | undefined => this.or40np313() /** * Index 76: or-40-n-p3-14 */ or40np314 = (): string | undefined => { return undefined } f76 = (): string | undefined => this.or40np314() /** * Index 77: or-40-n-p3-17 */ or40np317 = (): string | undefined => { return undefined } f77 = (): string | undefined => this.or40np317() /** * Index 78: or-40-n-p3-18 */ or40np318 = (): string | undefined => { return undefined } f78 = (): string | undefined => this.or40np318() /** * Index 79: or-40-n-p3-15 */ or40np315 = (): string | undefined => { return undefined } f79 = (): string | undefined => this.or40np315() /** * Index 80: or-40-n-p3-16 */ or40np316 = (): string | undefined => { return undefined } f80 = (): string | undefined => this.or40np316() /** * Index 81: or-40-n-p4-1 */ or40np41 = (): string | undefined => { return undefined } f81 = (): string | undefined => this.or40np41() /** * Index 82: or-40-n-p4-2 */ or40np42 = (): string | undefined => { return undefined } f82 = (): string | undefined => this.or40np42() /** * Index 83: or-40-n-p4-3 */ or40np43 = (): string | undefined => { return undefined } f83 = (): string | undefined => this.or40np43() /** * Index 84: or-40-n-p4-4 */ or40np44 = (): string | undefined => { return undefined } f84 = (): string | undefined => this.or40np44() /** * Index 85: or-40-n-p4-5 */ or40np45 = (): string | undefined => { return undefined } f85 = (): string | undefined => this.or40np45() /** * Index 86: or-40-n-p4-6 */ or40np46 = (): string | undefined => { return undefined } f86 = (): string | undefined => this.or40np46() /** * Index 87: or-40-n-p4-7 */ or40np47 = (): string | undefined => { return undefined } f87 = (): string | undefined => this.or40np47() /** * Index 88: or-40-n-p4-8 */ or40np48 = (): string | undefined => { return undefined } f88 = (): string | undefined => this.or40np48() /** * Index 89: or-40-n-p4-9 */ or40np49 = (): string | undefined => { return undefined } f89 = (): string | undefined => this.or40np49() /** * Index 90: or-40-n-p4-10 */ or40np410 = (): string | undefined => { return undefined } f90 = (): string | undefined => this.or40np410() /** * Index 91: or-40-n-p4-11 */ or40np411 = (): string | undefined => { return undefined } f91 = (): string | undefined => this.or40np411() /** * Index 92: or-40-n-p4-12 */ or40np412 = (): string | undefined => { return undefined } f92 = (): string | undefined => this.or40np412() /** * Index 93: or-40-n-p4-13 */ or40np413 = (): string | undefined => { return undefined } f93 = (): string | undefined => this.or40np413() /** * Index 94: or-40-n-p4-14 */ or40np414 = (): string | undefined => { return undefined } f94 = (): string | undefined => this.or40np414() /** * Index 95: or-40-n-p4-15 */ or40np415 = (): string | undefined => { return undefined } f95 = (): string | undefined => this.or40np415() /** * Index 96: or-40-n-p4-16 */ or40np416 = (): string | undefined => { return undefined } f96 = (): string | undefined => this.or40np416() /** * Index 97: or-40-n-p5-1 */ or40np51 = (): string | undefined => { return undefined } f97 = (): string | undefined => this.or40np51() /** * Index 98: or-40-n-p5-2 */ or40np52 = (): string | undefined => { return undefined } f98 = (): string | undefined => this.or40np52() /** * Index 99: or-40-n-p5-3 */ or40np53 = (): string | undefined => { return undefined } f99 = (): string | undefined => this.or40np53() /** * Index 100: or-40-n-p5-4 */ or40np54 = (): string | undefined => { return undefined } f100 = (): string | undefined => this.or40np54() /** * Index 101: or-40-n-p5-5 */ or40np55 = (): string | undefined => { return undefined } f101 = (): string | undefined => this.or40np55() /** * Index 102: or-40-n-p5-6 */ or40np56 = (): string | undefined => { return undefined } f102 = (): string | undefined => this.or40np56() /** * Index 103: or-40-n-p5-7 */ or40np57 = (): string | undefined => { return undefined } f103 = (): string | undefined => this.or40np57() /** * Index 104: or-40-n-p5-8 */ or40np58 = (): string | undefined => { return undefined } f104 = (): string | undefined => this.or40np58() /** * Index 105: or-40-n-p5-9 */ or40np59 = (): string | undefined => { return undefined } f105 = (): string | undefined => this.or40np59() /** * Index 106: or-40-n-p5-10 */ or40np510 = (): string | undefined => { return undefined } f106 = (): string | undefined => this.or40np510() /** * Index 107: or-40-n-p5-11 */ or40np511 = (): string | undefined => { return undefined } f107 = (): string | undefined => this.or40np511() /** * Index 108: or-40-n-p5-12 */ or40np512 = (): string | undefined => { return undefined } f108 = (): string | undefined => this.or40np512() /** * Index 109: or-40-n-p5-13 */ or40np513 = (): string | undefined => { return undefined } f109 = (): string | undefined => this.or40np513() /** * Index 110: or-40-n-p5-14 */ or40np514 = (): string | undefined => { return undefined } f110 = (): string | undefined => this.or40np514() /** * Index 111: or-40-n-p5-15 */ or40np515 = (): string | undefined => { return undefined } f111 = (): string | undefined => this.or40np515() /** * Index 112: or-40-n-p5-16 */ or40np516 = (): string | undefined => { return undefined } f112 = (): string | undefined => this.or40np516() /** * Index 113: or-40-n-p6-1 */ or40np61 = (): string | undefined => { return undefined } f113 = (): string | undefined => this.or40np61() /** * Index 114: or-40-n-p6-2 */ or40np62 = (): string | undefined => { return undefined } f114 = (): string | undefined => this.or40np62() /** * Index 115: or-40-n-p6-3 */ or40np63 = (): string | undefined => { return undefined } f115 = (): string | undefined => this.or40np63() /** * Index 116: or-40-n-p6-4 */ or40np64 = (): string | undefined => { return undefined } f116 = (): string | undefined => this.or40np64() /** * Index 117: or-40-n-p6-5 */ or40np65 = (): string | undefined => { return undefined } f117 = (): string | undefined => this.or40np65() /** * Index 118: or-40-n-p6-6 */ or40np66 = (): string | undefined => { return undefined } f118 = (): string | undefined => this.or40np66() /** * Index 119: or-40-n-p6-7 */ or40np67 = (): string | undefined => { return undefined } f119 = (): string | undefined => this.or40np67() /** * Index 120: or-40-n-p6-8 */ or40np68 = (): string | undefined => { return undefined } f120 = (): string | undefined => this.or40np68() /** * Index 121: or-40-n-p6-9 */ or40np69 = (): string | undefined => { return undefined } f121 = (): string | undefined => this.or40np69() /** * Index 122: or-40-n-p6-10 */ or40np610 = (): string | undefined => { return undefined } f122 = (): string | undefined => this.or40np610() /** * Index 123: or-40-n-p6-11 */ or40np611 = (): boolean | undefined => { return undefined } f123 = (): boolean | undefined => this.or40np611() /** * Index 124: or-40-n-p6-12 */ or40np612 = (): boolean | undefined => { return undefined } f124 = (): boolean | undefined => this.or40np612() /** * Index 125: or-40-n-p6-13 */ or40np613 = (): boolean | undefined => { return undefined } f125 = (): boolean | undefined => this.or40np613() /** * Index 126: or-40-n-p6-14 */ or40np614 = (): boolean | undefined => { return undefined } f126 = (): boolean | undefined => this.or40np614() /** * Index 127: or-40-n-p6-15 */ or40np615 = (): string | undefined => { return undefined } f127 = (): string | undefined => this.or40np615() /** * Index 128: or-40-n-p6-16 */ or40np616 = (): string | undefined => { return undefined } f128 = (): string | undefined => this.or40np616() /** * Index 129: or-40-n-p6-17 */ or40np617 = (): string | undefined => { return undefined } f129 = (): string | undefined => this.or40np617() /** * Index 130: or-40-n-p6-18 */ or40np618 = (): string | undefined => { return undefined } f130 = (): string | undefined => this.or40np618() /** * Index 131: or-40-n-p7-1 */ or40np71 = (): string | undefined => { return undefined } f131 = (): string | undefined => this.or40np71() /** * Index 132: or-40-n-p7-2 */ or40np72 = (): string | undefined => { return undefined } f132 = (): string | undefined => this.or40np72() /** * Index 133: or-40-n-p7-3 */ or40np73 = (): string | undefined => { return undefined } f133 = (): string | undefined => this.or40np73() /** * Index 134: or-40-n-p7-4 */ or40np74 = (): string | undefined => { return undefined } f134 = (): string | undefined => this.or40np74() /** * Index 135: or-40-n-p7-5 */ or40np75 = (): boolean | undefined => { return undefined } f135 = (): boolean | undefined => this.or40np75() /** * Index 136: or-40-n-p7-6 */ or40np76 = (): boolean | undefined => { return undefined } f136 = (): boolean | undefined => this.or40np76() /** * Index 137: or-40-n-p7-7 */ or40np77 = (): boolean | undefined => { return undefined } f137 = (): boolean | undefined => this.or40np77() /** * Index 138: or-40-n-p7-8 */ or40np78 = (): string | undefined => { return undefined } f138 = (): string | undefined => this.or40np78() /** * Index 139: or-40-n-p7-9 */ or40np79 = (): string | undefined => { return undefined } f139 = (): string | undefined => this.or40np79() /** * Index 140: or-40-n-p7-10 */ or40np710 = (): string | undefined => { return undefined } f140 = (): string | undefined => this.or40np710() /** * Index 141: or-40-n-p7-11 */ or40np711 = (): string | undefined => { return undefined } f141 = (): string | undefined => this.or40np711() /** * Index 142: or-40-n-p7-12 */ or40np712 = (): string | undefined => { return undefined } f142 = (): string | undefined => this.or40np712() /** * Index 143: or-40-n-p7-13 */ or40np713 = (): string | undefined => { return undefined } f143 = (): string | undefined => this.or40np713() /** * Index 144: or-40-n-p7-14 */ or40np714 = (): string | undefined => { return undefined } f144 = (): string | undefined => this.or40np714() /** * Index 145: or-40-n-p7-15 */ or40np715 = (): string | undefined => { return undefined } f145 = (): string | undefined => this.or40np715() /** * Index 146: or-40-n-p8-1 */ or40np81 = (): string | undefined => { return undefined } f146 = (): string | undefined => this.or40np81() /** * Index 147: or-40-n-p8-2 */ or40np82 = (): string | undefined => { return undefined } f147 = (): string | undefined => this.or40np82() /** * Index 148: or-40-n-p8-3 */ or40np83 = (): string | undefined => { return undefined } f148 = (): string | undefined => this.or40np83() /** * Index 149: or-40-n-p8-4 */ or40np84 = (): string | undefined => { return undefined } f149 = (): string | undefined => this.or40np84() /** * Index 150: or-40-n-p8-5 */ or40np85 = (): string | undefined => { return undefined } f150 = (): string | undefined => this.or40np85() /** * Index 151: or-40-n-p8-6 */ or40np86 = (): string | undefined => { return undefined } f151 = (): string | undefined => this.or40np86() /** * Index 152: or-40-n-p8-7 */ or40np87 = (): string | undefined => { return undefined } f152 = (): string | undefined => this.or40np87() /** * Index 153: or-40-n-p8-8 */ or40np88 = (): string | undefined => { return undefined } f153 = (): string | undefined => this.or40np88() /** * Index 154: or-40-n-p8-9 */ or40np89 = (): string | undefined => { return undefined } f154 = (): string | undefined => this.or40np89() /** * Index 155: or-40-n-p8-10 */ or40np810 = (): string | undefined => { return undefined } f155 = (): string | undefined => this.or40np810() /** * Index 156: or-40-n-p8-11 */ or40np811 = (): string | undefined => { return undefined } f156 = (): string | undefined => this.or40np811() /** * Index 157: or-40-n-p8-12 */ or40np812 = (): string | undefined => { return undefined } f157 = (): string | undefined => this.or40np812() /** * Index 158: or-40-n-p8-13 */ or40np813 = (): string | undefined => { return undefined } f158 = (): string | undefined => this.or40np813() /** * Index 159: or-40-n-p9-1 */ or40np91 = (): string | undefined => { return undefined } f159 = (): string | undefined => this.or40np91() /** * Index 160: or-40-n-p9-2 */ or40np92 = (): string | undefined => { return undefined } f160 = (): string | undefined => this.or40np92() /** * Index 161: or-40-n-p9-3 */ or40np93 = (): boolean | undefined => { return undefined } f161 = (): boolean | undefined => this.or40np93() /** * Index 162: or-40-n-p9-4 */ or40np94 = (): string | undefined => { return undefined } f162 = (): string | undefined => this.or40np94() /** * Index 163: or-40-n-p9-5 */ or40np95 = (): string | undefined => { return undefined } f163 = (): string | undefined => this.or40np95() /** * Index 164: or-40-n-p9-6 */ or40np96 = (): string | undefined => { return undefined } f164 = (): string | undefined => this.or40np96() /** * Index 165: or-40-n-p9-7 */ or40np97 = (): string | undefined => { return undefined } f165 = (): string | undefined => this.or40np97() /** * Index 166: or-40-n-p9-8 */ or40np98 = (): string | undefined => { return undefined } f166 = (): string | undefined => this.or40np98() /** * Index 167: or-40-n-p9-9 */ or40np99 = (): string | undefined => { return undefined } f167 = (): string | undefined => this.or40np99() /** * Index 168: or-40-n-p9-10 */ or40np910 = (): string | undefined => { return undefined } f168 = (): string | undefined => this.or40np910() /** * Index 169: or-40-n-p9-11 */ or40np911 = (): string | undefined => { return undefined } f169 = (): string | undefined => this.or40np911() /** * Index 170: or-40-n-p9-12 */ or40np912 = (): boolean | undefined => { return undefined } f170 = (): boolean | undefined => this.or40np912() /** * Index 171: or-40-n-p9-15 */ or40np915 = (): string | undefined => { return undefined } f171 = (): string | undefined => this.or40np915() /** * Index 172: or-40-n-p9-16 */ or40np916 = (): string | undefined => { return undefined } f172 = (): string | undefined => this.or40np916() /** * Index 173: or-40-n-p9-17 */ or40np917 = (): boolean | undefined => { return undefined } f173 = (): boolean | undefined => this.or40np917() /** * Index 174: or-40-n-p9-18 */ or40np918 = (): string | undefined => { return undefined } f174 = (): string | undefined => this.or40np918() /** * Index 175: or-40-n-p10-1 */ or40np101 = (): string | undefined => { return undefined } f175 = (): string | undefined => this.or40np101() /** * Index 176: or-40-n-p10-2 */ or40np102 = (): string | undefined => { return undefined } f176 = (): string | undefined => this.or40np102() /** * Index 177: or-40-n-p10-3 */ or40np103 = (): string | undefined => { return undefined } f177 = (): string | undefined => this.or40np103() /** * Index 178: or-40-n-p10-4 */ or40np104 = (): string | undefined => { return undefined } f178 = (): string | undefined => this.or40np104() /** * Index 179: or-40-n-p10-5 */ or40np105 = (): string | undefined => { return undefined } f179 = (): string | undefined => this.or40np105() /** * Index 180: or-40-n-p10-6 */ or40np106 = (): string | undefined => { return undefined } f180 = (): string | undefined => this.or40np106() /** * Index 181: or-40-n-p10-7 */ or40np107 = (): string | undefined => { return undefined } f181 = (): string | undefined => this.or40np107() /** * Index 182: or-40-n-p10-8 */ or40np108 = (): string | undefined => { return undefined } f182 = (): string | undefined => this.or40np108() /** * Index 183: or-40-n-p10-9 */ or40np109 = (): string | undefined => { return undefined } f183 = (): string | undefined => this.or40np109() /** * Index 184: or-40-n-p10-10 */ or40np1010 = (): string | undefined => { return undefined } f184 = (): string | undefined => this.or40np1010() /** * Index 185: or-40-n-p10-11 */ or40np1011 = (): string | undefined => { return undefined } f185 = (): string | undefined => this.or40np1011() /** * Index 186: or-40-n-p11-1 */ or40np11_1 = (): string | undefined => { return undefined } f186 = (): string | undefined => this.or40np11_1() /** * Index 187: or-40_n_p1_35 */ or40np135 = (): string | undefined => { return undefined } f187 = (): string | undefined => this.or40np135() /** * Index 188: or-40-n-p1-31 */ or40np131 = (): string | undefined => { return undefined } f188 = (): string | undefined => this.or40np131() /** * Index 189: or-40-n-p2-15 */ or40np215 = (): string | undefined => { return undefined } f189 = (): string | undefined => this.or40np215() /** * Index 190: or-40-n-p2-22 */ or40np222 = (): string | undefined => { return undefined } f190 = (): string | undefined => this.or40np222() /** * Index 191: or-40-n-p2-29 */ or40np229 = (): string | undefined => { return undefined } f191 = (): string | undefined => this.or40np229() /** * Index 192: or-40_n_p9_14 */ or40np914 = (): string | undefined => { return undefined } f192 = (): string | undefined => this.or40np914() /** * Index 193: or-40-n-p1-32a */ or40np132a = (): string | undefined => { return undefined } f193 = (): string | undefined => this.or40np132a() /** * Index 194: or-40-n-p1-32b */ or40np132b = (): string | undefined => { return undefined } f194 = (): string | undefined => this.or40np132b() /** * Index 195: or-40-n-p10-12a */ or40np1012a = (): string | undefined => { return undefined } f195 = (): string | undefined => this.or40np1012a() /** * Index 196: or-40-n-p10-12b */ or40np1012b = (): string | undefined => { return undefined } f196 = (): string | undefined => this.or40np1012b() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.f26(), this.f27(), this.f28(), this.f29(), this.f30(), this.f31(), this.f32(), this.f33(), this.f34(), this.f35(), this.f36(), this.f37(), this.f38(), this.f39(), this.f40(), this.f41(), this.f42(), this.f43(), this.f44(), this.f45(), this.f46(), this.f47(), this.f48(), this.f49(), this.f50(), this.f51(), this.f52(), this.f53(), this.f54(), this.f55(), this.f56(), this.f57(), this.f58(), this.f59(), this.f60(), this.f61(), this.f62(), this.f63(), this.f64(), this.f65(), this.f66(), this.f67(), this.f68(), this.f69(), this.f70(), this.f71(), this.f72(), this.f73(), this.f74(), this.f75(), this.f76(), this.f77(), this.f78(), this.f79(), this.f80(), this.f81(), this.f82(), this.f83(), this.f84(), this.f85(), this.f86(), this.f87(), this.f88(), this.f89(), this.f90(), this.f91(), this.f92(), this.f93(), this.f94(), this.f95(), this.f96(), this.f97(), this.f98(), this.f99(), this.f100(), this.f101(), this.f102(), this.f103(), this.f104(), this.f105(), this.f106(), this.f107(), this.f108(), this.f109(), this.f110(), this.f111(), this.f112(), this.f113(), this.f114(), this.f115(), this.f116(), this.f117(), this.f118(), this.f119(), this.f120(), this.f121(), this.f122(), this.f123(), this.f124(), this.f125(), this.f126(), this.f127(), this.f128(), this.f129(), this.f130(), this.f131(), this.f132(), this.f133(), this.f134(), this.f135(), this.f136(), this.f137(), this.f138(), this.f139(), this.f140(), this.f141(), this.f142(), this.f143(), this.f144(), this.f145(), this.f146(), this.f147(), this.f148(), this.f149(), this.f150(), this.f151(), this.f152(), this.f153(), this.f154(), this.f155(), this.f156(), this.f157(), this.f158(), this.f159(), this.f160(), this.f161(), this.f162(), this.f163(), this.f164(), this.f165(), this.f166(), this.f167(), this.f168(), this.f169(), this.f170(), this.f171(), this.f172(), this.f173(), this.f174(), this.f175(), this.f176(), this.f177(), this.f178(), this.f179(), this.f180(), this.f181(), this.f182(), this.f183(), this.f184(), this.f185(), this.f186(), this.f187(), this.f188(), this.f189(), this.f190(), this.f191(), this.f192(), this.f193(), this.f194(), this.f195(), this.f196() ] } const makeOR40N = (f1040: F1040): OR40N => new OR40N(f1040) export default makeOR40N ================================================ FILE: src/forms/Y2021/stateForms/OR/OR40P.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class OR40P extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State formOrder = 0 methods: FormMethods constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'OR-40-P' this.state = 'OR' this.methods = new FormMethods(this) } attachments = (): Form[] => { // const pmt = this.payment() const result: Form[] = [] // if ((pmt ?? 0) > 0) { // result.push(this.il1040V) // } // if (this.scheduleEIC.isRequired()) { // result.push(this.scheduleEIC) // } // if (this.methods.stateWithholding() > 0) { // const ilwit = new ILWIT(this.info, this.f1040) // result.push(ilwit) // ilwit.attachments().forEach((f) => result.push(f)) // } return result } /** * Index 0: Button - Clear form */ ButtonClearform = (): string | undefined => { return undefined } f0 = (): string | undefined => this.ButtonClearform() /** * Index 1: or-40-n-p9-1 */ or40np91 = (): string | undefined => { return undefined } f1 = (): string | undefined => this.or40np91() /** * Index 2: or-40-n-p9-2 */ or40np92 = (): string | undefined => { return undefined } f2 = (): string | undefined => this.or40np92() /** * Index 3: or-40-n-p9-3 */ or40np93 = (): boolean | undefined => { return undefined } f3 = (): boolean | undefined => this.or40np93() /** * Index 4: or-40-n-p9-4 */ or40np94 = (): string | undefined => { return undefined } f4 = (): string | undefined => this.or40np94() /** * Index 5: or-40-n-p9-5 */ or40np95 = (): string | undefined => { return undefined } f5 = (): string | undefined => this.or40np95() /** * Index 6: or-40-n-p9-6 */ or40np96 = (): string | undefined => { return undefined } f6 = (): string | undefined => this.or40np96() /** * Index 7: or-40-n-p9-7 */ or40np97 = (): string | undefined => { return undefined } f7 = (): string | undefined => this.or40np97() /** * Index 8: or-40-n-p9-8 */ or40np98 = (): string | undefined => { return undefined } f8 = (): string | undefined => this.or40np98() /** * Index 9: or-40-n-p9-9 */ or40np99 = (): string | undefined => { return undefined } f9 = (): string | undefined => this.or40np99() /** * Index 10: or-40-n-p9-10 */ or40np910 = (): string | undefined => { return undefined } f10 = (): string | undefined => this.or40np910() /** * Index 11: or-40-n-p9-11 */ or40np911 = (): string | undefined => { return undefined } f11 = (): string | undefined => this.or40np911() /** * Index 12: or-40-n-p9-12 */ or40np912 = (): boolean | undefined => { return undefined } f12 = (): boolean | undefined => this.or40np912() /** * Index 13: or-40-n-p9-15 */ or40np915 = (): string | undefined => { return undefined } f13 = (): string | undefined => this.or40np915() /** * Index 14: or-40-n-p9-16 */ or40np916 = (): string | undefined => { return undefined } f14 = (): string | undefined => this.or40np916() /** * Index 15: or-40-n-p9-17 */ or40np917 = (): boolean | undefined => { return undefined } f15 = (): boolean | undefined => this.or40np917() /** * Index 16: or-40-n-p9-18 */ or40np918 = (): string | undefined => { return undefined } f16 = (): string | undefined => this.or40np918() /** * Index 17: or-40-n-p10-1 */ or40np101 = (): string | undefined => { return undefined } f17 = (): string | undefined => this.or40np101() /** * Index 18: or-40-n-p10-2 */ or40np102 = (): string | undefined => { return undefined } f18 = (): string | undefined => this.or40np102() /** * Index 19: or-40-n-p10-3 */ or40np103 = (): string | undefined => { return undefined } f19 = (): string | undefined => this.or40np103() /** * Index 20: or-40-n-p10-4 */ or40np104 = (): string | undefined => { return undefined } f20 = (): string | undefined => this.or40np104() /** * Index 21: or-40-n-p10-5 */ or40np105 = (): string | undefined => { return undefined } f21 = (): string | undefined => this.or40np105() /** * Index 22: or-40-n-p10-6 */ or40np106 = (): string | undefined => { return undefined } f22 = (): string | undefined => this.or40np106() /** * Index 23: or-40-n-p10-7 */ or40np107 = (): string | undefined => { return undefined } f23 = (): string | undefined => this.or40np107() /** * Index 24: or-40-n-p10-8 */ or40np108 = (): string | undefined => { return undefined } f24 = (): string | undefined => this.or40np108() /** * Index 25: or-40-n-p10-9 */ or40np109 = (): string | undefined => { return undefined } f25 = (): string | undefined => this.or40np109() /** * Index 26: or-40-n-p10-10 */ or40np1010 = (): string | undefined => { return undefined } f26 = (): string | undefined => this.or40np1010() /** * Index 27: or-40-n-p10-11 */ or40np1011 = (): string | undefined => { return undefined } f27 = (): string | undefined => this.or40np1011() /** * Index 28: or-40-n-p11-1 */ or40np111 = (): string | undefined => { return undefined } f28 = (): string | undefined => this.or40np111() /** * Index 29: or-40-p1-1 */ or40p11 = (): string | undefined => { return undefined } f29 = (): string | undefined => this.or40p11() /** * Index 30: or-40-p1-2 */ or40p12 = (): boolean | undefined => { return undefined } f30 = (): boolean | undefined => this.or40p12() /** * Index 31: or-40-p1-3 */ or40p13 = (): string | undefined => { return undefined } f31 = (): string | undefined => this.or40p13() /** * Index 32: or-40-p1-4 */ or40p14 = (): boolean | undefined => { return undefined } f32 = (): boolean | undefined => this.or40p14() /** * Index 33: or-40-p1-5 */ or40p15 = (): boolean | undefined => { return undefined } f33 = (): boolean | undefined => this.or40p15() /** * Index 34: or-40-p1-6 */ or40p16 = (): boolean | undefined => { return undefined } f34 = (): boolean | undefined => this.or40p16() /** * Index 35: or-40-p1-7 */ or40p17 = (): boolean | undefined => { return undefined } f35 = (): boolean | undefined => this.or40p17() /** * Index 36: or-40-p1-8 */ or40p18 = (): boolean | undefined => { return undefined } f36 = (): boolean | undefined => this.or40p18() /** * Index 37: or-40-p1-9 */ or40p19 = (): boolean | undefined => { return undefined } f37 = (): boolean | undefined => this.or40p19() /** * Index 38: or-40-p1-10 */ or40p110 = (): boolean | undefined => { return undefined } f38 = (): boolean | undefined => this.or40p110() /** * Index 39: or-40-p1-11 */ or40p111 = (): boolean | undefined => { return undefined } f39 = (): boolean | undefined => this.or40p111() /** * Index 40: or-40-p1-12 */ or40p112 = (): boolean | undefined => { return undefined } f40 = (): boolean | undefined => this.or40p112() /** * Index 41: or-40-p1-13 */ or40p113 = (): string | undefined => { return undefined } f41 = (): string | undefined => this.or40p113() /** * Index 42: or-40-p1-14 */ or40p114 = (): string | undefined => { return undefined } f42 = (): string | undefined => this.or40p114() /** * Index 43: or-40-p1-15 */ or40p115 = (): string | undefined => { return undefined } f43 = (): string | undefined => this.or40p115() /** * Index 44: or-40-p1-16 */ or40p116 = (): string | undefined => { return undefined } f44 = (): string | undefined => this.or40p116() /** * Index 45: or-40-p1-17 */ or40p117 = (): string | undefined => { return undefined } f45 = (): string | undefined => this.or40p117() /** * Index 46: or-40-p1-22 */ or40p122 = (): boolean | undefined => { return undefined } f46 = (): boolean | undefined => this.or40p122() /** * Index 47: or-40-p1-23 */ or40p123 = (): string | undefined => { return undefined } f47 = (): string | undefined => this.or40p123() /** * Index 48: or-40-p1-24 */ or40p124 = (): string | undefined => { return undefined } f48 = (): string | undefined => this.or40p124() /** * Index 49: or-40-p1-25 */ or40p125 = (): string | undefined => { return undefined } f49 = (): string | undefined => this.or40p125() /** * Index 50: or-40-p1-26 */ or40p126 = (): string | undefined => { return undefined } f50 = (): string | undefined => this.or40p126() /** * Index 51: or-40-p1-27 */ or40p127 = (): string | undefined => { return undefined } f51 = (): string | undefined => this.or40p127() /** * Index 52: or-40-p1-28 */ or40p128 = (): boolean | undefined => { return undefined } f52 = (): boolean | undefined => this.or40p128() /** * Index 53: or-40-p1-29 */ or40p129 = (): boolean | undefined => { return undefined } f53 = (): boolean | undefined => this.or40p129() /** * Index 54: or-40-p1-30 */ or40p130 = (): boolean | undefined => { return undefined } f54 = (): boolean | undefined => this.or40p130() /** * Index 55: or-40-p1-31 */ or40p131 = (): string | undefined => { return undefined } f55 = (): string | undefined => this.or40p131() /** * Index 56: or-40-p1-32 */ or40p132 = (): string | undefined => { return undefined } f56 = (): string | undefined => this.or40p132() /** * Index 57: or-40-p1-35 */ or40p135 = (): string | undefined => { return undefined } f57 = (): string | undefined => this.or40p135() /** * Index 58: or-40-p1-36 */ or40p136 = (): string | undefined => { return undefined } f58 = (): string | undefined => this.or40p136() /** * Index 59: or-40-p2-6 */ or40p26 = (): string | undefined => { return undefined } f59 = (): string | undefined => this.or40p26() /** * Index 60: or-40-p2-7 */ or40p27 = (): boolean | undefined => { return undefined } f60 = (): boolean | undefined => this.or40p27() /** * Index 61: or-40-p2-8 */ or40p28 = (): boolean | undefined => { return undefined } f61 = (): boolean | undefined => this.or40p28() /** * Index 62: or-40-p2-9 */ or40p29 = (): boolean | undefined => { return undefined } f62 = (): boolean | undefined => this.or40p29() /** * Index 63: or-40-p2-10 */ or40p210 = (): string | undefined => { return undefined } f63 = (): string | undefined => this.or40p210() /** * Index 64: or-40-p2-11 */ or40p211 = (): boolean | undefined => { return undefined } f64 = (): boolean | undefined => this.or40p211() /** * Index 65: or-40-p2-12 */ or40p212 = (): boolean | undefined => { return undefined } f65 = (): boolean | undefined => this.or40p212() /** * Index 66: or-40-p2-13 */ or40p213 = (): boolean | undefined => { return undefined } f66 = (): boolean | undefined => this.or40p213() /** * Index 67: or-40-p2-14 */ or40p214 = (): boolean | undefined => { return undefined } f67 = (): boolean | undefined => this.or40p214() /** * Index 68: or-40-p2-15 */ or40p215 = (): string | undefined => { return undefined } f68 = (): string | undefined => this.or40p215() /** * Index 69: or-40-p2-16 */ or40p216 = (): string | undefined => { return undefined } f69 = (): string | undefined => this.or40p216() /** * Index 70: or-40-p2-17 */ or40p217 = (): string | undefined => { return undefined } f70 = (): string | undefined => this.or40p217() /** * Index 71: or-40-p2-18 */ or40p218 = (): string | undefined => { return undefined } f71 = (): string | undefined => this.or40p218() /** * Index 72: or-40-p2-19 */ or40p219 = (): string | undefined => { return undefined } f72 = (): string | undefined => this.or40p219() /** * Index 73: or-40-p2-21 */ or40p221 = (): boolean | undefined => { return undefined } f73 = (): boolean | undefined => this.or40p221() /** * Index 74: or-40-p2-22 */ or40p222 = (): string | undefined => { return undefined } f74 = (): string | undefined => this.or40p222() /** * Index 75: or-40-p2-23 */ or40p223 = (): string | undefined => { return undefined } f75 = (): string | undefined => this.or40p223() /** * Index 76: or-40-p2-24 */ or40p224 = (): string | undefined => { return undefined } f76 = (): string | undefined => this.or40p224() /** * Index 77: or-40-p2-25 */ or40p225 = (): string | undefined => { return undefined } f77 = (): string | undefined => this.or40p225() /** * Index 78: or-40-p2-26 */ or40p226 = (): string | undefined => { return undefined } f78 = (): string | undefined => this.or40p226() /** * Index 79: or-40-p2-28 */ or40p228 = (): boolean | undefined => { return undefined } f79 = (): boolean | undefined => this.or40p228() /** * Index 80: or-40-p2-29 */ or40p229 = (): string | undefined => { return undefined } f80 = (): string | undefined => this.or40p229() /** * Index 81: or-40-p2-30 */ or40p230 = (): string | undefined => { return undefined } f81 = (): string | undefined => this.or40p230() /** * Index 82: or-40-p2-31 */ or40p231 = (): string | undefined => { return undefined } f82 = (): string | undefined => this.or40p231() /** * Index 83: or-40-p2-32 */ or40p232 = (): string | undefined => { return undefined } f83 = (): string | undefined => this.or40p232() /** * Index 84: or-40-p2-33 */ or40p233 = (): string | undefined => { return undefined } f84 = (): string | undefined => this.or40p233() /** * Index 85: or-40-p2-35 */ or40p235 = (): boolean | undefined => { return undefined } f85 = (): boolean | undefined => this.or40p235() /** * Index 86: or-40-p2-36 */ or40p236 = (): string | undefined => { return undefined } f86 = (): string | undefined => this.or40p236() /** * Index 87: or-40-p2-37 */ or40p237 = (): string | undefined => { return undefined } f87 = (): string | undefined => this.or40p237() /** * Index 88: or-40-p3-1 */ or40p31 = (): string | undefined => { return undefined } f88 = (): string | undefined => this.or40p31() /** * Index 89: or-40-p3-2 */ or40p32 = (): string | undefined => { return undefined } f89 = (): string | undefined => this.or40p32() /** * Index 90: or-40-p3-3 */ or40p33 = (): string | undefined => { return undefined } f90 = (): string | undefined => this.or40p33() /** * Index 91: or-40-p3-4 */ or40p34 = (): string | undefined => { return undefined } f91 = (): string | undefined => this.or40p34() /** * Index 92: or-40-p3-5 */ or40p35 = (): string | undefined => { return undefined } f92 = (): string | undefined => this.or40p35() /** * Index 93: or-40-p3-6 */ or40p36 = (): string | undefined => { return undefined } f93 = (): string | undefined => this.or40p36() /** * Index 94: or-40-p3-7 */ or40p37 = (): string | undefined => { return undefined } f94 = (): string | undefined => this.or40p37() /** * Index 95: or-40-p3-8 */ or40p38 = (): string | undefined => { return undefined } f95 = (): string | undefined => this.or40p38() /** * Index 96: or-40-p3-9 */ or40p39 = (): string | undefined => { return undefined } f96 = (): string | undefined => this.or40p39() /** * Index 97: or-40-p3-10 */ or40p310 = (): string | undefined => { return undefined } f97 = (): string | undefined => this.or40p310() /** * Index 98: or-40-p3-11 */ or40p311 = (): string | undefined => { return undefined } f98 = (): string | undefined => this.or40p311() /** * Index 99: or-40-p3-12 */ or40p312 = (): string | undefined => { return undefined } f99 = (): string | undefined => this.or40p312() /** * Index 100: or-40-p3-13 */ or40p313 = (): string | undefined => { return undefined } f100 = (): string | undefined => this.or40p313() /** * Index 101: or-40-p3-14 */ or40p314 = (): string | undefined => { return undefined } f101 = (): string | undefined => this.or40p314() /** * Index 102: or-40-p3-15 */ or40p315 = (): string | undefined => { return undefined } f102 = (): string | undefined => this.or40p315() /** * Index 103: or-40-p3-16 */ or40p316 = (): string | undefined => { return undefined } f103 = (): string | undefined => this.or40p316() /** * Index 104: or-40-p3-17 */ or40p317 = (): string | undefined => { return undefined } f104 = (): string | undefined => this.or40p317() /** * Index 105: or-40-p4-4 */ or40p44 = (): string | undefined => { return undefined } f105 = (): string | undefined => this.or40p44() /** * Index 106: or-40-p4-5 */ or40p45 = (): string | undefined => { return undefined } f106 = (): string | undefined => this.or40p45() /** * Index 107: or-40-p4-6 */ or40p46 = (): string | undefined => { return undefined } f107 = (): string | undefined => this.or40p46() /** * Index 108: or-40-p4-7 */ or40p47 = (): string | undefined => { return undefined } f108 = (): string | undefined => this.or40p47() /** * Index 109: or-40-p4-8 */ or40p48 = (): string | undefined => { return undefined } f109 = (): string | undefined => this.or40p48() /** * Index 110: or-40-p4-9 */ or40p49 = (): string | undefined => { return undefined } f110 = (): string | undefined => this.or40p49() /** * Index 111: or-40-p4-10 */ or40p410 = (): string | undefined => { return undefined } f111 = (): string | undefined => this.or40p410() /** * Index 112: or-40-p4-11 */ or40p411 = (): string | undefined => { return undefined } f112 = (): string | undefined => this.or40p411() /** * Index 113: or-40-p4-12 */ or40p412 = (): string | undefined => { return undefined } f113 = (): string | undefined => this.or40p412() /** * Index 114: or-40-p4-13 */ or40p413 = (): string | undefined => { return undefined } f114 = (): string | undefined => this.or40p413() /** * Index 115: or-40-p4-14 */ or40p414 = (): string | undefined => { return undefined } f115 = (): string | undefined => this.or40p414() /** * Index 116: or-40-p4-15 */ or40p415 = (): string | undefined => { return undefined } f116 = (): string | undefined => this.or40p415() /** * Index 117: or-40-p4-16 */ or40p416 = (): string | undefined => { return undefined } f117 = (): string | undefined => this.or40p416() /** * Index 118: or-40-p4-1 */ or40p41 = (): string | undefined => { return undefined } f118 = (): string | undefined => this.or40p41() /** * Index 119: or-40-p4-2 */ or40p42 = (): string | undefined => { return undefined } f119 = (): string | undefined => this.or40p42() /** * Index 120: or-40-p4-3 */ or40p43 = (): string | undefined => { return undefined } f120 = (): string | undefined => this.or40p43() /** * Index 121: or-40-p5-1 */ or40p51 = (): string | undefined => { return undefined } f121 = (): string | undefined => this.or40p51() /** * Index 122: or-40-p5-2 */ or40p52 = (): string | undefined => { return undefined } f122 = (): string | undefined => this.or40p52() /** * Index 123: or-40-p5-3 */ or40p53 = (): string | undefined => { return undefined } f123 = (): string | undefined => this.or40p53() /** * Index 124: or-40-p5-4 */ or40p54 = (): string | undefined => { return undefined } f124 = (): string | undefined => this.or40p54() /** * Index 125: or-40-p5-5 */ or40p55 = (): string | undefined => { return undefined } f125 = (): string | undefined => this.or40p55() /** * Index 126: or-40-p5-6 */ or40p56 = (): string | undefined => { return undefined } f126 = (): string | undefined => this.or40p56() /** * Index 127: or-40-p5-7 */ or40p57 = (): string | undefined => { return undefined } f127 = (): string | undefined => this.or40p57() /** * Index 128: or-40-p5-8 */ or40p58 = (): string | undefined => { return undefined } f128 = (): string | undefined => this.or40p58() /** * Index 129: or-40-p5-9 */ or40p59 = (): string | undefined => { return undefined } f129 = (): string | undefined => this.or40p59() /** * Index 130: or-40-p5-10 */ or40p510 = (): string | undefined => { return undefined } f130 = (): string | undefined => this.or40p510() /** * Index 131: or-40-p5-11 */ or40p511 = (): string | undefined => { return undefined } f131 = (): string | undefined => this.or40p511() /** * Index 132: or-40-p5-12 */ or40p512 = (): string | undefined => { return undefined } f132 = (): string | undefined => this.or40p512() /** * Index 133: or-40-p5-13 */ or40p513 = (): string | undefined => { return undefined } f133 = (): string | undefined => this.or40p513() /** * Index 134: or-40-p5-14 */ or40p514 = (): string | undefined => { return undefined } f134 = (): string | undefined => this.or40p514() /** * Index 135: or-40-p5-15 */ or40p515 = (): string | undefined => { return undefined } f135 = (): string | undefined => this.or40p515() /** * Index 136: or-40-p5-16 */ or40p516 = (): string | undefined => { return undefined } f136 = (): string | undefined => this.or40p516() /** * Index 137: or-40-p6-1 */ or40p61 = (): string | undefined => { return undefined } f137 = (): string | undefined => this.or40p61() /** * Index 138: or-40-p6-2 */ or40p62 = (): string | undefined => { return undefined } f138 = (): string | undefined => this.or40p62() /** * Index 139: or-40-p6-3 */ or40p63 = (): string | undefined => { return undefined } f139 = (): string | undefined => this.or40p63() /** * Index 140: or-40-p6-4 */ or40p64 = (): string | undefined => { return undefined } f140 = (): string | undefined => this.or40p64() /** * Index 141: or-40-p6-5 */ or40p65 = (): string | undefined => { return undefined } f141 = (): string | undefined => this.or40p65() /** * Index 142: or-40-p6-6 */ or40p66 = (): string | undefined => { return undefined } f142 = (): string | undefined => this.or40p66() /** * Index 143: or-40-p6-7 */ or40p67 = (): string | undefined => { return undefined } f143 = (): string | undefined => this.or40p67() /** * Index 144: or-40-p6-8 */ or40p68 = (): string | undefined => { return undefined } f144 = (): string | undefined => this.or40p68() /** * Index 145: or-40-p6-9 */ or40p69 = (): string | undefined => { return undefined } f145 = (): string | undefined => this.or40p69() /** * Index 146: or-40-p6-10 */ or40p610 = (): string | undefined => { return undefined } f146 = (): string | undefined => this.or40p610() /** * Index 147: or-40-p6-11 */ or40p611 = (): string | undefined => { return undefined } f147 = (): string | undefined => this.or40p611() /** * Index 148: or-40-p6-12 */ or40p612 = (): string | undefined => { return undefined } f148 = (): string | undefined => this.or40p612() /** * Index 149: or-40-p6-13 */ or40p613 = (): boolean | undefined => { return undefined } f149 = (): boolean | undefined => this.or40p613() /** * Index 150: or-40-p6-14 */ or40p614 = (): boolean | undefined => { return undefined } f150 = (): boolean | undefined => this.or40p614() /** * Index 151: or-40-p6-15 */ or40p615 = (): boolean | undefined => { return undefined } f151 = (): boolean | undefined => this.or40p615() /** * Index 152: or-40-p6-16 */ or40p616 = (): boolean | undefined => { return undefined } f152 = (): boolean | undefined => this.or40p616() /** * Index 153: or-40-p6-17 */ or40p617 = (): string | undefined => { return undefined } f153 = (): string | undefined => this.or40p617() /** * Index 154: or-40-p6-18 */ or40p618 = (): string | undefined => { return undefined } f154 = (): string | undefined => this.or40p618() /** * Index 155: or-40-p7-1 */ or40p71 = (): string | undefined => { return undefined } f155 = (): string | undefined => this.or40p71() /** * Index 156: or-40-p7-2 */ or40p72 = (): string | undefined => { return undefined } f156 = (): string | undefined => this.or40p72() /** * Index 157: or-40-p7-3 */ or40p73 = (): string | undefined => { return undefined } f157 = (): string | undefined => this.or40p73() /** * Index 158: or-40-p7-4 */ or40p74 = (): string | undefined => { return undefined } f158 = (): string | undefined => this.or40p74() /** * Index 159: or-40-p7-5 */ or40p75 = (): boolean | undefined => { return undefined } f159 = (): boolean | undefined => this.or40p75() /** * Index 160: or-40-p7-7 */ or40p77 = (): boolean | undefined => { return undefined } f160 = (): boolean | undefined => this.or40p77() /** * Index 161: or-40-p7-8 */ or40p78 = (): boolean | undefined => { return undefined } f161 = (): boolean | undefined => this.or40p78() /** * Index 162: or-40-p7-9 */ or40p79 = (): string | undefined => { return undefined } f162 = (): string | undefined => this.or40p79() /** * Index 163: or-40-p7-10 */ or40p710 = (): string | undefined => { return undefined } f163 = (): string | undefined => this.or40p710() /** * Index 164: or-40-p7-11 */ or40p711 = (): string | undefined => { return undefined } f164 = (): string | undefined => this.or40p711() /** * Index 165: or-40-p7-12 */ or40p712 = (): string | undefined => { return undefined } f165 = (): string | undefined => this.or40p712() /** * Index 166: or-40-p7-16 */ or40p716 = (): string | undefined => { return undefined } f166 = (): string | undefined => this.or40p716() /** * Index 167: or-40-p7-17 */ or40p717 = (): string | undefined => { return undefined } f167 = (): string | undefined => this.or40p717() /** * Index 168: or-40-p9-1 */ or40p91 = (): string | undefined => { return undefined } f168 = (): string | undefined => this.or40p91() /** * Index 169: or-40-p9-2 */ or40p92 = (): string | undefined => { return undefined } f169 = (): string | undefined => this.or40p92() /** * Index 170: or-40-p9-3 */ or40p93 = (): string | undefined => { return undefined } f170 = (): string | undefined => this.or40p93() /** * Index 171: or-40-p9-4 */ or40p94 = (): string | undefined => { return undefined } f171 = (): string | undefined => this.or40p94() /** * Index 172: or-40-p9-5 */ or40p95 = (): string | undefined => { return undefined } f172 = (): string | undefined => this.or40p95() /** * Index 173: or-40-p9-6 */ or40p96 = (): string | undefined => { return undefined } f173 = (): string | undefined => this.or40p96() /** * Index 174: or-40-p9-7 */ or40p97 = (): string | undefined => { return undefined } f174 = (): string | undefined => this.or40p97() /** * Index 175: or-40-p9-8 */ or40p98 = (): string | undefined => { return undefined } f175 = (): string | undefined => this.or40p98() /** * Index 176: or-40-p9-9 */ or40p99 = (): string | undefined => { return undefined } f176 = (): string | undefined => this.or40p99() /** * Index 177: or-40-p9-10 */ or40p910 = (): string | undefined => { return undefined } f177 = (): string | undefined => this.or40p910() /** * Index 178: or-40-p9-11 */ or40p911 = (): string | undefined => { return undefined } f178 = (): string | undefined => this.or40p911() /** * Index 179: or-40-p9-12 */ or40p912 = (): string | undefined => { return undefined } f179 = (): string | undefined => this.or40p912() /** * Index 180: or-40-p9-13 */ or40p913 = (): string | undefined => { return undefined } f180 = (): string | undefined => this.or40p913() /** * Index 181: or-40-p1-18 */ or40p118 = (): string | undefined => { return undefined } f181 = (): string | undefined => this.or40p118() /** * Index 182: or-40-p1-19 */ or40p119 = (): string | undefined => { return undefined } f182 = (): string | undefined => this.or40p119() /** * Index 183: or-40-p1-20 */ or40p120 = (): boolean | undefined => { return undefined } f183 = (): boolean | undefined => this.or40p120() /** * Index 184: or-40-p1-21 */ or40p121 = (): boolean | undefined => { return undefined } f184 = (): boolean | undefined => this.or40p121() /** * Index 185: or-40_p2_1 */ or40p21 = (): string | undefined => { return undefined } f185 = (): string | undefined => this.or40p21() /** * Index 186: or-40-p1-33 */ or40p133 = (): string | undefined => { return undefined } f186 = (): string | undefined => this.or40p133() /** * Index 187: or-40-p2-20 */ or40p220 = (): string | undefined => { return undefined } f187 = (): string | undefined => this.or40p220() /** * Index 188: or-40-p2-27 */ or40p227 = (): string | undefined => { return undefined } f188 = (): string | undefined => this.or40p227() /** * Index 189: or-40-p2-34 */ or40p234 = (): string | undefined => { return undefined } f189 = (): string | undefined => this.or40p234() /** * Index 190: or-40_n_p9_14 */ or40np914 = (): string | undefined => { return undefined } f190 = (): string | undefined => this.or40np914() /** * Index 191: or-40-p7-15 */ or40p715 = (): string | undefined => { return undefined } f191 = (): string | undefined => this.or40p715() /** * Index 192: or-40-p1-34a */ or40p134a = (): string | undefined => { return undefined } f192 = (): string | undefined => this.or40p134a() /** * Index 193: or-40-p1-34b */ or40p134b = (): string | undefined => { return undefined } f193 = (): string | undefined => this.or40p134b() /** * Index 194: or-40-n-p10-12a */ or40np1012a = (): string | undefined => { return undefined } f194 = (): string | undefined => this.or40np1012a() /** * Index 195: or-40-n-p10-12b */ or40np1012b = (): string | undefined => { return undefined } f195 = (): string | undefined => this.or40np1012b() /** * Index 196: or-40-p7-13 */ or40p713 = (): string | undefined => { return undefined } f196 = (): string | undefined => this.or40p713() /** * Index 197: or-40-p7-14 */ or40p714 = (): string | undefined => { return undefined } f197 = (): string | undefined => this.or40p714() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.f26(), this.f27(), this.f28(), this.f29(), this.f30(), this.f31(), this.f32(), this.f33(), this.f34(), this.f35(), this.f36(), this.f37(), this.f38(), this.f39(), this.f40(), this.f41(), this.f42(), this.f43(), this.f44(), this.f45(), this.f46(), this.f47(), this.f48(), this.f49(), this.f50(), this.f51(), this.f52(), this.f53(), this.f54(), this.f55(), this.f56(), this.f57(), this.f58(), this.f59(), this.f60(), this.f61(), this.f62(), this.f63(), this.f64(), this.f65(), this.f66(), this.f67(), this.f68(), this.f69(), this.f70(), this.f71(), this.f72(), this.f73(), this.f74(), this.f75(), this.f76(), this.f77(), this.f78(), this.f79(), this.f80(), this.f81(), this.f82(), this.f83(), this.f84(), this.f85(), this.f86(), this.f87(), this.f88(), this.f89(), this.f90(), this.f91(), this.f92(), this.f93(), this.f94(), this.f95(), this.f96(), this.f97(), this.f98(), this.f99(), this.f100(), this.f101(), this.f102(), this.f103(), this.f104(), this.f105(), this.f106(), this.f107(), this.f108(), this.f109(), this.f110(), this.f111(), this.f112(), this.f113(), this.f114(), this.f115(), this.f116(), this.f117(), this.f118(), this.f119(), this.f120(), this.f121(), this.f122(), this.f123(), this.f124(), this.f125(), this.f126(), this.f127(), this.f128(), this.f129(), this.f130(), this.f131(), this.f132(), this.f133(), this.f134(), this.f135(), this.f136(), this.f137(), this.f138(), this.f139(), this.f140(), this.f141(), this.f142(), this.f143(), this.f144(), this.f145(), this.f146(), this.f147(), this.f148(), this.f149(), this.f150(), this.f151(), this.f152(), this.f153(), this.f154(), this.f155(), this.f156(), this.f157(), this.f158(), this.f159(), this.f160(), this.f161(), this.f162(), this.f163(), this.f164(), this.f165(), this.f166(), this.f167(), this.f168(), this.f169(), this.f170(), this.f171(), this.f172(), this.f173(), this.f174(), this.f175(), this.f176(), this.f177(), this.f178(), this.f179(), this.f180(), this.f181(), this.f182(), this.f183(), this.f184(), this.f185(), this.f186(), this.f187(), this.f188(), this.f189(), this.f190(), this.f191(), this.f192(), this.f193(), this.f194(), this.f195(), this.f196(), this.f197() ] } const makeOR40P = (f1040: F1040): OR40P => new OR40P(f1040) export default makeOR40P ================================================ FILE: src/forms/Y2021/stateForms/OR/OR40V.ts ================================================ import Form from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { OR40 } from './OR40' import { Field } from 'ustaxes/core/pdfFiller' import { State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export default class OR40V extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State or40: OR40 formOrder = -1 attachments: () => Form[] = () => [] constructor(f1040: F1040, or40: OR40) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'OR-40-V' this.state = 'OR' this.or40 = or40 } /** * Index 0: Button - Clear form */ ButtonClearform = (): string | undefined => { return undefined } f0 = (): string | undefined => this.ButtonClearform() /** * Index 1: or-40-v-p1-1 */ or40vp11 = (): string | undefined => { return undefined } f1 = (): string | undefined => this.or40vp11() /** * Index 2: or-40-v-p1-2 */ or40vp12 = (): string | undefined => { return undefined } f2 = (): string | undefined => this.or40vp12() /** * Index 3: or-40-v-p1-3 */ or40vp13 = (): string | undefined => { return undefined } f3 = (): string | undefined => this.or40vp13() /** * Index 4: or-40-v-p1-4 */ or40vp14 = (): string | undefined => { return undefined } f4 = (): string | undefined => this.or40vp14() /** * Index 5: or-40-v-p1-5 */ or40vp15 = (): string | undefined => { return undefined } f5 = (): string | undefined => this.or40vp15() /** * Index 6: or-40-v-p1-6 */ or40vp16 = (): string | undefined => { return undefined } f6 = (): string | undefined => this.or40vp16() /** * Index 7: or-40-v-p1-7 */ or40vp17 = (): string | undefined => { return undefined } f7 = (): string | undefined => this.or40vp17() /** * Index 8: or-40-v-p1-8 */ or40vp18 = (): string | undefined => { return undefined } f8 = (): string | undefined => this.or40vp18() /** * Index 9: or-40-v-p1-9 */ or40vp19 = (): string | undefined => { return undefined } f9 = (): string | undefined => this.or40vp19() /** * Index 10: or-40-v-p1-10 */ or40vp110 = (): string | undefined => { return undefined } f10 = (): string | undefined => this.or40vp110() /** * Index 11: or-40-v-p1-11 */ or40vp111 = (): string | undefined => { return undefined } f11 = (): string | undefined => this.or40vp111() /** * Index 12: or-40-v-p1-12 */ or40vp112 = (): string | undefined => { return undefined } f12 = (): string | undefined => this.or40vp112() /** * Index 13: or-40-v-p1-13 */ or40vp113 = (): string | undefined => { return undefined } f13 = (): string | undefined => this.or40vp113() /** * Index 14: or-40-v-p1-14 */ or40vp114 = (): string | undefined => { return undefined } f14 = (): string | undefined => this.or40vp114() /** * Index 15: or-40-v-p1-15 */ or40vp115 = (): string | undefined => { return undefined } f15 = (): string | undefined => this.or40vp115() /** * Index 16: or-40-v-p1-group1 */ or40vp1group1 = (): string | undefined => { return undefined } f16 = (): string | undefined => this.or40vp1group1() /** * Index 17: or-40-v-p1-16 */ or40vp116 = (): string | undefined => { return undefined } f17 = (): string | undefined => this.or40vp116() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17() ] } ================================================ FILE: src/forms/Y2021/stateForms/OR/ORASC.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class ORASC extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State formOrder = 0 methods: FormMethods constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'OR-ASC' this.state = 'OR' this.methods = new FormMethods(this) } attachments = (): Form[] => { // const pmt = this.payment() const result: Form[] = [] // if ((pmt ?? 0) > 0) { // result.push(this.il1040V) // } // if (this.scheduleEIC.isRequired()) { // result.push(this.scheduleEIC) // } // if (this.methods.stateWithholding() > 0) { // const ilwit = new ILWIT(this.info, this.f1040) // result.push(ilwit) // ilwit.attachments().forEach((f) => result.push(f)) // } return result } /** * Index 0: or-asc-p1-2 */ orascp12 = (): string | undefined => { return undefined } f0 = (): string | undefined => this.orascp12() /** * Index 1: or-asc-p1-3 */ orascp13 = (): string | undefined => { return undefined } f1 = (): string | undefined => this.orascp13() /** * Index 2: or-asc-p1-4 */ orascp14 = (): string | undefined => { return undefined } f2 = (): string | undefined => this.orascp14() /** * Index 3: or-asc-p1-5 */ orascp15 = (): string | undefined => { return undefined } f3 = (): string | undefined => this.orascp15() /** * Index 4: or-asc-p1-6 */ orascp16 = (): string | undefined => { return undefined } f4 = (): string | undefined => this.orascp16() /** * Index 5: or-asc-p1-7 */ orascp17 = (): string | undefined => { return undefined } f5 = (): string | undefined => this.orascp17() /** * Index 6: or-asc-p1-8 */ orascp18 = (): string | undefined => { return undefined } f6 = (): string | undefined => this.orascp18() /** * Index 7: or-asc-p1-12 */ orascp112 = (): string | undefined => { return undefined } f7 = (): string | undefined => this.orascp112() /** * Index 8: or-asc-p1-11 */ orascp111 = (): string | undefined => { return undefined } f8 = (): string | undefined => this.orascp111() /** * Index 9: or-asc-p1-1 */ orascp11 = (): string | undefined => { return undefined } f9 = (): string | undefined => this.orascp11() /** * Index 10: or-asc-p2-1 */ orascp21 = (): string | undefined => { return undefined } f10 = (): string | undefined => this.orascp21() /** * Index 11: or-asc-p2-3 */ orascp23 = (): string | undefined => { return undefined } f11 = (): string | undefined => this.orascp23() /** * Index 12: or-asc-p2-4 */ orascp24 = (): string | undefined => { return undefined } f12 = (): string | undefined => this.orascp24() /** * Index 13: or-asc-p2-6 */ orascp26 = (): string | undefined => { return undefined } f13 = (): string | undefined => this.orascp26() /** * Index 14: or-asc-p2-7 */ orascp27 = (): string | undefined => { return undefined } f14 = (): string | undefined => this.orascp27() /** * Index 15: or-asc-p1-1a */ orascp11a = (): string | undefined => { return undefined } f15 = (): string | undefined => this.orascp11a() /** * Index 16: or-asc-p1-1b */ orascp11b = (): string | undefined => { return undefined } f16 = (): string | undefined => this.orascp11b() /** * Index 17: or-asc-p1-9 */ orascp19 = (): string | undefined => { return undefined } f17 = (): string | undefined => this.orascp19() /** * Index 18: or-asc-p1-10 */ orascp110 = (): string | undefined => { return undefined } f18 = (): string | undefined => this.orascp110() /** * Index 19: or-asc-p2-9 */ orascp29 = (): string | undefined => { return undefined } f19 = (): string | undefined => this.orascp29() /** * Index 20: or-asc-p2-10 */ orascp210 = (): string | undefined => { return undefined } f20 = (): string | undefined => this.orascp210() /** * Index 21: or-asc-p2-12 */ orascp212 = (): string | undefined => { return undefined } f21 = (): string | undefined => this.orascp212() /** * Index 22: or-asc-p2-13 */ orascp213 = (): string | undefined => { return undefined } f22 = (): string | undefined => this.orascp213() /** * Index 23: or-asc-p2-15 */ orascp215 = (): string | undefined => { return undefined } f23 = (): string | undefined => this.orascp215() /** * Index 24: or-asc-p2-16 */ orascp216 = (): string | undefined => { return undefined } f24 = (): string | undefined => this.orascp216() /** * Index 25: or-asc-p2-17 */ orascp217 = (): string | undefined => { return undefined } f25 = (): string | undefined => this.orascp217() /** * Index 26: or-asc-p2-18 */ orascp218 = (): string | undefined => { return undefined } f26 = (): string | undefined => this.orascp218() /** * Index 27: or-asc-p2-19 */ orascp219 = (): string | undefined => { return undefined } f27 = (): string | undefined => this.orascp219() /** * Index 28: or-asc-p2-20 */ orascp220 = (): string | undefined => { return undefined } f28 = (): string | undefined => this.orascp220() /** * Index 29: or-asc-p2-21 */ orascp221 = (): string | undefined => { return undefined } f29 = (): string | undefined => this.orascp221() /** * Index 30: or-asc-p2-22 */ orascp222 = (): string | undefined => { return undefined } f30 = (): string | undefined => this.orascp222() /** * Index 31: or-asc-p2-23 */ orascp223 = (): string | undefined => { return undefined } f31 = (): string | undefined => this.orascp223() /** * Index 32: or-asc-p2-24 */ orascp224 = (): string | undefined => { return undefined } f32 = (): string | undefined => this.orascp224() /** * Index 33: or-asc-p2-25 */ orascp225 = (): string | undefined => { return undefined } f33 = (): string | undefined => this.orascp225() /** * Index 34: or-asc-p3-1 */ orascp31 = (): string | undefined => { return undefined } f34 = (): string | undefined => this.orascp31() /** * Index 35: or-asc-p3-2 */ orascp32 = (): string | undefined => { return undefined } f35 = (): string | undefined => this.orascp32() /** * Index 36: or-asc-p3-3 */ orascp33 = (): string | undefined => { return undefined } f36 = (): string | undefined => this.orascp33() /** * Index 37: or-asc-p3-4 */ orascp34 = (): string | undefined => { return undefined } f37 = (): string | undefined => this.orascp34() /** * Index 38: or-asc-p3-5 */ orascp35 = (): string | undefined => { return undefined } f38 = (): string | undefined => this.orascp35() /** * Index 39: or-asc-p3-6 */ orascp36 = (): string | undefined => { return undefined } f39 = (): string | undefined => this.orascp36() /** * Index 40: or-asc-p3-8 */ orascp38 = (): string | undefined => { return undefined } f40 = (): string | undefined => this.orascp38() /** * Index 41: or-asc-p3-10 */ orascp310 = (): string | undefined => { return undefined } f41 = (): string | undefined => this.orascp310() /** * Index 42: Button - Clear form */ ButtonClearform = (): string | undefined => { return undefined } f42 = (): string | undefined => this.ButtonClearform() /** * Index 43: or-asc-p2-2 */ orascp22 = (): string | undefined => { return undefined } f43 = (): string | undefined => this.orascp22() /** * Index 44: or-asc-p2-5 */ orascp25 = (): string | undefined => { return undefined } f44 = (): string | undefined => this.orascp25() /** * Index 45: or-asc-p2-8 */ orascp28 = (): string | undefined => { return undefined } f45 = (): string | undefined => this.orascp28() /** * Index 46: or-asc-p2-11 */ orascp211 = (): string | undefined => { return undefined } f46 = (): string | undefined => this.orascp211() /** * Index 47: or-asc-p2-14 */ orascp214 = (): string | undefined => { return undefined } f47 = (): string | undefined => this.orascp214() /** * Index 48: or-asc-p3-7 */ orascp37 = (): string | undefined => { return undefined } f48 = (): string | undefined => this.orascp37() /** * Index 49: or-asc-p3-9 */ orascp39 = (): string | undefined => { return undefined } f49 = (): string | undefined => this.orascp39() /** * Index 50: or-asc-p3-11 */ orascp311 = (): string | undefined => { return undefined } f50 = (): string | undefined => this.orascp311() /** * Index 51: or-asc-p3-12 */ orascp312 = (): string | undefined => { return undefined } f51 = (): string | undefined => this.orascp312() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.f26(), this.f27(), this.f28(), this.f29(), this.f30(), this.f31(), this.f32(), this.f33(), this.f34(), this.f35(), this.f36(), this.f37(), this.f38(), this.f39(), this.f40(), this.f41(), this.f42(), this.f43(), this.f44(), this.f45(), this.f46(), this.f47(), this.f48(), this.f49(), this.f50(), this.f51() ] } const makeORASC = (f1040: F1040): ORASC => new ORASC(f1040) export default makeORASC ================================================ FILE: src/forms/Y2021/stateForms/OR/ORASCNP.ts ================================================ import Form, { FormMethods } from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class ORASCNP extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State formOrder = 0 methods: FormMethods constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'OR-ASC-NP' this.state = 'OR' this.methods = new FormMethods(this) } attachments = (): Form[] => { // const pmt = this.payment() const result: Form[] = [] // if ((pmt ?? 0) > 0) { // result.push(this.il1040V) // } // if (this.scheduleEIC.isRequired()) { // result.push(this.scheduleEIC) // } // if (this.methods.stateWithholding() > 0) { // const ilwit = new ILWIT(this.info, this.f1040) // result.push(ilwit) // ilwit.attachments().forEach((f) => result.push(f)) // } return result } /** * Index 0: or-asc-p1-1 */ orascp11 = (): string | undefined => { return undefined } f0 = (): string | undefined => this.orascp11() /** * Index 1: or-asc-p1-1a */ orascp11a = (): string | undefined => { return undefined } f1 = (): string | undefined => this.orascp11a() /** * Index 2: or-asc-p1-1b */ orascp11b = (): string | undefined => { return undefined } f2 = (): string | undefined => this.orascp11b() /** * Index 3: or-asc-p1-10 */ orascp110 = (): string | undefined => { return undefined } f3 = (): string | undefined => this.orascp110() /** * Index 4: or-asc-p3-1 */ orascp31 = (): string | undefined => { return undefined } f4 = (): string | undefined => this.orascp31() /** * Index 5: or-asc-p3-2 */ orascp32 = (): string | undefined => { return undefined } f5 = (): string | undefined => this.orascp32() /** * Index 6: or-asc-p3-3 */ orascp33 = (): string | undefined => { return undefined } f6 = (): string | undefined => this.orascp33() /** * Index 7: or-asc-p3-4 */ orascp34 = (): string | undefined => { return undefined } f7 = (): string | undefined => this.orascp34() /** * Index 8: or-asc-p3-5 */ orascp35 = (): string | undefined => { return undefined } f8 = (): string | undefined => this.orascp35() /** * Index 9: or-asc-p3-6 */ orascp36 = (): string | undefined => { return undefined } f9 = (): string | undefined => this.orascp36() /** * Index 10: or-asc-p3-7 */ orascp37 = (): string | undefined => { return undefined } f10 = (): string | undefined => this.orascp37() /** * Index 11: or-asc-p1-4 */ orascp14 = (): string | undefined => { return undefined } f11 = (): string | undefined => this.orascp14() /** * Index 12: Button - Clear form */ ButtonClearform = (): string | undefined => { return undefined } f12 = (): string | undefined => this.ButtonClearform() /** * Index 13: or-asc-p3-8 */ orascp38 = (): string | undefined => { return undefined } f13 = (): string | undefined => this.orascp38() /** * Index 14: or-asc-p3-10 */ orascp310 = (): string | undefined => { return undefined } f14 = (): string | undefined => this.orascp310() /** * Index 15: or-asc-p3-11 */ orascp311 = (): string | undefined => { return undefined } f15 = (): string | undefined => this.orascp311() /** * Index 16: or-asc-p3-13 */ orascp313 = (): string | undefined => { return undefined } f16 = (): string | undefined => this.orascp313() /** * Index 17: or-asc-p3-14 */ orascp314 = (): string | undefined => { return undefined } f17 = (): string | undefined => this.orascp314() /** * Index 18: or-asc-p3-17 */ orascp317 = (): string | undefined => { return undefined } f18 = (): string | undefined => this.orascp317() /** * Index 19: or-asc-p3-20 */ orascp320 = (): string | undefined => { return undefined } f19 = (): string | undefined => this.orascp320() /** * Index 20: or-asc-p3-9 */ orascp39 = (): string | undefined => { return undefined } f20 = (): string | undefined => this.orascp39() /** * Index 21: or-asc-p3-12 */ orascp312 = (): string | undefined => { return undefined } f21 = (): string | undefined => this.orascp312() /** * Index 22: or-asc-p3-15 */ orascp315 = (): string | undefined => { return undefined } f22 = (): string | undefined => this.orascp315() /** * Index 23: or-asc-p3-18 */ orascp318 = (): string | undefined => { return undefined } f23 = (): string | undefined => this.orascp318() /** * Index 24: or-asc-p3-21 */ orascp321 = (): string | undefined => { return undefined } f24 = (): string | undefined => this.orascp321() /** * Index 25: or-asc-p1-2 */ orascp12 = (): string | undefined => { return undefined } f25 = (): string | undefined => this.orascp12() /** * Index 26: or-asc-p1-3 */ orascp13 = (): string | undefined => { return undefined } f26 = (): string | undefined => this.orascp13() /** * Index 27: or-asc-p1-11 */ orascp111 = (): string | undefined => { return undefined } f27 = (): string | undefined => this.orascp111() /** * Index 28: or-asc-p1-12 */ orascp112 = (): string | undefined => { return undefined } f28 = (): string | undefined => this.orascp112() /** * Index 29: or-asc-p1-5 */ orascp15 = (): string | undefined => { return undefined } f29 = (): string | undefined => this.orascp15() /** * Index 30: or-asc-p1-6 */ orascp16 = (): string | undefined => { return undefined } f30 = (): string | undefined => this.orascp16() /** * Index 31: or-asc-p1-7 */ orascp17 = (): string | undefined => { return undefined } f31 = (): string | undefined => this.orascp17() /** * Index 32: or-asc-p1-8 */ orascp18 = (): string | undefined => { return undefined } f32 = (): string | undefined => this.orascp18() /** * Index 33: or-asc-p2-13 */ orascp213 = (): string | undefined => { return undefined } f33 = (): string | undefined => this.orascp213() /** * Index 34: or-asc-p2-1 */ orascp21 = (): string | undefined => { return undefined } f34 = (): string | undefined => this.orascp21() /** * Index 35: or-asc-p2-2 */ orascp22 = (): string | undefined => { return undefined } f35 = (): string | undefined => this.orascp22() /** * Index 36: or-asc-p2-3 */ orascp23 = (): string | undefined => { return undefined } f36 = (): string | undefined => this.orascp23() /** * Index 37: or-asc-p2-4 */ orascp24 = (): string | undefined => { return undefined } f37 = (): string | undefined => this.orascp24() /** * Index 38: or-asc-p2-5 */ orascp25 = (): string | undefined => { return undefined } f38 = (): string | undefined => this.orascp25() /** * Index 39: or-asc-p2-6 */ orascp26 = (): string | undefined => { return undefined } f39 = (): string | undefined => this.orascp26() /** * Index 40: or-asc-p2-7 */ orascp27 = (): string | undefined => { return undefined } f40 = (): string | undefined => this.orascp27() /** * Index 41: or-asc-p2-8 */ orascp28 = (): string | undefined => { return undefined } f41 = (): string | undefined => this.orascp28() /** * Index 42: or-asc-p2-9 */ orascp29 = (): string | undefined => { return undefined } f42 = (): string | undefined => this.orascp29() /** * Index 43: or-asc-p2-10 */ orascp210 = (): string | undefined => { return undefined } f43 = (): string | undefined => this.orascp210() /** * Index 44: or-asc-p2-11 */ orascp211 = (): string | undefined => { return undefined } f44 = (): string | undefined => this.orascp211() /** * Index 45: or-asc-p2-12 */ orascp212 = (): string | undefined => { return undefined } f45 = (): string | undefined => this.orascp212() /** * Index 46: or-asc-p4-1 */ orascp41 = (): string | undefined => { return undefined } f46 = (): string | undefined => this.orascp41() /** * Index 47: or-asc-p4-2 */ orascp42 = (): string | undefined => { return undefined } f47 = (): string | undefined => this.orascp42() /** * Index 48: or-asc-p4-3 */ orascp43 = (): string | undefined => { return undefined } f48 = (): string | undefined => this.orascp43() /** * Index 49: or-asc-p4-4 */ orascp44 = (): string | undefined => { return undefined } f49 = (): string | undefined => this.orascp44() /** * Index 50: or-asc-p4-5 */ orascp45 = (): string | undefined => { return undefined } f50 = (): string | undefined => this.orascp45() /** * Index 51: or-asc-p4-6 */ orascp46 = (): string | undefined => { return undefined } f51 = (): string | undefined => this.orascp46() /** * Index 52: or-asc-p4-7 */ orascp47 = (): string | undefined => { return undefined } f52 = (): string | undefined => this.orascp47() /** * Index 53: or-asc-p4-8 */ orascp48 = (): string | undefined => { return undefined } f53 = (): string | undefined => this.orascp48() /** * Index 54: or-asc-p4-9 */ orascp49 = (): string | undefined => { return undefined } f54 = (): string | undefined => this.orascp49() /** * Index 55: or-asc-p4-10 */ orascp410 = (): string | undefined => { return undefined } f55 = (): string | undefined => this.orascp410() /** * Index 56: or-asc-p4-11 */ orascp411 = (): string | undefined => { return undefined } f56 = (): string | undefined => this.orascp411() /** * Index 57: or-asc-p4-12 */ orascp412 = (): string | undefined => { return undefined } f57 = (): string | undefined => this.orascp412() /** * Index 58: or-asc-p4-13 */ orascp413 = (): string | undefined => { return undefined } f58 = (): string | undefined => this.orascp413() /** * Index 59: or-asc-p4-14 */ orascp414 = (): string | undefined => { return undefined } f59 = (): string | undefined => this.orascp414() /** * Index 60: or-asc-p5-1 */ orascp51 = (): string | undefined => { return undefined } f60 = (): string | undefined => this.orascp51() /** * Index 61: or-asc-p5-3 */ orascp53 = (): string | undefined => { return undefined } f61 = (): string | undefined => this.orascp53() /** * Index 62: or-asc-p5-5 */ orascp55 = (): string | undefined => { return undefined } f62 = (): string | undefined => this.orascp55() /** * Index 63: or-asc-p3-16 */ orascp316 = (): string | undefined => { return undefined } f63 = (): string | undefined => this.orascp316() /** * Index 64: or-asc-p3-19 */ orascp319 = (): string | undefined => { return undefined } f64 = (): string | undefined => this.orascp319() /** * Index 65: or-asc-p3-22 */ orascp322 = (): string | undefined => { return undefined } f65 = (): string | undefined => this.orascp322() /** * Index 66: or-asc-p3-23 */ orascp323 = (): string | undefined => { return undefined } f66 = (): string | undefined => this.orascp323() /** * Index 67: or-asc-p5-2 */ orascp52 = (): string | undefined => { return undefined } f67 = (): string | undefined => this.orascp52() /** * Index 68: or-asc-p5-4 */ orascp54 = (): string | undefined => { return undefined } f68 = (): string | undefined => this.orascp54() /** * Index 69: or-asc-p5-6 */ orascp56 = (): string | undefined => { return undefined } f69 = (): string | undefined => this.orascp56() /** * Index 70: or-asc-p5-7 */ orascp57 = (): string | undefined => { return undefined } f70 = (): string | undefined => this.orascp57() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.f26(), this.f27(), this.f28(), this.f29(), this.f30(), this.f31(), this.f32(), this.f33(), this.f34(), this.f35(), this.f36(), this.f37(), this.f38(), this.f39(), this.f40(), this.f41(), this.f42(), this.f43(), this.f44(), this.f45(), this.f46(), this.f47(), this.f48(), this.f49(), this.f50(), this.f51(), this.f52(), this.f53(), this.f54(), this.f55(), this.f56(), this.f57(), this.f58(), this.f59(), this.f60(), this.f61(), this.f62(), this.f63(), this.f64(), this.f65(), this.f66(), this.f67(), this.f68(), this.f69(), this.f70() ] } const makeORASCNP = (f1040: F1040): ORASCNP => new ORASCNP(f1040) export default makeORASCNP ================================================ FILE: src/forms/Y2021/stateForms/OR/ORWFHDC.ts ================================================ import Form from 'ustaxes/core/stateForms/Form' import F1040 from '../../irsForms/F1040' import { Field } from 'ustaxes/core/pdfFiller' import { State } from 'ustaxes/core/data' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' export class ORWFHDC extends Form { info: ValidatedInformation f1040: F1040 formName: string state: State formOrder = 1 attachments: () => Form[] = () => [] constructor(f1040: F1040) { super() this.info = f1040.info this.f1040 = f1040 this.formName = 'OR-WFHDC' this.state = 'OR' } /** * Index 0: or-wfhdc-p1_1 */ orwfhdcp11 = (): string | undefined => { return undefined } f0 = (): string | undefined => this.orwfhdcp11() /** * Index 1: or-wfhdc-p1_2 */ orwfhdcp12 = (): string | undefined => { return undefined } f1 = (): string | undefined => this.orwfhdcp12() /** * Index 2: or-wfhdc-p1_3 */ orwfhdcp13 = (): string | undefined => { return undefined } f2 = (): string | undefined => this.orwfhdcp13() /** * Index 3: or-wfhdc-p1_4 */ orwfhdcp14 = (): string | undefined => { return undefined } f3 = (): string | undefined => this.orwfhdcp14() /** * Index 4: or-wfhdc-p1_5 */ orwfhdcp15 = (): boolean | undefined => { return undefined } f4 = (): boolean | undefined => this.orwfhdcp15() /** * Index 5: or-wfhdc-p1_6 */ orwfhdcp16 = (): boolean | undefined => { return undefined } f5 = (): boolean | undefined => this.orwfhdcp16() /** * Index 6: or-wfhdc-p1_7 */ orwfhdcp17 = (): string | undefined => { return undefined } f6 = (): string | undefined => this.orwfhdcp17() /** * Index 7: or-wfhdc-p1_8 */ orwfhdcp18 = (): string | undefined => { return undefined } f7 = (): string | undefined => this.orwfhdcp18() /** * Index 8: or-wfhdc-p1_9 */ orwfhdcp19 = (): string | undefined => { return undefined } f8 = (): string | undefined => this.orwfhdcp19() /** * Index 9: or-wfhdc-p1_10 */ orwfhdcp110 = (): string | undefined => { return undefined } f9 = (): string | undefined => this.orwfhdcp110() /** * Index 10: or-wfhdc-p1_11 */ orwfhdcp111 = (): boolean | undefined => { return undefined } f10 = (): boolean | undefined => this.orwfhdcp111() /** * Index 11: or-wfhdc-p1_12 */ orwfhdcp112 = (): boolean | undefined => { return undefined } f11 = (): boolean | undefined => this.orwfhdcp112() /** * Index 12: or-wfhdc-p1_13 */ orwfhdcp113 = (): string | undefined => { return undefined } f12 = (): string | undefined => this.orwfhdcp113() /** * Index 13: or-wfhdc-p1_14 */ orwfhdcp114 = (): string | undefined => { return undefined } f13 = (): string | undefined => this.orwfhdcp114() /** * Index 14: or-wfhdc-p1_15 */ orwfhdcp115 = (): string | undefined => { return undefined } f14 = (): string | undefined => this.orwfhdcp115() /** * Index 15: or-wfhdc-p1_16 */ orwfhdcp116 = (): string | undefined => { return undefined } f15 = (): string | undefined => this.orwfhdcp116() /** * Index 16: or-wfhdc-p1_17 */ orwfhdcp117 = (): string | undefined => { return undefined } f16 = (): string | undefined => this.orwfhdcp117() /** * Index 17: or-wfhdc-p1_18 */ orwfhdcp118 = (): string | undefined => { return undefined } f17 = (): string | undefined => this.orwfhdcp118() /** * Index 18: or-wfhdc-p1_21 */ orwfhdcp121 = (): string | undefined => { return undefined } f18 = (): string | undefined => this.orwfhdcp121() /** * Index 19: or-wfhdc-p1_22 */ orwfhdcp122 = (): string | undefined => { return undefined } f19 = (): string | undefined => this.orwfhdcp122() /** * Index 20: or-wfhdc-p1_23 */ orwfhdcp123 = (): string | undefined => { return undefined } f20 = (): string | undefined => this.orwfhdcp123() /** * Index 21: or-wfhdc-p1_24 */ orwfhdcp124 = (): string | undefined => { return undefined } f21 = (): string | undefined => this.orwfhdcp124() /** * Index 22: or-wfhdc-p1_26 */ orwfhdcp126 = (): string | undefined => { return undefined } f22 = (): string | undefined => this.orwfhdcp126() /** * Index 23: or-wfhdc-p2_1 */ orwfhdcp21 = (): string | undefined => { return undefined } f23 = (): string | undefined => this.orwfhdcp21() /** * Index 24: or-wfhdc-p2_2 */ orwfhdcp22 = (): string | undefined => { return undefined } f24 = (): string | undefined => this.orwfhdcp22() /** * Index 25: or-wfhdc-p2_3 */ orwfhdcp23 = (): string | undefined => { return undefined } f25 = (): string | undefined => this.orwfhdcp23() /** * Index 26: or-wfhdc-p2_4 */ orwfhdcp24 = (): string | undefined => { return undefined } f26 = (): string | undefined => this.orwfhdcp24() /** * Index 27: or-wfhdc-p2_5 */ orwfhdcp25 = (): string | undefined => { return undefined } f27 = (): string | undefined => this.orwfhdcp25() /** * Index 28: or-wfhdc-p2_6 */ orwfhdcp26 = (): string | undefined => { return undefined } f28 = (): string | undefined => this.orwfhdcp26() /** * Index 29: or-wfhdc-p2_9 */ orwfhdcp29 = (): string | undefined => { return undefined } f29 = (): string | undefined => this.orwfhdcp29() /** * Index 30: or-wfhdc-p2_10 */ orwfhdcp210 = (): string | undefined => { return undefined } f30 = (): string | undefined => this.orwfhdcp210() /** * Index 31: or-wfhdc-p2_11 */ orwfhdcp211 = (): string | undefined => { return undefined } f31 = (): string | undefined => this.orwfhdcp211() /** * Index 32: or-wfhdc-p2_12 */ orwfhdcp212 = (): string | undefined => { return undefined } f32 = (): string | undefined => this.orwfhdcp212() /** * Index 33: or-wfhdc-p2_14 */ orwfhdcp214 = (): string | undefined => { return undefined } f33 = (): string | undefined => this.orwfhdcp214() /** * Index 34: or-wfhdc-p2_15 */ orwfhdcp215 = (): string | undefined => { return undefined } f34 = (): string | undefined => this.orwfhdcp215() /** * Index 35: or-wfhdc-p2_16 */ orwfhdcp216 = (): string | undefined => { return undefined } f35 = (): string | undefined => this.orwfhdcp216() /** * Index 36: or-wfhdc-p2_17 */ orwfhdcp217 = (): string | undefined => { return undefined } f36 = (): string | undefined => this.orwfhdcp217() /** * Index 37: or-wfhdc-p2_18 */ orwfhdcp218 = (): string | undefined => { return undefined } f37 = (): string | undefined => this.orwfhdcp218() /** * Index 38: or-wfhdc-p2_19 */ orwfhdcp219 = (): string | undefined => { return undefined } f38 = (): string | undefined => this.orwfhdcp219() /** * Index 39: or-wfhdc-p2_20 */ orwfhdcp220 = (): string | undefined => { return undefined } f39 = (): string | undefined => this.orwfhdcp220() /** * Index 40: or-wfhdc-p2_21 */ orwfhdcp221 = (): string | undefined => { return undefined } f40 = (): string | undefined => this.orwfhdcp221() /** * Index 41: or-wfhdc-p2_23 */ orwfhdcp223 = (): string | undefined => { return undefined } f41 = (): string | undefined => this.orwfhdcp223() /** * Index 42: or-wfhdc-p2_24 */ orwfhdcp224 = (): string | undefined => { return undefined } f42 = (): string | undefined => this.orwfhdcp224() /** * Index 43: or-wfhdc-p2_25 */ orwfhdcp225 = (): string | undefined => { return undefined } f43 = (): string | undefined => this.orwfhdcp225() /** * Index 44: or-wfhdc-p2_26 */ orwfhdcp226 = (): string | undefined => { return undefined } f44 = (): string | undefined => this.orwfhdcp226() /** * Index 45: or-wfhdc-p2_28 */ orwfhdcp228 = (): string | undefined => { return undefined } f45 = (): string | undefined => this.orwfhdcp228() /** * Index 46: or-wfhdc-p2_29 */ orwfhdcp229 = (): string | undefined => { return undefined } f46 = (): string | undefined => this.orwfhdcp229() /** * Index 47: or-wfhdc-p4_1 */ orwfhdcp41 = (): string | undefined => { return undefined } f47 = (): string | undefined => this.orwfhdcp41() /** * Index 48: or-wfhdc-p4_2 */ orwfhdcp42 = (): string | undefined => { return undefined } f48 = (): string | undefined => this.orwfhdcp42() /** * Index 49: or-wfhdc-p4_3 */ orwfhdcp43 = (): string | undefined => { return undefined } f49 = (): string | undefined => this.orwfhdcp43() /** * Index 50: or-wfhdc-p4_4 */ orwfhdcp44 = (): string | undefined => { return undefined } f50 = (): string | undefined => this.orwfhdcp44() /** * Index 51: or-wfhdc-p4_5 */ orwfhdcp45 = (): string | undefined => { return undefined } f51 = (): string | undefined => this.orwfhdcp45() /** * Index 52: or-wfhdc-p4_6 */ orwfhdcp46 = (): string | undefined => { return undefined } f52 = (): string | undefined => this.orwfhdcp46() /** * Index 53: or-wfhdc-p4_7 */ orwfhdcp47 = (): string | undefined => { return undefined } f53 = (): string | undefined => this.orwfhdcp47() /** * Index 54: or-wfhdc-p4_8 */ orwfhdcp48 = (): string | undefined => { return undefined } f54 = (): string | undefined => this.orwfhdcp48() /** * Index 55: or-wfhdc-p5_1 */ orwfhdcp51 = (): string | undefined => { return undefined } f55 = (): string | undefined => this.orwfhdcp51() /** * Index 56: or-wfhdc-p5_2 */ orwfhdcp52 = (): string | undefined => { return undefined } f56 = (): string | undefined => this.orwfhdcp52() /** * Index 57: or-wfhdc-p5_3 */ orwfhdcp53 = (): string | undefined => { return undefined } f57 = (): string | undefined => this.orwfhdcp53() /** * Index 58: or-wfhdc-p5_4 */ orwfhdcp54 = (): string | undefined => { return undefined } f58 = (): string | undefined => this.orwfhdcp54() /** * Index 59: or-wfhdc-p5_5 */ orwfhdcp55 = (): string | undefined => { return undefined } f59 = (): string | undefined => this.orwfhdcp55() /** * Index 60: or-wfhdc-p5_6 */ orwfhdcp56 = (): string | undefined => { return undefined } f60 = (): string | undefined => this.orwfhdcp56() /** * Index 61: or-wfhdc-p5_7 */ orwfhdcp57 = (): string | undefined => { return undefined } f61 = (): string | undefined => this.orwfhdcp57() /** * Index 62: or-wfhdc-p5_8 */ orwfhdcp58 = (): string | undefined => { return undefined } f62 = (): string | undefined => this.orwfhdcp58() /** * Index 63: or-wfhdc-p5_9 */ orwfhdcp59 = (): string | undefined => { return undefined } f63 = (): string | undefined => this.orwfhdcp59() /** * Index 64: or-wfhdc-p5_10 */ orwfhdcp510 = (): string | undefined => { return undefined } f64 = (): string | undefined => this.orwfhdcp510() /** * Index 65: or-wfhdc-p5_11 */ orwfhdcp511 = (): string | undefined => { return undefined } f65 = (): string | undefined => this.orwfhdcp511() /** * Index 66: or-wfhdc-p5_12 */ orwfhdcp512 = (): string | undefined => { return undefined } f66 = (): string | undefined => this.orwfhdcp512() /** * Index 67: or-wfhdc-p5_13 */ orwfhdcp513 = (): string | undefined => { return undefined } f67 = (): string | undefined => this.orwfhdcp513() /** * Index 68: Button - Clear form */ ButtonClearform = (): string | undefined => { return undefined } f68 = (): string | undefined => this.ButtonClearform() /** * Index 69: or-wfhdc-p1_19 */ orwfhdcp119 = (): string | undefined => { return undefined } f69 = (): string | undefined => this.orwfhdcp119() /** * Index 70: or-wfhdc-p1_25 */ orwfhdcp125 = (): string | undefined => { return undefined } f70 = (): string | undefined => this.orwfhdcp125() /** * Index 71: or-wfhdc-p2_13 */ orwfhdcp213 = (): string | undefined => { return undefined } f71 = (): string | undefined => this.orwfhdcp213() /** * Index 72: or-wfhdc-p2_27 */ orwfhdcp227 = (): string | undefined => { return undefined } f72 = (): string | undefined => this.orwfhdcp227() /** * Index 73: or-wfhdc-p2_7 */ orwfhdcp27 = (): string | undefined => { return undefined } f73 = (): string | undefined => this.orwfhdcp27() /** * Index 74: or-wfhdc-p3_1 */ orwfhdcp31 = (): string | undefined => { return undefined } f74 = (): string | undefined => this.orwfhdcp31() /** * Index 75: or-wfhdc-p3_2 */ orwfhdcp32 = (): string | undefined => { return undefined } f75 = (): string | undefined => this.orwfhdcp32() /** * Index 76: or-wfhdc-p3_3 */ orwfhdcp33 = (): string | undefined => { return undefined } f76 = (): string | undefined => this.orwfhdcp33() /** * Index 77: or-wfhdc-p3_4 */ orwfhdcp34 = (): string | undefined => { return undefined } f77 = (): string | undefined => this.orwfhdcp34() /** * Index 78: or-wfhdc-p3_5 */ orwfhdcp35 = (): string | undefined => { return undefined } f78 = (): string | undefined => this.orwfhdcp35() /** * Index 79: or-wfhdc-p3_6 */ orwfhdcp36 = (): string | undefined => { return undefined } f79 = (): string | undefined => this.orwfhdcp36() /** * Index 80: or-wfhdc-p3_7 */ orwfhdcp37 = (): boolean | undefined => { return undefined } f80 = (): boolean | undefined => this.orwfhdcp37() /** * Index 81: or-wfhdc-p3_8 */ orwfhdcp38 = (): string | undefined => { return undefined } f81 = (): string | undefined => this.orwfhdcp38() /** * Index 82: or-wfhdc-p3_9 */ orwfhdcp39 = (): string | undefined => { return undefined } f82 = (): string | undefined => this.orwfhdcp39() /** * Index 83: or-wfhdc-p3_10 */ orwfhdcp310 = (): string | undefined => { return undefined } f83 = (): string | undefined => this.orwfhdcp310() /** * Index 84: or-wfhdc-p3_11 */ orwfhdcp311 = (): string | undefined => { return undefined } f84 = (): string | undefined => this.orwfhdcp311() /** * Index 85: or-wfhdc-p3_12 */ orwfhdcp312 = (): string | undefined => { return undefined } f85 = (): string | undefined => this.orwfhdcp312() /** * Index 86: or-wfhdc-p3_13 */ orwfhdcp313 = (): string | undefined => { return undefined } f86 = (): string | undefined => this.orwfhdcp313() /** * Index 87: or-wfhdc-p3_14 */ orwfhdcp314 = (): string | undefined => { return undefined } f87 = (): string | undefined => this.orwfhdcp314() /** * Index 88: or-wfhdc-p3_16 */ orwfhdcp316 = (): string | undefined => { return undefined } f88 = (): string | undefined => this.orwfhdcp316() /** * Index 89: or-wfhdc-p3_17 */ orwfhdcp317 = (): boolean | undefined => { return undefined } f89 = (): boolean | undefined => this.orwfhdcp317() /** * Index 90: or-wfhdc-p3_18 */ orwfhdcp318 = (): string | undefined => { return undefined } f90 = (): string | undefined => this.orwfhdcp318() /** * Index 91: or-wfhdc-p3_19 */ orwfhdcp319 = (): string | undefined => { return undefined } f91 = (): string | undefined => this.orwfhdcp319() /** * Index 92: or-wfhdc-p3_20 */ orwfhdcp320 = (): string | undefined => { return undefined } f92 = (): string | undefined => this.orwfhdcp320() /** * Index 93: or-wfhdc-p3_21 */ orwfhdcp321 = (): string | undefined => { return undefined } f93 = (): string | undefined => this.orwfhdcp321() /** * Index 94: or-wfhdc-p3_22 */ orwfhdcp322 = (): string | undefined => { return undefined } f94 = (): string | undefined => this.orwfhdcp322() /** * Index 95: or-wfhdc-p3_23 */ orwfhdcp323 = (): string | undefined => { return undefined } f95 = (): string | undefined => this.orwfhdcp323() /** * Index 96: or-wfhdc-p3_24 */ orwfhdcp324 = (): string | undefined => { return undefined } f96 = (): string | undefined => this.orwfhdcp324() /** * Index 97: or-wfhdc-p3_26 */ orwfhdcp326 = (): string | undefined => { return undefined } f97 = (): string | undefined => this.orwfhdcp326() /** * Index 98: or-wfhdc-p3_27 */ orwfhdcp327 = (): boolean | undefined => { return undefined } f98 = (): boolean | undefined => this.orwfhdcp327() /** * Index 99: or-wfhdc-p3_28 */ orwfhdcp328 = (): string | undefined => { return undefined } f99 = (): string | undefined => this.orwfhdcp328() /** * Index 100: or-wfhdc-p3_29 */ orwfhdcp329 = (): string | undefined => { return undefined } f100 = (): string | undefined => this.orwfhdcp329() /** * Index 101: or-wfhdc-p3_30 */ orwfhdcp330 = (): string | undefined => { return undefined } f101 = (): string | undefined => this.orwfhdcp330() /** * Index 102: or-wfhdc-p3_15 */ orwfhdcp315 = (): string | undefined => { return undefined } f102 = (): string | undefined => this.orwfhdcp315() /** * Index 103: or-wfhdc-p3_25 */ orwfhdcp325 = (): string | undefined => { return undefined } f103 = (): string | undefined => this.orwfhdcp325() /** * Index 104: or-wfhdc-p1_20a */ orwfhdcp120a = (): string | undefined => { return undefined } f104 = (): string | undefined => this.orwfhdcp120a() /** * Index 105: or-wfhdc-p1_20b */ orwfhdcp120b = (): string | undefined => { return undefined } f105 = (): string | undefined => this.orwfhdcp120b() /** * Index 106: or-wfhdc-p2_8a */ orwfhdcp28a = (): string | undefined => { return undefined } f106 = (): string | undefined => this.orwfhdcp28a() /** * Index 107: or-wfhdc-p2_8b */ orwfhdcp28b = (): string | undefined => { return undefined } f107 = (): string | undefined => this.orwfhdcp28b() /** * Index 108: or-wfhdc-p2_22a */ orwfhdcp222a = (): string | undefined => { return undefined } f108 = (): string | undefined => this.orwfhdcp222a() /** * Index 109: or-wfhdc-p2_22b */ orwfhdcp222b = (): string | undefined => { return undefined } f109 = (): string | undefined => this.orwfhdcp222b() fields = (): Field[] => [ this.f0(), this.f1(), this.f2(), this.f3(), this.f4(), this.f5(), this.f6(), this.f7(), this.f8(), this.f9(), this.f10(), this.f11(), this.f12(), this.f13(), this.f14(), this.f15(), this.f16(), this.f17(), this.f18(), this.f19(), this.f20(), this.f21(), this.f22(), this.f23(), this.f24(), this.f25(), this.f26(), this.f27(), this.f28(), this.f29(), this.f30(), this.f31(), this.f32(), this.f33(), this.f34(), this.f35(), this.f36(), this.f37(), this.f38(), this.f39(), this.f40(), this.f41(), this.f42(), this.f43(), this.f44(), this.f45(), this.f46(), this.f47(), this.f48(), this.f49(), this.f50(), this.f51(), this.f52(), this.f53(), this.f54(), this.f55(), this.f56(), this.f57(), this.f58(), this.f59(), this.f60(), this.f61(), this.f62(), this.f63(), this.f64(), this.f65(), this.f66(), this.f67(), this.f68(), this.f69(), this.f70(), this.f71(), this.f72(), this.f73(), this.f74(), this.f75(), this.f76(), this.f77(), this.f78(), this.f79(), this.f80(), this.f81(), this.f82(), this.f83(), this.f84(), this.f85(), this.f86(), this.f87(), this.f88(), this.f89(), this.f90(), this.f91(), this.f92(), this.f93(), this.f94(), this.f95(), this.f96(), this.f97(), this.f98(), this.f99(), this.f100(), this.f101(), this.f102(), this.f103(), this.f104(), this.f105(), this.f106(), this.f107(), this.f108(), this.f109() ] } const makeORWFHDC = (f1040: F1040): ORWFHDC => new ORWFHDC(f1040) export default makeORWFHDC ================================================ FILE: src/forms/Y2021/stateForms/OR/Parameters.ts ================================================ // import { FilingStatus } from 'ustaxes/core/data' const parameters = { // exemptions: { // [FilingStatus.S]: { // incomeLowerLimit: 2325, // incomeUpperLimit: 250000, // exemptionAmount: 2325 // }, // [FilingStatus.MFJ]: { // incomeLowerLimit: 4650, // incomeUpperLimit: 500000, // exemptionAmount: 4650 // } // }, // taxRate: 0.0495, // seniorExemption: 1000, // blindExemption: 1000, // earnedIncomeCreditFactor: 0.18, // eicDependentCredit: 2325 } export default parameters ================================================ FILE: src/forms/Y2021/stateForms/PA/Form.ts ================================================ export default class PAForm {} ================================================ FILE: src/forms/Y2021/stateForms/RI/Form.ts ================================================ export default class RIForm {} ================================================ FILE: src/forms/Y2021/stateForms/SC/Form.ts ================================================ export default class SCForm {} ================================================ FILE: src/forms/Y2021/stateForms/SD/Form.ts ================================================ export default class SDForm {} ================================================ FILE: src/forms/Y2021/stateForms/TN/Form.ts ================================================ export default class TNForm {} ================================================ FILE: src/forms/Y2021/stateForms/TX/Form.ts ================================================ export default class TXForm {} ================================================ FILE: src/forms/Y2021/stateForms/UT/Form.ts ================================================ export default class UTForm {} ================================================ FILE: src/forms/Y2021/stateForms/VA/Form.ts ================================================ export default class VAForm {} ================================================ FILE: src/forms/Y2021/stateForms/VT/Form.ts ================================================ export default class VTForm {} ================================================ FILE: src/forms/Y2021/stateForms/WA/Form.ts ================================================ export default class WAForm {} ================================================ FILE: src/forms/Y2021/stateForms/WI/Form.ts ================================================ export default class WIForm {} ================================================ FILE: src/forms/Y2021/stateForms/WV/Form.ts ================================================ export default class WVForm {} ================================================ FILE: src/forms/Y2021/stateForms/WY/Form.ts ================================================ export default class WYForm {} ================================================ FILE: src/forms/Y2021/stateForms/index.ts ================================================ import F1040 from '../irsForms/F1040' import { State } from 'ustaxes/core/data' import StateForm from 'ustaxes/core/stateForms/Form' import il1040 from './IL/IL1040' import { Either } from 'ustaxes/core/util' import { createStateReturn as createStateReturnF } from '../../StateForms' import { StateFormError } from '../../StateForms' export const noFilingRequirementStates: State[] = [ 'AK', 'TN', 'WY', 'FL', 'NH', 'SD', 'TX', 'WA', 'NV' ] export const stateForms: { [K in State]?: (f1040: F1040) => StateForm } = { IL: il1040 } export const createStateReturn = ( f1040: F1040 ): Either => createStateReturnF(noFilingRequirementStates, stateForms)(f1040) ================================================ FILE: src/forms/Y2021/tests/Schedule8812.test.ts ================================================ import { commonTests } from '.' import Schedule8812 from '../irsForms/Schedule8812' import F1040 from '../irsForms/F1040' const withSchedule8812 = async ( f: (f1040: F1040, s8812: Schedule8812) => void ): Promise => await commonTests.withValid1040( (f1040: F1040): void => { if (f1040.schedule8812.isNeeded()) { f(f1040, f1040.schedule8812) } }, // Add filter to info property so we're only testing in the domain // we care about. (info) => info.taxPayer.dependents.length > 0 ) describe('Schedule 8812', () => { it('should be attached with qualifiying dependents', async () => { await commonTests.withValid1040((f1040) => { // If there are qualifying dependents, we must have a schedule 8812 if (f1040.qualifyingDependents.qualifyingChildren().length > 0) { expect(f1040.schedule8812).not.toBe(undefined) } }) }) it('should not produce line 5 with no dependents', async () => { await withSchedule8812((f1040, s8812) => { // If Schedule A is attached, the deduction should be greater than the standard deduction if (s8812.l4a() === 0) { expect(s8812.l5()).toEqual(0) } }) }) it('should show a multiple of 1000 at l10', async () => { await withSchedule8812((f1040, s8812) => { expect(s8812.l10() % 1000).toEqual(0) }) }) }) ================================================ FILE: src/forms/Y2021/tests/ScheduleA.test.ts ================================================ import { commonTests } from '.' import * as fc from 'fast-check' describe('ScheduleA', () => { it('should make deduction > standard deduction if Schedule A is attached', async () => { await commonTests.withValid1040((f1040) => { // If Schedule A is attached, the deduction should be greater than the standard deduction // Cancel test if Schedule A is not attached fc.pre(f1040.scheduleA.isNeeded()) expect(f1040.l12a() ?? 0).toBeGreaterThan(f1040.standardDeduction() ?? 0) }) }) it('should be attached if deduction is more than standard', async () => { await commonTests.withValid1040((f1040) => { const standardDeduction = f1040.standardDeduction() ?? 0 // If the deduction is more than standard, we must have a schedule A // Note dependents of other taxpayers may still itemize. if ((f1040.l12a() ?? 0) > standardDeduction) { expect(f1040.scheduleA).not.toBe(undefined) } }) }) }) ================================================ FILE: src/forms/Y2021/tests/ScheduleD.test.ts ================================================ import * as fc from 'fast-check' import { testKit, commonTests } from '.' describe('ScheduleD', () => { it('should never pass through more than allowed losses', async () => { await fc.assert( testKit.with1040Property((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) if (f1040.scheduleD.isNeeded()) { expect(Math.round(f1040.l7() ?? 0)).toBeGreaterThanOrEqual( -f1040.scheduleD.l21Min() ) } return Promise.resolve() }) ) }) }) ================================================ FILE: src/forms/Y2021/tests/ScheduleEIC.test.ts ================================================ /* eslint @typescript-eslint/no-empty-function: "off" */ import * as federal from '../data/federal' import { testKit, commonTests } from '.' beforeAll(() => jest.spyOn(console, 'warn').mockImplementation(() => {})) describe('ScheduleEIC', () => { it('should disallow EIC for income below threshold', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) const formula = federal.EIC.formulas[f1040.info.taxPayer.filingStatus] if (formula !== undefined && f1040.wages() < formula[0][1].lowerBound) { expect(f1040.scheduleEIC.allowed()).toBe(false) expect(f1040.scheduleEIC.credit()).toBe(0) } return Promise.resolve() }) }) }) ================================================ FILE: src/forms/Y2021/tests/f1040.test.ts ================================================ import { displayRound } from 'ustaxes/core/irsForms/util' import { commonTests, testKit } from '.' jest.setTimeout(40000) beforeAll(() => { jest.spyOn(console, 'warn').mockImplementation((x: string) => { if (!x.includes('Removing XFA form data as pdf-lib')) { console.warn(x) } }) }) describe('f1040', () => { commonTests.run() it('should never have higher AGI than total income', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040(forms) expect(f1040).not.toBeUndefined() if (f1040 !== undefined) { expect(displayRound(f1040.l11()) ?? 0).toBeLessThanOrEqual( // It is possible for losses to create negative income. displayRound(Math.max(0, f1040.l9())) ?? 0 ) } return Promise.resolve() }) }) it('should never produce higher tax than total income', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040(forms) expect(f1040).not.toBeUndefined() if (f1040 !== undefined) { // Remove line 7 for AMT expect( displayRound(f1040.l24() - (f1040.l17() ?? 0)) ?? 0 ).toBeLessThanOrEqual(displayRound(Math.max(0, f1040.l9())) ?? 0) } return Promise.resolve() }) }) it('should never produce tax on taxable income higher than income', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040(forms) expect(f1040).not.toBeUndefined() if (f1040 !== undefined) { // tax on taxable income should be less than taxable income if (f1040.l15() > 0) { expect(f1040.l16() ?? 0).toBeLessThan(f1040.l15()) } else { expect(f1040.l16() ?? 0).toEqual(0) } } return Promise.resolve() }) }) }) ================================================ FILE: src/forms/Y2021/tests/f6251.test.ts ================================================ /* eslint @typescript-eslint/no-empty-function: "off" */ import { FilingStatus, PersonRole } from 'ustaxes/core/data' import F1040 from '../irsForms/F1040' import F6251 from '../irsForms/F6251' import { cloneDeep } from 'lodash' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' const baseInformation: ValidatedInformation = { f1099s: [], f3921s: [ { name: 'Stock Option', personRole: PersonRole.PRIMARY, exercisePricePerShare: 1, fmv: 101, numShares: 1000 } ], credits: [], scheduleK1Form1065s: [], itemizedDeductions: undefined, w2s: [ { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.PRIMARY, occupation: 'w2s-occupation', state: 'AL', income: 100000, medicareIncome: 0, fedWithholding: 0, ssWages: 100000, ssWithholding: 0, medicareWithholding: 0, stateWages: 100000, stateWithholding: 0 } ], estimatedTaxes: [], realEstate: [], taxPayer: { primaryPerson: { address: { address: '0001', aptNo: '', city: 'AR city', state: 'AR', zip: '1234567' }, firstName: 'payer-first-name', lastName: 'payer-last-name', isTaxpayerDependent: false, role: PersonRole.PRIMARY, ssid: '111111111' }, spouse: undefined, dependents: [], filingStatus: FilingStatus.S }, questions: {}, f1098es: [], stateResidencies: [{ state: 'AL' }], healthSavingsAccounts: [], individualRetirementArrangements: [] } describe('AMT', () => { it('stock options should trigger AMT', () => { const information = cloneDeep(baseInformation) const f1040 = new F1040(information, []) const f6251 = new F6251(f1040) expect(f6251.isNeeded()).toEqual(true) expect(Math.round(f6251.l1() ?? 0)).toEqual(87450) expect(Math.round(f6251.l7() ?? 0)).toEqual(32864) expect(Math.round(f6251.l10())).toEqual(15015) expect(Math.round(f6251.l11())).toEqual(17849) }) it('small stock options should NOT trigger AMT', () => { const information = cloneDeep(baseInformation) information.f3921s[0].exercisePricePerShare = 100 const f1040 = new F1040(information, []) const f6251 = new F6251(f1040) expect(f6251.isNeeded()).toEqual(false) expect(Math.round(f6251.l1() ?? 0)).toEqual(87450) expect(Math.round(f6251.l7() ?? 0)).toEqual(7124) expect(Math.round(f6251.l10())).toEqual(15015) expect(Math.round(f6251.l11())).toEqual(0) }) }) ================================================ FILE: src/forms/Y2021/tests/fica.test.ts ================================================ import { fica } from '../data/federal' import F1040 from '../irsForms/F1040' import F8959 from '../irsForms/F8959' import Form from 'ustaxes/core/irsForms/Form' import Schedule2 from '../irsForms/Schedule2' import Schedule3 from '../irsForms/Schedule3' import { displayRound } from 'ustaxes/core/irsForms/util' import { testKit, commonTests } from '.' import { FilingStatus, IncomeW2, PersonRole } from 'ustaxes/core/data' import { run } from 'ustaxes/core/util' import { blankState } from 'ustaxes/redux/reducer' import { ValidatedInformation } from 'ustaxes/forms/F1040Base' import * as fc from 'fast-check' jest.setTimeout(10000) beforeAll(() => { jest.spyOn(console, 'warn').mockImplementation(() => { // do nothing }) }) const sampleW2: IncomeW2 = { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.PRIMARY, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, ssWithholding: fica.maxSSTax, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 } const sampleInfo: ValidatedInformation = { ...blankState, taxPayer: { dependents: [], filingStatus: FilingStatus.MFJ, primaryPerson: { address: { address: '', city: '' }, firstName: '', isTaxpayerDependent: false, lastName: '', role: PersonRole.PRIMARY, ssid: '', isBlind: false, dateOfBirth: new Date('2000-01-01') }, spouse: { firstName: '', isTaxpayerDependent: false, lastName: '', role: PersonRole.SPOUSE, ssid: '', isBlind: false, dateOfBirth: new Date('2000-01-01') } } } const hasSSRefund = (f1040: F1040): boolean => f1040.schedule3.l11() > 0 function hasAdditionalMedicareTax(f1040: F1040): boolean { const medicareTax = f1040.f8959.l18() return medicareTax > 0 } /* eslint-disable @typescript-eslint/no-explicit-any */ type Constructor = new (...args: any[]) => T function hasAttachment( attachments: Form[], formType: Constructor ): boolean { return ( attachments.find((f) => { return f instanceof formType }) !== undefined ) } describe('fica', () => { it('should give refund SS tax overpayment only in some conditions', async () => { await testKit.with1040Assert((forms) => { const f1040 = commonTests.findF1040OrFail(forms) if (f1040.validW2s().length <= 1) { // Should never give SS refund with 1 or fewer W2s expect(hasSSRefund(f1040)).toEqual(false) } else { const ssWithheld = f1040 .validW2s() .map((w2) => w2.ssWithholding) .reduce((l, r) => l + r, 0) if ( f1040.wages() <= fica.maxIncomeSSTaxApplies || f1040.validW2s().some((w2) => w2.ssWithholding > fica.maxSSTax) || ssWithheld < fica.maxSSTax ) { // Should never give SS refund if W2 income below max threshold, some W2 has // withheld over the max, or there is no SS withholding to refund. expect(hasSSRefund(f1040)).toEqual(false) } else { // Otherwise, should always give SS refund, and attach schedule 3 expect(hasSSRefund(f1040)).toEqual(true) expect(hasAttachment(forms, Schedule3)).toEqual(true) } } return Promise.resolve() }) }) it('should give SS refund based on filing status', async () => { await testKit.with1040Assert((forms) => { const f1040 = commonTests.findF1040OrFail(forms) if (hasSSRefund(f1040)) { const ssRefund = f1040.schedule3.l11() expect(displayRound(ssRefund)).not.toBeUndefined() expect(ssRefund).toBeGreaterThan(0) const ssWithheld = f1040 .validW2s() .filter((w2) => w2.personRole == PersonRole.PRIMARY) .map((w2) => w2.ssWithholding) .reduce((l, r) => l + r, 0) + f1040 .validW2s() .filter((w2) => w2.personRole == PersonRole.SPOUSE) .map((w2) => w2.ssWithholding) .reduce((l, r) => l + r, 0) expect(ssRefund).toEqual(ssWithheld - fica.maxSSTax) } else { fc.pre(false) } return Promise.resolve() }) }) it('should not give a refund if each person has less than the max', () => { const testInfo: ValidatedInformation = { ...sampleInfo, w2s: [ { ...sampleW2, personRole: PersonRole.SPOUSE, ssWithholding: fica.maxSSTax }, { ...sampleW2, personRole: PersonRole.PRIMARY, ssWithholding: fica.maxSSTax } ] } const f1040 = run(testKit.builder.build(testInfo, []).f1040()) .map(commonTests.findF1040OrFail) .orThrow() expect(f1040.schedule3.claimableExcessSSTaxWithholding()).toEqual(0) }) it('should give a refund if a person has more than the max if they have two w2s', () => { const testInfo: ValidatedInformation = { ...sampleInfo, w2s: [ { ...sampleW2, personRole: PersonRole.SPOUSE, ssWithholding: fica.maxSSTax }, { ...sampleW2, personRole: PersonRole.SPOUSE, // This person has already contributed to the max for their other w2 so the refund should equal this amount ssWithholding: 1000 }, { ...sampleW2, personRole: PersonRole.PRIMARY, ssWithholding: fica.maxSSTax } ] } const f1040 = run(testKit.builder.build(testInfo, []).f1040()) .map(commonTests.findF1040OrFail) .orThrow() expect(f1040.schedule3.claimableExcessSSTaxWithholding()).toEqual(1000) }) it('should add Additional Medicare Tax form 8959', async () => { await testKit.with1040Assert((forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) const filingStatus = f1040.info.taxPayer.filingStatus // Should add Additional Medicare Tax if medicare wages over threshold if ( f1040.medicareWages() > fica.additionalMedicareTaxThreshold(filingStatus) ) { expect(hasAdditionalMedicareTax(f1040)).toEqual(true) // Should attach both S2 and F8959 to return expect(hasAttachment(forms, Schedule2)).toEqual(true) expect(hasAttachment(forms, F8959)).toEqual(true) } else { const selfEmploymentWages = f1040.scheduleSE.l6() ?? 0 const hasTax = f1040.medicareWages() + selfEmploymentWages > fica.additionalMedicareTaxThreshold(filingStatus) expect(hasAdditionalMedicareTax(f1040)).toEqual(hasTax) expect(hasAttachment(forms, F8959)).toEqual(hasTax) } return Promise.resolve() }) }) it('should add Additional Medicare Tax based on filing status', async () => { await testKit.with1040Assert(async (forms): Promise => { const f1040 = commonTests.findF1040OrFail(forms) if (hasAdditionalMedicareTax(f1040)) { const filingStatus = f1040.info.taxPayer.filingStatus const selfEmploymentWages = f1040.scheduleSE.l6() ?? 0 const incomeOverThreshold = f1040.medicareWages() + selfEmploymentWages - fica.additionalMedicareTaxThreshold(filingStatus) expect(incomeOverThreshold).toBeGreaterThan(0) // Adds the right amount of additional tax const s2l8 = f1040.f8959.l18() expect(s2l8).not.toBeUndefined() expect(Math.round(s2l8)).toEqual( Math.round(incomeOverThreshold * fica.additionalMedicareTaxRate) ) // Also adds in the extra Medicare tax withheld to 1040 taxes already paid const medicareWithheld = f1040 .validW2s() .map((w2) => w2.medicareWithholding) .reduce((l, r) => l + r, 0) const regularWithholding = fica.regularMedicareTaxRate * f1040.medicareWages() if (medicareWithheld > regularWithholding) { const f1040l25c = f1040.l25c() expect(f1040l25c).not.toBeUndefined() const additionalWithheld = medicareWithheld - regularWithholding expect(displayRound(f1040l25c)).toEqual( displayRound(additionalWithheld) ) } else { expect(displayRound(f1040.l25c()) ?? 0).toEqual(0) } } return Promise.resolve() }) }) }) ================================================ FILE: src/forms/Y2021/tests/index.ts ================================================ import CommonTests, { FormTestInfo } from 'ustaxes/forms/tests/CommonTests' import TestKit from 'ustaxes/forms/tests/TestKit' import F1040 from '../irsForms/F1040' export const testKit = new TestKit('Y2021') class FormTestInfo2021 extends FormTestInfo { getAssets = (f1040: F1040) => f1040.assets getInfo = (f1040: F1040) => f1040.info } export const commonTests = new CommonTests( testKit, new FormTestInfo2021() ) ================================================ FILE: src/forms/Y2021/tests/states/il.test.ts ================================================ import * as fc from 'fast-check' import F1040 from '../../irsForms/F1040' import Form from 'ustaxes/core/irsForms/Form' import { create1040 } from '../../irsForms/Main' import { Information, PersonRole } from 'ustaxes/core/data' import { createStateReturn } from '../../stateForms' import { ILWIT } from '../../stateForms/IL/ILWit' import { isLeft } from 'ustaxes/core/util' import StateForm from 'ustaxes/core/stateForms/Form' import * as arbitraries from 'ustaxes/core/tests/arbitraries' import { fail } from 'assert' const withStateReturn = ( info: Information, logContext: fc.ContextValue, test: (f1040Forms: [F1040, Form[]], stateForms: StateForm[]) => void ): void => { const f1040Result = create1040(info, []) if (isLeft(f1040Result)) { // ignore error infos with no 1040 logContext.log(f1040Result.left.join(';')) return } const [f1040] = f1040Result.right const stateReturn = createStateReturn(f1040) if (isLeft(stateReturn)) { fail(stateReturn.left.join(';')) } test(f1040Result.right, stateReturn.right) } const A = new arbitraries.Arbitraries(2020) describe('il year 2020', () => { it('should produce correct withholding attachments in', () => { fc.assert( fc.property(A.information(), fc.context(), (info, ctx) => { info.stateResidencies = [{ state: 'IL' }] info.w2s.forEach((w2) => { w2.state = 'IL' }) withStateReturn(info, ctx, (_, stateForms) => { ctx.log(stateForms.map((f) => f.formName).join(';')) expect(stateForms.filter((f) => f.formName === 'IL-WIT').length).toBe( Math.ceil( Math.max( ...[PersonRole.PRIMARY, PersonRole.SPOUSE].map( (r) => info.w2s.filter( (w2) => w2.personRole === r && (w2.stateWithholding ?? 0) > 0 ).length ) ) / ILWIT.WITHHOLDING_FORMS_PER_PAGE ) ) }) }) ) }) }) ================================================ FILE: src/forms/Y2021/tests/taxRates.test.ts ================================================ import { FilingStatus } from 'ustaxes/core/data' import { CURRENT_YEAR } from '../data/federal' import { computeOrdinaryTax } from '../irsForms/TaxTable' import fs from 'fs/promises' import { parseCsvOrThrow } from 'ustaxes/data/csvImport' const getTaxTable = async (): Promise => { const path = './src/forms/Y2021/tests/taxTable.csv' const taxTableCsv = (await fs.readFile(path)).toString('utf-8') return parseCsvOrThrow(taxTableCsv, (r: string[], rowNum) => // ignore heading row. rowNum > 0 ? [r.map((s) => Number(s))] : [] ) } const expectTax = (status: FilingStatus, amount: number, tax: number) => { const computedTax = Math.round(computeOrdinaryTax(status, amount)) expect(computedTax).toEqual(tax) } const expectTaxUnder100KRange = ( status: FilingStatus, min: number, max: number, tax: number ) => { const diff = max - min const quarter = Math.round((diff / 4) * 100) / 100 expectTax(status, min, tax) expectTax(status, min + quarter, tax) expectTax(status, min + 2 * quarter, tax) expectTax(status, min + 3 * quarter, tax) expectTax(status, max, tax) } describe('Tax rates', () => { it('test should be updated for new year', () => { // WARNING! Do not just change the year. Also update the CSV and expected tax amounts below! expect(CURRENT_YEAR).toEqual(2021) }) it('ordinary taxes for single status should be correct', async () => { const rows = await getTaxTable() rows.forEach(([min, lessThan, tax]) => { expectTaxUnder100KRange(FilingStatus.S, min, lessThan - 0.01, tax) }) // Over $100,000 const amounts = [ [100000, 18021], [164925, 33603], [164926, 33603], [209425, 47843], [209426, 47843], [523600, 157804], [523601, 157805] ] amounts.forEach(([amount, tax]) => { expectTax(FilingStatus.S, amount, tax) }) }) it('ordinary taxes for married filing jointly status should be correct', async () => { const rows = await getTaxTable() rows.forEach(([min, lessThan, , tax]) => { expectTaxUnder100KRange(FilingStatus.MFJ, min, lessThan - 0.01, tax) }) // Over $100,000 const amounts = [ [100000, 13497], [172750, 29502], [172751, 29502], [329850, 67206], [329851, 67206], [418850, 95686], [418851, 95686], [628300, 168994], [628301, 168994] ] amounts.forEach(([amount, tax]) => { expectTax(FilingStatus.MFJ, amount, tax) }) }) it('ordinary taxes for married filing separately status should be correct', async () => { const rows = await getTaxTable() rows.forEach(([min, lessThan, , , tax]) => { expectTaxUnder100KRange(FilingStatus.MFS, min, lessThan - 0.01, tax) }) // Over $100,000 const amounts = [ [100000, 18021], [164925, 33603], [164926, 33603], [209425, 47843], [209426, 47843], [314150, 84497], [314151, 84497] ] amounts.forEach(([amount, tax]) => { expectTax(FilingStatus.MFS, amount, tax) }) }) it('ordinary taxes for head of household status should be correct', async () => { const rows = await getTaxTable() rows.forEach(([min, lessThan, , , , tax]) => { expectTaxUnder100KRange(FilingStatus.HOH, min, lessThan - 0.01, tax) }) // Over $100,000 const amounts = [ [100000, 16569], [164900, 32145], [164901, 32145], [209400, 46385], [209401, 46385], [523600, 156355], [523601, 156355] ] amounts.forEach(([amount, tax]) => { expectTax(FilingStatus.HOH, amount, tax) }) }) }) ================================================ FILE: src/forms/Y2021/tests/taxTable.csv ================================================ At Least,Less than,Single,Married Filing Jointly,Married Filing Separately,Head of Household 0,5,0,0,0,0 5,15,1,1,1,1 15,25,2,2,2,2 25,50,4,4,4,4 50,75,6,6,6,6 75,100,9,9,9,9 100,125,11,11,11,11 125,150,14,14,14,14 150,175,16,16,16,16 175,200,19,19,19,19 200,225,21,21,21,21 225,250,24,24,24,24 250,275,26,26,26,26 275,300,29,29,29,29 300,325,31,31,31,31 325,350,34,34,34,34 350,375,36,36,36,36 375,400,39,39,39,39 400,425,41,41,41,41 425,450,44,44,44,44 450,475,46,46,46,46 475,500,49,49,49,49 500,525,51,51,51,51 525,550,54,54,54,54 550,575,56,56,56,56 575,600,59,59,59,59 600,625,61,61,61,61 625,650,64,64,64,64 650,675,66,66,66,66 675,700,69,69,69,69 700,725,71,71,71,71 725,750,74,74,74,74 750,775,76,76,76,76 775,800,79,79,79,79 800,825,81,81,81,81 825,850,84,84,84,84 850,875,86,86,86,86 875,900,89,89,89,89 900,925,91,91,91,91 925,950,94,94,94,94 950,975,96,96,96,96 975,1000,99,99,99,99 1000,1025,101,101,101,101 1025,1050,104,104,104,104 1050,1075,106,106,106,106 1075,1100,109,109,109,109 1100,1125,111,111,111,111 1125,1150,114,114,114,114 1150,1175,116,116,116,116 1175,1200,119,119,119,119 1200,1225,121,121,121,121 1225,1250,124,124,124,124 1250,1275,126,126,126,126 1275,1300,129,129,129,129 1300,1325,131,131,131,131 1325,1350,134,134,134,134 1350,1375,136,136,136,136 1375,1400,139,139,139,139 1400,1425,141,141,141,141 1425,1450,144,144,144,144 1450,1475,146,146,146,146 1475,1500,149,149,149,149 1500,1525,151,151,151,151 1525,1550,154,154,154,154 1550,1575,156,156,156,156 1575,1600,159,159,159,159 1600,1625,161,161,161,161 1625,1650,164,164,164,164 1650,1675,166,166,166,166 1675,1700,169,169,169,169 1700,1725,171,171,171,171 1725,1750,174,174,174,174 1750,1775,176,176,176,176 1775,1800,179,179,179,179 1800,1825,181,181,181,181 1825,1850,184,184,184,184 1850,1875,186,186,186,186 1875,1900,189,189,189,189 1900,1925,191,191,191,191 1925,1950,194,194,194,194 1950,1975,196,196,196,196 1975,2000,199,199,199,199 2000,2025,201,201,201,201 2025,2050,204,204,204,204 2050,2075,206,206,206,206 2075,2100,209,209,209,209 2100,2125,211,211,211,211 2125,2150,214,214,214,214 2150,2175,216,216,216,216 2175,2200,219,219,219,219 2200,2225,221,221,221,221 2225,2250,224,224,224,224 2250,2275,226,226,226,226 2275,2300,229,229,229,229 2300,2325,231,231,231,231 2325,2350,234,234,234,234 2350,2375,236,236,236,236 2375,2400,239,239,239,239 2400,2425,241,241,241,241 2425,2450,244,244,244,244 2450,2475,246,246,246,246 2475,2500,249,249,249,249 2500,2525,251,251,251,251 2525,2550,254,254,254,254 2550,2575,256,256,256,256 2575,2600,259,259,259,259 2600,2625,261,261,261,261 2625,2650,264,264,264,264 2650,2675,266,266,266,266 2675,2700,269,269,269,269 2700,2725,271,271,271,271 2725,2750,274,274,274,274 2750,2775,276,276,276,276 2775,2800,279,279,279,279 2800,2825,281,281,281,281 2825,2850,284,284,284,284 2850,2875,286,286,286,286 2875,2900,289,289,289,289 2900,2925,291,291,291,291 2925,2950,294,294,294,294 2950,2975,296,296,296,296 2975,3000,299,299,299,299 3000,3050,303,303,303,303 3050,3100,308,308,308,308 3100,3150,313,313,313,313 3150,3200,318,318,318,318 3200,3250,323,323,323,323 3250,3300,328,328,328,328 3300,3350,333,333,333,333 3350,3400,338,338,338,338 3400,3450,343,343,343,343 3450,3500,348,348,348,348 3500,3550,353,353,353,353 3550,3600,358,358,358,358 3600,3650,363,363,363,363 3650,3700,368,368,368,368 3700,3750,373,373,373,373 3750,3800,378,378,378,378 3800,3850,383,383,383,383 3850,3900,388,388,388,388 3900,3950,393,393,393,393 3950,4000,398,398,398,398 4000,4050,403,403,403,403 4050,4100,408,408,408,408 4100,4150,413,413,413,413 4150,4200,418,418,418,418 4200,4250,423,423,423,423 4250,4300,428,428,428,428 4300,4350,433,433,433,433 4350,4400,438,438,438,438 4400,4450,443,443,443,443 4450,4500,448,448,448,448 4500,4550,453,453,453,453 4550,4600,458,458,458,458 4600,4650,463,463,463,463 4650,4700,468,468,468,468 4700,4750,473,473,473,473 4750,4800,478,478,478,478 4800,4850,483,483,483,483 4850,4900,488,488,488,488 4900,4950,493,493,493,493 4950,5000,498,498,498,498 5000,5050,503,503,503,503 5050,5100,508,508,508,508 5100,5150,513,513,513,513 5150,5200,518,518,518,518 5200,5250,523,523,523,523 5250,5300,528,528,528,528 5300,5350,533,533,533,533 5350,5400,538,538,538,538 5400,5450,543,543,543,543 5450,5500,548,548,548,548 5500,5550,553,553,553,553 5550,5600,558,558,558,558 5600,5650,563,563,563,563 5650,5700,568,568,568,568 5700,5750,573,573,573,573 5750,5800,578,578,578,578 5800,5850,583,583,583,583 5850,5900,588,588,588,588 5900,5950,593,593,593,593 5950,6000,598,598,598,598 6000,6050,603,603,603,603 6050,6100,608,608,608,608 6100,6150,613,613,613,613 6150,6200,618,618,618,618 6200,6250,623,623,623,623 6250,6300,628,628,628,628 6300,6350,633,633,633,633 6350,6400,638,638,638,638 6400,6450,643,643,643,643 6450,6500,648,648,648,648 6500,6550,653,653,653,653 6550,6600,658,658,658,658 6600,6650,663,663,663,663 6650,6700,668,668,668,668 6700,6750,673,673,673,673 6750,6800,678,678,678,678 6800,6850,683,683,683,683 6850,6900,688,688,688,688 6900,6950,693,693,693,693 6950,7000,698,698,698,698 7000,7050,703,703,703,703 7050,7100,708,708,708,708 7100,7150,713,713,713,713 7150,7200,718,718,718,718 7200,7250,723,723,723,723 7250,7300,728,728,728,728 7300,7350,733,733,733,733 7350,7400,738,738,738,738 7400,7450,743,743,743,743 7450,7500,748,748,748,748 7500,7550,753,753,753,753 7550,7600,758,758,758,758 7600,7650,763,763,763,763 7650,7700,768,768,768,768 7700,7750,773,773,773,773 7750,7800,778,778,778,778 7800,7850,783,783,783,783 7850,7900,788,788,788,788 7900,7950,793,793,793,793 7950,8000,798,798,798,798 8000,8050,803,803,803,803 8050,8100,808,808,808,808 8100,8150,813,813,813,813 8150,8200,818,818,818,818 8200,8250,823,823,823,823 8250,8300,828,828,828,828 8300,8350,833,833,833,833 8350,8400,838,838,838,838 8400,8450,843,843,843,843 8450,8500,848,848,848,848 8500,8550,853,853,853,853 8550,8600,858,858,858,858 8600,8650,863,863,863,863 8650,8700,868,868,868,868 8700,8750,873,873,873,873 8750,8800,878,878,878,878 8800,8850,883,883,883,883 8850,8900,888,888,888,888 8900,8950,893,893,893,893 8950,9000,898,898,898,898 9000,9050,903,903,903,903 9050,9100,908,908,908,908 9100,9150,913,913,913,913 9150,9200,918,918,918,918 9200,9250,923,923,923,923 9250,9300,928,928,928,928 9300,9350,933,933,933,933 9350,9400,938,938,938,938 9400,9450,943,943,943,943 9450,9500,948,948,948,948 9500,9550,953,953,953,953 9550,9600,958,958,958,958 9600,9650,963,963,963,963 9650,9700,968,968,968,968 9700,9750,973,973,973,973 9750,9800,978,978,978,978 9800,9850,983,983,983,983 9850,9900,988,988,988,988 9900,9950,993,993,993,993 9950,10000,998,998,998,998 10000,10050,1004,1003,1004,1003 10050,10100,1010,1008,1010,1008 10100,10150,1016,1013,1016,1013 10150,10200,1022,1018,1022,1018 10200,10250,1028,1023,1028,1023 10250,10300,1034,1028,1034,1028 10300,10350,1040,1033,1040,1033 10350,10400,1046,1038,1046,1038 10400,10450,1052,1043,1052,1043 10450,10500,1058,1048,1058,1048 10500,10550,1064,1053,1064,1053 10550,10600,1070,1058,1070,1058 10600,10650,1076,1063,1076,1063 10650,10700,1082,1068,1082,1068 10700,10750,1088,1073,1088,1073 10750,10800,1094,1078,1094,1078 10800,10850,1100,1083,1100,1083 10850,10900,1106,1088,1106,1088 10900,10950,1112,1093,1112,1093 10950,11000,1118,1098,1118,1098 11000,11050,1124,1103,1124,1103 11050,11100,1130,1108,1130,1108 11100,11150,1136,1113,1136,1113 11150,11200,1142,1118,1142,1118 11200,11250,1148,1123,1148,1123 11250,11300,1154,1128,1154,1128 11300,11350,1160,1133,1160,1133 11350,11400,1166,1138,1166,1138 11400,11450,1172,1143,1172,1143 11450,11500,1178,1148,1178,1148 11500,11550,1184,1153,1184,1153 11550,11600,1190,1158,1190,1158 11600,11650,1196,1163,1196,1163 11650,11700,1202,1168,1202,1168 11700,11750,1208,1173,1208,1173 11750,11800,1214,1178,1214,1178 11800,11850,1220,1183,1220,1183 11850,11900,1226,1188,1226,1188 11900,11950,1232,1193,1232,1193 11950,12000,1238,1198,1238,1198 12000,12050,1244,1203,1244,1203 12050,12100,1250,1208,1250,1208 12100,12150,1256,1213,1256,1213 12150,12200,1262,1218,1262,1218 12200,12250,1268,1223,1268,1223 12250,12300,1274,1228,1274,1228 12300,12350,1280,1233,1280,1233 12350,12400,1286,1238,1286,1238 12400,12450,1292,1243,1292,1243 12450,12500,1298,1248,1298,1248 12500,12550,1304,1253,1304,1253 12550,12600,1310,1258,1310,1258 12600,12650,1316,1263,1316,1263 12650,12700,1322,1268,1322,1268 12700,12750,1328,1273,1328,1273 12750,12800,1334,1278,1334,1278 12800,12850,1340,1283,1340,1283 12850,12900,1346,1288,1346,1288 12900,12950,1352,1293,1352,1293 12950,13000,1358,1298,1358,1298 13000,13050,1364,1303,1364,1303 13050,13100,1370,1308,1370,1308 13100,13150,1376,1313,1376,1313 13150,13200,1382,1318,1382,1318 13200,13250,1388,1323,1388,1323 13250,13300,1394,1328,1394,1328 13300,13350,1400,1333,1400,1333 13350,13400,1406,1338,1406,1338 13400,13450,1412,1343,1412,1343 13450,13500,1418,1348,1418,1348 13500,13550,1424,1353,1424,1353 13550,13600,1430,1358,1430,1358 13600,13650,1436,1363,1436,1363 13650,13700,1442,1368,1442,1368 13700,13750,1448,1373,1448,1373 13750,13800,1454,1378,1454,1378 13800,13850,1460,1383,1460,1383 13850,13900,1466,1388,1466,1388 13900,13950,1472,1393,1472,1393 13950,14000,1478,1398,1478,1398 14000,14050,1484,1403,1484,1403 14050,14100,1490,1408,1490,1408 14100,14150,1496,1413,1496,1413 14150,14200,1502,1418,1502,1418 14200,14250,1508,1423,1508,1423 14250,14300,1514,1428,1514,1429 14300,14350,1520,1433,1520,1435 14350,14400,1526,1438,1526,1441 14400,14450,1532,1443,1532,1447 14450,14500,1538,1448,1538,1453 14500,14550,1544,1453,1544,1459 14550,14600,1550,1458,1550,1465 14600,14650,1556,1463,1556,1471 14650,14700,1562,1468,1562,1477 14700,14750,1568,1473,1568,1483 14750,14800,1574,1478,1574,1489 14800,14850,1580,1483,1580,1495 14850,14900,1586,1488,1586,1501 14900,14950,1592,1493,1592,1507 14950,15000,1598,1498,1598,1513 15000,15050,1604,1503,1604,1519 15050,15100,1610,1508,1610,1525 15100,15150,1616,1513,1616,1531 15150,15200,1622,1518,1622,1537 15200,15250,1628,1523,1628,1543 15250,15300,1634,1528,1634,1549 15300,15350,1640,1533,1640,1555 15350,15400,1646,1538,1646,1561 15400,15450,1652,1543,1652,1567 15450,15500,1658,1548,1658,1573 15500,15550,1664,1553,1664,1579 15550,15600,1670,1558,1670,1585 15600,15650,1676,1563,1676,1591 15650,15700,1682,1568,1682,1597 15700,15750,1688,1573,1688,1603 15750,15800,1694,1578,1694,1609 15800,15850,1700,1583,1700,1615 15850,15900,1706,1588,1706,1621 15900,15950,1712,1593,1712,1627 15950,16000,1718,1598,1718,1633 16000,16050,1724,1603,1724,1639 16050,16100,1730,1608,1730,1645 16100,16150,1736,1613,1736,1651 16150,16200,1742,1618,1742,1657 16200,16250,1748,1623,1748,1663 16250,16300,1754,1628,1754,1669 16300,16350,1760,1633,1760,1675 16350,16400,1766,1638,1766,1681 16400,16450,1772,1643,1772,1687 16450,16500,1778,1648,1778,1693 16500,16550,1784,1653,1784,1699 16550,16600,1790,1658,1790,1705 16600,16650,1796,1663,1796,1711 16650,16700,1802,1668,1802,1717 16700,16750,1808,1673,1808,1723 16750,16800,1814,1678,1814,1729 16800,16850,1820,1683,1820,1735 16850,16900,1826,1688,1826,1741 16900,16950,1832,1693,1832,1747 16950,17000,1838,1698,1838,1753 17000,17050,1844,1703,1844,1759 17050,17100,1850,1708,1850,1765 17100,17150,1856,1713,1856,1771 17150,17200,1862,1718,1862,1777 17200,17250,1868,1723,1868,1783 17250,17300,1874,1728,1874,1789 17300,17350,1880,1733,1880,1795 17350,17400,1886,1738,1886,1801 17400,17450,1892,1743,1892,1807 17450,17500,1898,1748,1898,1813 17500,17550,1904,1753,1904,1819 17550,17600,1910,1758,1910,1825 17600,17650,1916,1763,1916,1831 17650,17700,1922,1768,1922,1837 17700,17750,1928,1773,1928,1843 17750,17800,1934,1778,1934,1849 17800,17850,1940,1783,1940,1855 17850,17900,1946,1788,1946,1861 17900,17950,1952,1793,1952,1867 17950,18000,1958,1798,1958,1873 18000,18050,1964,1803,1964,1879 18050,18100,1970,1808,1970,1885 18100,18150,1976,1813,1976,1891 18150,18200,1982,1818,1982,1897 18200,18250,1988,1823,1988,1903 18250,18300,1994,1828,1994,1909 18300,18350,2000,1833,2000,1915 18350,18400,2006,1838,2006,1921 18400,18450,2012,1843,2012,1927 18450,18500,2018,1848,2018,1933 18500,18550,2024,1853,2024,1939 18550,18600,2030,1858,2030,1945 18600,18650,2036,1863,2036,1951 18650,18700,2042,1868,2042,1957 18700,18750,2048,1873,2048,1963 18750,18800,2054,1878,2054,1969 18800,18850,2060,1883,2060,1975 18850,18900,2066,1888,2066,1981 18900,18950,2072,1893,2072,1987 18950,19000,2078,1898,2078,1993 19000,19050,2084,1903,2084,1999 19050,19100,2090,1908,2090,2005 19100,19150,2096,1913,2096,2011 19150,19200,2102,1918,2102,2017 19200,19250,2108,1923,2108,2023 19250,19300,2114,1928,2114,2029 19300,19350,2120,1933,2120,2035 19350,19400,2126,1938,2126,2041 19400,19450,2132,1943,2132,2047 19450,19500,2138,1948,2138,2053 19500,19550,2144,1953,2144,2059 19550,19600,2150,1958,2150,2065 19600,19650,2156,1963,2156,2071 19650,19700,2162,1968,2162,2077 19700,19750,2168,1973,2168,2083 19750,19800,2174,1978,2174,2089 19800,19850,2180,1983,2180,2095 19850,19900,2186,1988,2186,2101 19900,19950,2192,1993,2192,2107 19950,20000,2198,1999,2198,2113 20000,20050,2204,2005,2204,2119 20050,20100,2210,2011,2210,2125 20100,20150,2216,2017,2216,2131 20150,20200,2222,2023,2222,2137 20200,20250,2228,2029,2228,2143 20250,20300,2234,2035,2234,2149 20300,20350,2240,2041,2240,2155 20350,20400,2246,2047,2246,2161 20400,20450,2252,2053,2252,2167 20450,20500,2258,2059,2258,2173 20500,20550,2264,2065,2264,2179 20550,20600,2270,2071,2270,2185 20600,20650,2276,2077,2276,2191 20650,20700,2282,2083,2282,2197 20700,20750,2288,2089,2288,2203 20750,20800,2294,2095,2294,2209 20800,20850,2300,2101,2300,2215 20850,20900,2306,2107,2306,2221 20900,20950,2312,2113,2312,2227 20950,21000,2318,2119,2318,2233 21000,21050,2324,2125,2324,2239 21050,21100,2330,2131,2330,2245 21100,21150,2336,2137,2336,2251 21150,21200,2342,2143,2342,2257 21200,21250,2348,2149,2348,2263 21250,21300,2354,2155,2354,2269 21300,21350,2360,2161,2360,2275 21350,21400,2366,2167,2366,2281 21400,21450,2372,2173,2372,2287 21450,21500,2378,2179,2378,2293 21500,21550,2384,2185,2384,2299 21550,21600,2390,2191,2390,2305 21600,21650,2396,2197,2396,2311 21650,21700,2402,2203,2402,2317 21700,21750,2408,2209,2408,2323 21750,21800,2414,2215,2414,2329 21800,21850,2420,2221,2420,2335 21850,21900,2426,2227,2426,2341 21900,21950,2432,2233,2432,2347 21950,22000,2438,2239,2438,2353 22000,22050,2444,2245,2444,2359 22050,22100,2450,2251,2450,2365 22100,22150,2456,2257,2456,2371 22150,22200,2462,2263,2462,2377 22200,22250,2468,2269,2468,2383 22250,22300,2474,2275,2474,2389 22300,22350,2480,2281,2480,2395 22350,22400,2486,2287,2486,2401 22400,22450,2492,2293,2492,2407 22450,22500,2498,2299,2498,2413 22500,22550,2504,2305,2504,2419 22550,22600,2510,2311,2510,2425 22600,22650,2516,2317,2516,2431 22650,22700,2522,2323,2522,2437 22700,22750,2528,2329,2528,2443 22750,22800,2534,2335,2534,2449 22800,22850,2540,2341,2540,2455 22850,22900,2546,2347,2546,2461 22900,22950,2552,2353,2552,2467 22950,23000,2558,2359,2558,2473 23000,23050,2564,2365,2564,2479 23050,23100,2570,2371,2570,2485 23100,23150,2576,2377,2576,2491 23150,23200,2582,2383,2582,2497 23200,23250,2588,2389,2588,2503 23250,23300,2594,2395,2594,2509 23300,23350,2600,2401,2600,2515 23350,23400,2606,2407,2606,2521 23400,23450,2612,2413,2612,2527 23450,23500,2618,2419,2618,2533 23500,23550,2624,2425,2624,2539 23550,23600,2630,2431,2630,2545 23600,23650,2636,2437,2636,2551 23650,23700,2642,2443,2642,2557 23700,23750,2648,2449,2648,2563 23750,23800,2654,2455,2654,2569 23800,23850,2660,2461,2660,2575 23850,23900,2666,2467,2666,2581 23900,23950,2672,2473,2672,2587 23950,24000,2678,2479,2678,2593 24000,24050,2684,2485,2684,2599 24050,24100,2690,2491,2690,2605 24100,24150,2696,2497,2696,2611 24150,24200,2702,2503,2702,2617 24200,24250,2708,2509,2708,2623 24250,24300,2714,2515,2714,2629 24300,24350,2720,2521,2720,2635 24350,24400,2726,2527,2726,2641 24400,24450,2732,2533,2732,2647 24450,24500,2738,2539,2738,2653 24500,24550,2744,2545,2744,2659 24550,24600,2750,2551,2750,2665 24600,24650,2756,2557,2756,2671 24650,24700,2762,2563,2762,2677 24700,24750,2768,2569,2768,2683 24750,24800,2774,2575,2774,2689 24800,24850,2780,2581,2780,2695 24850,24900,2786,2587,2786,2701 24900,24950,2792,2593,2792,2707 24950,25000,2798,2599,2798,2713 25000,25050,2804,2605,2804,2719 25050,25100,2810,2611,2810,2725 25100,25150,2816,2617,2816,2731 25150,25200,2822,2623,2822,2737 25200,25250,2828,2629,2828,2743 25250,25300,2834,2635,2834,2749 25300,25350,2840,2641,2840,2755 25350,25400,2846,2647,2846,2761 25400,25450,2852,2653,2852,2767 25450,25500,2858,2659,2858,2773 25500,25550,2864,2665,2864,2779 25550,25600,2870,2671,2870,2785 25600,25650,2876,2677,2876,2791 25650,25700,2882,2683,2882,2797 25700,25750,2888,2689,2888,2803 25750,25800,2894,2695,2894,2809 25800,25850,2900,2701,2900,2815 25850,25900,2906,2707,2906,2821 25900,25950,2912,2713,2912,2827 25950,26000,2918,2719,2918,2833 26000,26050,2924,2725,2924,2839 26050,26100,2930,2731,2930,2845 26100,26150,2936,2737,2936,2851 26150,26200,2942,2743,2942,2857 26200,26250,2948,2749,2948,2863 26250,26300,2954,2755,2954,2869 26300,26350,2960,2761,2960,2875 26350,26400,2966,2767,2966,2881 26400,26450,2972,2773,2972,2887 26450,26500,2978,2779,2978,2893 26500,26550,2984,2785,2984,2899 26550,26600,2990,2791,2990,2905 26600,26650,2996,2797,2996,2911 26650,26700,3002,2803,3002,2917 26700,26750,3008,2809,3008,2923 26750,26800,3014,2815,3014,2929 26800,26850,3020,2821,3020,2935 26850,26900,3026,2827,3026,2941 26900,26950,3032,2833,3032,2947 26950,27000,3038,2839,3038,2953 27000,27050,3044,2845,3044,2959 27050,27100,3050,2851,3050,2965 27100,27150,3056,2857,3056,2971 27150,27200,3062,2863,3062,2977 27200,27250,3068,2869,3068,2983 27250,27300,3074,2875,3074,2989 27300,27350,3080,2881,3080,2995 27350,27400,3086,2887,3086,3001 27400,27450,3092,2893,3092,3007 27450,27500,3098,2899,3098,3013 27500,27550,3104,2905,3104,3019 27550,27600,3110,2911,3110,3025 27600,27650,3116,2917,3116,3031 27650,27700,3122,2923,3122,3037 27700,27750,3128,2929,3128,3043 27750,27800,3134,2935,3134,3049 27800,27850,3140,2941,3140,3055 27850,27900,3146,2947,3146,3061 27900,27950,3152,2953,3152,3067 27950,28000,3158,2959,3158,3073 28000,28050,3164,2965,3164,3079 28050,28100,3170,2971,3170,3085 28100,28150,3176,2977,3176,3091 28150,28200,3182,2983,3182,3097 28200,28250,3188,2989,3188,3103 28250,28300,3194,2995,3194,3109 28300,28350,3200,3001,3200,3115 28350,28400,3206,3007,3206,3121 28400,28450,3212,3013,3212,3127 28450,28500,3218,3019,3218,3133 28500,28550,3224,3025,3224,3139 28550,28600,3230,3031,3230,3145 28600,28650,3236,3037,3236,3151 28650,28700,3242,3043,3242,3157 28700,28750,3248,3049,3248,3163 28750,28800,3254,3055,3254,3169 28800,28850,3260,3061,3260,3175 28850,28900,3266,3067,3266,3181 28900,28950,3272,3073,3272,3187 28950,29000,3278,3079,3278,3193 29000,29050,3284,3085,3284,3199 29050,29100,3290,3091,3290,3205 29100,29150,3296,3097,3296,3211 29150,29200,3302,3103,3302,3217 29200,29250,3308,3109,3308,3223 29250,29300,3314,3115,3314,3229 29300,29350,3320,3121,3320,3235 29350,29400,3326,3127,3326,3241 29400,29450,3332,3133,3332,3247 29450,29500,3338,3139,3338,3253 29500,29550,3344,3145,3344,3259 29550,29600,3350,3151,3350,3265 29600,29650,3356,3157,3356,3271 29650,29700,3362,3163,3362,3277 29700,29750,3368,3169,3368,3283 29750,29800,3374,3175,3374,3289 29800,29850,3380,3181,3380,3295 29850,29900,3386,3187,3386,3301 29900,29950,3392,3193,3392,3307 29950,30000,3398,3199,3398,3313 30000,30050,3404,3205,3404,3319 30050,30100,3410,3211,3410,3325 30100,30150,3416,3217,3416,3331 30150,30200,3422,3223,3422,3337 30200,30250,3428,3229,3428,3343 30250,30300,3434,3235,3434,3349 30300,30350,3440,3241,3440,3355 30350,30400,3446,3247,3446,3361 30400,30450,3452,3253,3452,3367 30450,30500,3458,3259,3458,3373 30500,30550,3464,3265,3464,3379 30550,30600,3470,3271,3470,3385 30600,30650,3476,3277,3476,3391 30650,30700,3482,3283,3482,3397 30700,30750,3488,3289,3488,3403 30750,30800,3494,3295,3494,3409 30800,30850,3500,3301,3500,3415 30850,30900,3506,3307,3506,3421 30900,30950,3512,3313,3512,3427 30950,31000,3518,3319,3518,3433 31000,31050,3524,3325,3524,3439 31050,31100,3530,3331,3530,3445 31100,31150,3536,3337,3536,3451 31150,31200,3542,3343,3542,3457 31200,31250,3548,3349,3548,3463 31250,31300,3554,3355,3554,3469 31300,31350,3560,3361,3560,3475 31350,31400,3566,3367,3566,3481 31400,31450,3572,3373,3572,3487 31450,31500,3578,3379,3578,3493 31500,31550,3584,3385,3584,3499 31550,31600,3590,3391,3590,3505 31600,31650,3596,3397,3596,3511 31650,31700,3602,3403,3602,3517 31700,31750,3608,3409,3608,3523 31750,31800,3614,3415,3614,3529 31800,31850,3620,3421,3620,3535 31850,31900,3626,3427,3626,3541 31900,31950,3632,3433,3632,3547 31950,32000,3638,3439,3638,3553 32000,32050,3644,3445,3644,3559 32050,32100,3650,3451,3650,3565 32100,32150,3656,3457,3656,3571 32150,32200,3662,3463,3662,3577 32200,32250,3668,3469,3668,3583 32250,32300,3674,3475,3674,3589 32300,32350,3680,3481,3680,3595 32350,32400,3686,3487,3686,3601 32400,32450,3692,3493,3692,3607 32450,32500,3698,3499,3698,3613 32500,32550,3704,3505,3704,3619 32550,32600,3710,3511,3710,3625 32600,32650,3716,3517,3716,3631 32650,32700,3722,3523,3722,3637 32700,32750,3728,3529,3728,3643 32750,32800,3734,3535,3734,3649 32800,32850,3740,3541,3740,3655 32850,32900,3746,3547,3746,3661 32900,32950,3752,3553,3752,3667 32950,33000,3758,3559,3758,3673 33000,33050,3764,3565,3764,3679 33050,33100,3770,3571,3770,3685 33100,33150,3776,3577,3776,3691 33150,33200,3782,3583,3782,3697 33200,33250,3788,3589,3788,3703 33250,33300,3794,3595,3794,3709 33300,33350,3800,3601,3800,3715 33350,33400,3806,3607,3806,3721 33400,33450,3812,3613,3812,3727 33450,33500,3818,3619,3818,3733 33500,33550,3824,3625,3824,3739 33550,33600,3830,3631,3830,3745 33600,33650,3836,3637,3836,3751 33650,33700,3842,3643,3842,3757 33700,33750,3848,3649,3848,3763 33750,33800,3854,3655,3854,3769 33800,33850,3860,3661,3860,3775 33850,33900,3866,3667,3866,3781 33900,33950,3872,3673,3872,3787 33950,34000,3878,3679,3878,3793 34000,34050,3884,3685,3884,3799 34050,34100,3890,3691,3890,3805 34100,34150,3896,3697,3896,3811 34150,34200,3902,3703,3902,3817 34200,34250,3908,3709,3908,3823 34250,34300,3914,3715,3914,3829 34300,34350,3920,3721,3920,3835 34350,34400,3926,3727,3926,3841 34400,34450,3932,3733,3932,3847 34450,34500,3938,3739,3938,3853 34500,34550,3944,3745,3944,3859 34550,34600,3950,3751,3950,3865 34600,34650,3956,3757,3956,3871 34650,34700,3962,3763,3962,3877 34700,34750,3968,3769,3968,3883 34750,34800,3974,3775,3974,3889 34800,34850,3980,3781,3980,3895 34850,34900,3986,3787,3986,3901 34900,34950,3992,3793,3992,3907 34950,35000,3998,3799,3998,3913 35000,35050,4004,3805,4004,3919 35050,35100,4010,3811,4010,3925 35100,35150,4016,3817,4016,3931 35150,35200,4022,3823,4022,3937 35200,35250,4028,3829,4028,3943 35250,35300,4034,3835,4034,3949 35300,35350,4040,3841,4040,3955 35350,35400,4046,3847,4046,3961 35400,35450,4052,3853,4052,3967 35450,35500,4058,3859,4058,3973 35500,35550,4064,3865,4064,3979 35550,35600,4070,3871,4070,3985 35600,35650,4076,3877,4076,3991 35650,35700,4082,3883,4082,3997 35700,35750,4088,3889,4088,4003 35750,35800,4094,3895,4094,4009 35800,35850,4100,3901,4100,4015 35850,35900,4106,3907,4106,4021 35900,35950,4112,3913,4112,4027 35950,36000,4118,3919,4118,4033 36000,36050,4124,3925,4124,4039 36050,36100,4130,3931,4130,4045 36100,36150,4136,3937,4136,4051 36150,36200,4142,3943,4142,4057 36200,36250,4148,3949,4148,4063 36250,36300,4154,3955,4154,4069 36300,36350,4160,3961,4160,4075 36350,36400,4166,3967,4166,4081 36400,36450,4172,3973,4172,4087 36450,36500,4178,3979,4178,4093 36500,36550,4184,3985,4184,4099 36550,36600,4190,3991,4190,4105 36600,36650,4196,3997,4196,4111 36650,36700,4202,4003,4202,4117 36700,36750,4208,4009,4208,4123 36750,36800,4214,4015,4214,4129 36800,36850,4220,4021,4220,4135 36850,36900,4226,4027,4226,4141 36900,36950,4232,4033,4232,4147 36950,37000,4238,4039,4238,4153 37000,37050,4244,4045,4244,4159 37050,37100,4250,4051,4250,4165 37100,37150,4256,4057,4256,4171 37150,37200,4262,4063,4262,4177 37200,37250,4268,4069,4268,4183 37250,37300,4274,4075,4274,4189 37300,37350,4280,4081,4280,4195 37350,37400,4286,4087,4286,4201 37400,37450,4292,4093,4292,4207 37450,37500,4298,4099,4298,4213 37500,37550,4304,4105,4304,4219 37550,37600,4310,4111,4310,4225 37600,37650,4316,4117,4316,4231 37650,37700,4322,4123,4322,4237 37700,37750,4328,4129,4328,4243 37750,37800,4334,4135,4334,4249 37800,37850,4340,4141,4340,4255 37850,37900,4346,4147,4346,4261 37900,37950,4352,4153,4352,4267 37950,38000,4358,4159,4358,4273 38000,38050,4364,4165,4364,4279 38050,38100,4370,4171,4370,4285 38100,38150,4376,4177,4376,4291 38150,38200,4382,4183,4382,4297 38200,38250,4388,4189,4388,4303 38250,38300,4394,4195,4394,4309 38300,38350,4400,4201,4400,4315 38350,38400,4406,4207,4406,4321 38400,38450,4412,4213,4412,4327 38450,38500,4418,4219,4418,4333 38500,38550,4424,4225,4424,4339 38550,38600,4430,4231,4430,4345 38600,38650,4436,4237,4436,4351 38650,38700,4442,4243,4442,4357 38700,38750,4448,4249,4448,4363 38750,38800,4454,4255,4454,4369 38800,38850,4460,4261,4460,4375 38850,38900,4466,4267,4466,4381 38900,38950,4472,4273,4472,4387 38950,39000,4478,4279,4478,4393 39000,39050,4484,4285,4484,4399 39050,39100,4490,4291,4490,4405 39100,39150,4496,4297,4496,4411 39150,39200,4502,4303,4502,4417 39200,39250,4508,4309,4508,4423 39250,39300,4514,4315,4514,4429 39300,39350,4520,4321,4520,4435 39350,39400,4526,4327,4526,4441 39400,39450,4532,4333,4532,4447 39450,39500,4538,4339,4538,4453 39500,39550,4544,4345,4544,4459 39550,39600,4550,4351,4550,4465 39600,39650,4556,4357,4556,4471 39650,39700,4562,4363,4562,4477 39700,39750,4568,4369,4568,4483 39750,39800,4574,4375,4574,4489 39800,39850,4580,4381,4580,4495 39850,39900,4586,4387,4586,4501 39900,39950,4592,4393,4592,4507 39950,40000,4598,4399,4598,4513 40000,40050,4604,4405,4604,4519 40050,40100,4610,4411,4610,4525 40100,40150,4616,4417,4616,4531 40150,40200,4622,4423,4622,4537 40200,40250,4628,4429,4628,4543 40250,40300,4634,4435,4634,4549 40300,40350,4640,4441,4640,4555 40350,40400,4646,4447,4646,4561 40400,40450,4652,4453,4652,4567 40450,40500,4658,4459,4658,4573 40500,40550,4664,4465,4664,4579 40550,40600,4675,4471,4675,4585 40600,40650,4686,4477,4686,4591 40650,40700,4697,4483,4697,4597 40700,40750,4708,4489,4708,4603 40750,40800,4719,4495,4719,4609 40800,40850,4730,4501,4730,4615 40850,40900,4741,4507,4741,4621 40900,40950,4752,4513,4752,4627 40950,41000,4763,4519,4763,4633 41000,41050,4774,4525,4774,4639 41050,41100,4785,4531,4785,4645 41100,41150,4796,4537,4796,4651 41150,41200,4807,4543,4807,4657 41200,41250,4818,4549,4818,4663 41250,41300,4829,4555,4829,4669 41300,41350,4840,4561,4840,4675 41350,41400,4851,4567,4851,4681 41400,41450,4862,4573,4862,4687 41450,41500,4873,4579,4873,4693 41500,41550,4884,4585,4884,4699 41550,41600,4895,4591,4895,4705 41600,41650,4906,4597,4906,4711 41650,41700,4917,4603,4917,4717 41700,41750,4928,4609,4928,4723 41750,41800,4939,4615,4939,4729 41800,41850,4950,4621,4950,4735 41850,41900,4961,4627,4961,4741 41900,41950,4972,4633,4972,4747 41950,42000,4983,4639,4983,4753 42000,42050,4994,4645,4994,4759 42050,42100,5005,4651,5005,4765 42100,42150,5016,4657,5016,4771 42150,42200,5027,4663,5027,4777 42200,42250,5038,4669,5038,4783 42250,42300,5049,4675,5049,4789 42300,42350,5060,4681,5060,4795 42350,42400,5071,4687,5071,4801 42400,42450,5082,4693,5082,4807 42450,42500,5093,4699,5093,4813 42500,42550,5104,4705,5104,4819 42550,42600,5115,4711,5115,4825 42600,42650,5126,4717,5126,4831 42650,42700,5137,4723,5137,4837 42700,42750,5148,4729,5148,4843 42750,42800,5159,4735,5159,4849 42800,42850,5170,4741,5170,4855 42850,42900,5181,4747,5181,4861 42900,42950,5192,4753,5192,4867 42950,43000,5203,4759,5203,4873 43000,43050,5214,4765,5214,4879 43050,43100,5225,4771,5225,4885 43100,43150,5236,4777,5236,4891 43150,43200,5247,4783,5247,4897 43200,43250,5258,4789,5258,4903 43250,43300,5269,4795,5269,4909 43300,43350,5280,4801,5280,4915 43350,43400,5291,4807,5291,4921 43400,43450,5302,4813,5302,4927 43450,43500,5313,4819,5313,4933 43500,43550,5324,4825,5324,4939 43550,43600,5335,4831,5335,4945 43600,43650,5346,4837,5346,4951 43650,43700,5357,4843,5357,4957 43700,43750,5368,4849,5368,4963 43750,43800,5379,4855,5379,4969 43800,43850,5390,4861,5390,4975 43850,43900,5401,4867,5401,4981 43900,43950,5412,4873,5412,4987 43950,44000,5423,4879,5423,4993 44000,44050,5434,4885,5434,4999 44050,44100,5445,4891,5445,5005 44100,44150,5456,4897,5456,5011 44150,44200,5467,4903,5467,5017 44200,44250,5478,4909,5478,5023 44250,44300,5489,4915,5489,5029 44300,44350,5500,4921,5500,5035 44350,44400,5511,4927,5511,5041 44400,44450,5522,4933,5522,5047 44450,44500,5533,4939,5533,5053 44500,44550,5544,4945,5544,5059 44550,44600,5555,4951,5555,5065 44600,44650,5566,4957,5566,5071 44650,44700,5577,4963,5577,5077 44700,44750,5588,4969,5588,5083 44750,44800,5599,4975,5599,5089 44800,44850,5610,4981,5610,5095 44850,44900,5621,4987,5621,5101 44900,44950,5632,4993,5632,5107 44950,45000,5643,4999,5643,5113 45000,45050,5654,5005,5654,5119 45050,45100,5665,5011,5665,5125 45100,45150,5676,5017,5676,5131 45150,45200,5687,5023,5687,5137 45200,45250,5698,5029,5698,5143 45250,45300,5709,5035,5709,5149 45300,45350,5720,5041,5720,5155 45350,45400,5731,5047,5731,5161 45400,45450,5742,5053,5742,5167 45450,45500,5753,5059,5753,5173 45500,45550,5764,5065,5764,5179 45550,45600,5775,5071,5775,5185 45600,45650,5786,5077,5786,5191 45650,45700,5797,5083,5797,5197 45700,45750,5808,5089,5808,5203 45750,45800,5819,5095,5819,5209 45800,45850,5830,5101,5830,5215 45850,45900,5841,5107,5841,5221 45900,45950,5852,5113,5852,5227 45950,46000,5863,5119,5863,5233 46000,46050,5874,5125,5874,5239 46050,46100,5885,5131,5885,5245 46100,46150,5896,5137,5896,5251 46150,46200,5907,5143,5907,5257 46200,46250,5918,5149,5918,5263 46250,46300,5929,5155,5929,5269 46300,46350,5940,5161,5940,5275 46350,46400,5951,5167,5951,5281 46400,46450,5962,5173,5962,5287 46450,46500,5973,5179,5973,5293 46500,46550,5984,5185,5984,5299 46550,46600,5995,5191,5995,5305 46600,46650,6006,5197,6006,5311 46650,46700,6017,5203,6017,5317 46700,46750,6028,5209,6028,5323 46750,46800,6039,5215,6039,5329 46800,46850,6050,5221,6050,5335 46850,46900,6061,5227,6061,5341 46900,46950,6072,5233,6072,5347 46950,47000,6083,5239,6083,5353 47000,47050,6094,5245,6094,5359 47050,47100,6105,5251,6105,5365 47100,47150,6116,5257,6116,5371 47150,47200,6127,5263,6127,5377 47200,47250,6138,5269,6138,5383 47250,47300,6149,5275,6149,5389 47300,47350,6160,5281,6160,5395 47350,47400,6171,5287,6171,5401 47400,47450,6182,5293,6182,5407 47450,47500,6193,5299,6193,5413 47500,47550,6204,5305,6204,5419 47550,47600,6215,5311,6215,5425 47600,47650,6226,5317,6226,5431 47650,47700,6237,5323,6237,5437 47700,47750,6248,5329,6248,5443 47750,47800,6259,5335,6259,5449 47800,47850,6270,5341,6270,5455 47850,47900,6281,5347,6281,5461 47900,47950,6292,5353,6292,5467 47950,48000,6303,5359,6303,5473 48000,48050,6314,5365,6314,5479 48050,48100,6325,5371,6325,5485 48100,48150,6336,5377,6336,5491 48150,48200,6347,5383,6347,5497 48200,48250,6358,5389,6358,5503 48250,48300,6369,5395,6369,5509 48300,48350,6380,5401,6380,5515 48350,48400,6391,5407,6391,5521 48400,48450,6402,5413,6402,5527 48450,48500,6413,5419,6413,5533 48500,48550,6424,5425,6424,5539 48550,48600,6435,5431,6435,5545 48600,48650,6446,5437,6446,5551 48650,48700,6457,5443,6457,5557 48700,48750,6468,5449,6468,5563 48750,48800,6479,5455,6479,5569 48800,48850,6490,5461,6490,5575 48850,48900,6501,5467,6501,5581 48900,48950,6512,5473,6512,5587 48950,49000,6523,5479,6523,5593 49000,49050,6534,5485,6534,5599 49050,49100,6545,5491,6545,5605 49100,49150,6556,5497,6556,5611 49150,49200,6567,5503,6567,5617 49200,49250,6578,5509,6578,5623 49250,49300,6589,5515,6589,5629 49300,49350,6600,5521,6600,5635 49350,49400,6611,5527,6611,5641 49400,49450,6622,5533,6622,5647 49450,49500,6633,5539,6633,5653 49500,49550,6644,5545,6644,5659 49550,49600,6655,5551,6655,5665 49600,49650,6666,5557,6666,5671 49650,49700,6677,5563,6677,5677 49700,49750,6688,5569,6688,5683 49750,49800,6699,5575,6699,5689 49800,49850,6710,5581,6710,5695 49850,49900,6721,5587,6721,5701 49900,49950,6732,5593,6732,5707 49950,50000,6743,5599,6743,5713 50000,50050,6754,5605,6754,5719 50050,50100,6765,5611,6765,5725 50100,50150,6776,5617,6776,5731 50150,50200,6787,5623,6787,5737 50200,50250,6798,5629,6798,5743 50250,50300,6809,5635,6809,5749 50300,50350,6820,5641,6820,5755 50350,50400,6831,5647,6831,5761 50400,50450,6842,5653,6842,5767 50450,50500,6853,5659,6853,5773 50500,50550,6864,5665,6864,5779 50550,50600,6875,5671,6875,5785 50600,50650,6886,5677,6886,5791 50650,50700,6897,5683,6897,5797 50700,50750,6908,5689,6908,5803 50750,50800,6919,5695,6919,5809 50800,50850,6930,5701,6930,5815 50850,50900,6941,5707,6941,5821 50900,50950,6952,5713,6952,5827 50950,51000,6963,5719,6963,5833 51000,51050,6974,5725,6974,5839 51050,51100,6985,5731,6985,5845 51100,51150,6996,5737,6996,5851 51150,51200,7007,5743,7007,5857 51200,51250,7018,5749,7018,5863 51250,51300,7029,5755,7029,5869 51300,51350,7040,5761,7040,5875 51350,51400,7051,5767,7051,5881 51400,51450,7062,5773,7062,5887 51450,51500,7073,5779,7073,5893 51500,51550,7084,5785,7084,5899 51550,51600,7095,5791,7095,5905 51600,51650,7106,5797,7106,5911 51650,51700,7117,5803,7117,5917 51700,51750,7128,5809,7128,5923 51750,51800,7139,5815,7139,5929 51800,51850,7150,5821,7150,5935 51850,51900,7161,5827,7161,5941 51900,51950,7172,5833,7172,5947 51950,52000,7183,5839,7183,5953 52000,52050,7194,5845,7194,5959 52050,52100,7205,5851,7205,5965 52100,52150,7216,5857,7216,5971 52150,52200,7227,5863,7227,5977 52200,52250,7238,5869,7238,5983 52250,52300,7249,5875,7249,5989 52300,52350,7260,5881,7260,5995 52350,52400,7271,5887,7271,6001 52400,52450,7282,5893,7282,6007 52450,52500,7293,5899,7293,6013 52500,52550,7304,5905,7304,6019 52550,52600,7315,5911,7315,6025 52600,52650,7326,5917,7326,6031 52650,52700,7337,5923,7337,6037 52700,52750,7348,5929,7348,6043 52750,52800,7359,5935,7359,6049 52800,52850,7370,5941,7370,6055 52850,52900,7381,5947,7381,6061 52900,52950,7392,5953,7392,6067 52950,53000,7403,5959,7403,6073 53000,53050,7414,5965,7414,6079 53050,53100,7425,5971,7425,6085 53100,53150,7436,5977,7436,6091 53150,53200,7447,5983,7447,6097 53200,53250,7458,5989,7458,6103 53250,53300,7469,5995,7469,6109 53300,53350,7480,6001,7480,6115 53350,53400,7491,6007,7491,6121 53400,53450,7502,6013,7502,6127 53450,53500,7513,6019,7513,6133 53500,53550,7524,6025,7524,6139 53550,53600,7535,6031,7535,6145 53600,53650,7546,6037,7546,6151 53650,53700,7557,6043,7557,6157 53700,53750,7568,6049,7568,6163 53750,53800,7579,6055,7579,6169 53800,53850,7590,6061,7590,6175 53850,53900,7601,6067,7601,6181 53900,53950,7612,6073,7612,6187 53950,54000,7623,6079,7623,6193 54000,54050,7634,6085,7634,6199 54050,54100,7645,6091,7645,6205 54100,54150,7656,6097,7656,6211 54150,54200,7667,6103,7667,6217 54200,54250,7678,6109,7678,6226 54250,54300,7689,6115,7689,6237 54300,54350,7700,6121,7700,6248 54350,54400,7711,6127,7711,6259 54400,54450,7722,6133,7722,6270 54450,54500,7733,6139,7733,6281 54500,54550,7744,6145,7744,6292 54550,54600,7755,6151,7755,6303 54600,54650,7766,6157,7766,6314 54650,54700,7777,6163,7777,6325 54700,54750,7788,6169,7788,6336 54750,54800,7799,6175,7799,6347 54800,54850,7810,6181,7810,6358 54850,54900,7821,6187,7821,6369 54900,54950,7832,6193,7832,6380 54950,55000,7843,6199,7843,6391 55000,55050,7854,6205,7854,6402 55050,55100,7865,6211,7865,6413 55100,55150,7876,6217,7876,6424 55150,55200,7887,6223,7887,6435 55200,55250,7898,6229,7898,6446 55250,55300,7909,6235,7909,6457 55300,55350,7920,6241,7920,6468 55350,55400,7931,6247,7931,6479 55400,55450,7942,6253,7942,6490 55450,55500,7953,6259,7953,6501 55500,55550,7964,6265,7964,6512 55550,55600,7975,6271,7975,6523 55600,55650,7986,6277,7986,6534 55650,55700,7997,6283,7997,6545 55700,55750,8008,6289,8008,6556 55750,55800,8019,6295,8019,6567 55800,55850,8030,6301,8030,6578 55850,55900,8041,6307,8041,6589 55900,55950,8052,6313,8052,6600 55950,56000,8063,6319,8063,6611 56000,56050,8074,6325,8074,6622 56050,56100,8085,6331,8085,6633 56100,56150,8096,6337,8096,6644 56150,56200,8107,6343,8107,6655 56200,56250,8118,6349,8118,6666 56250,56300,8129,6355,8129,6677 56300,56350,8140,6361,8140,6688 56350,56400,8151,6367,8151,6699 56400,56450,8162,6373,8162,6710 56450,56500,8173,6379,8173,6721 56500,56550,8184,6385,8184,6732 56550,56600,8195,6391,8195,6743 56600,56650,8206,6397,8206,6754 56650,56700,8217,6403,8217,6765 56700,56750,8228,6409,8228,6776 56750,56800,8239,6415,8239,6787 56800,56850,8250,6421,8250,6798 56850,56900,8261,6427,8261,6809 56900,56950,8272,6433,8272,6820 56950,57000,8283,6439,8283,6831 57000,57050,8294,6445,8294,6842 57050,57100,8305,6451,8305,6853 57100,57150,8316,6457,8316,6864 57150,57200,8327,6463,8327,6875 57200,57250,8338,6469,8338,6886 57250,57300,8349,6475,8349,6897 57300,57350,8360,6481,8360,6908 57350,57400,8371,6487,8371,6919 57400,57450,8382,6493,8382,6930 57450,57500,8393,6499,8393,6941 57500,57550,8404,6505,8404,6952 57550,57600,8415,6511,8415,6963 57600,57650,8426,6517,8426,6974 57650,57700,8437,6523,8437,6985 57700,57750,8448,6529,8448,6996 57750,57800,8459,6535,8459,7007 57800,57850,8470,6541,8470,7018 57850,57900,8481,6547,8481,7029 57900,57950,8492,6553,8492,7040 57950,58000,8503,6559,8503,7051 58000,58050,8514,6565,8514,7062 58050,58100,8525,6571,8525,7073 58100,58150,8536,6577,8536,7084 58150,58200,8547,6583,8547,7095 58200,58250,8558,6589,8558,7106 58250,58300,8569,6595,8569,7117 58300,58350,8580,6601,8580,7128 58350,58400,8591,6607,8591,7139 58400,58450,8602,6613,8602,7150 58450,58500,8613,6619,8613,7161 58500,58550,8624,6625,8624,7172 58550,58600,8635,6631,8635,7183 58600,58650,8646,6637,8646,7194 58650,58700,8657,6643,8657,7205 58700,58750,8668,6649,8668,7216 58750,58800,8679,6655,8679,7227 58800,58850,8690,6661,8690,7238 58850,58900,8701,6667,8701,7249 58900,58950,8712,6673,8712,7260 58950,59000,8723,6679,8723,7271 59000,59050,8734,6685,8734,7282 59050,59100,8745,6691,8745,7293 59100,59150,8756,6697,8756,7304 59150,59200,8767,6703,8767,7315 59200,59250,8778,6709,8778,7326 59250,59300,8789,6715,8789,7337 59300,59350,8800,6721,8800,7348 59350,59400,8811,6727,8811,7359 59400,59450,8822,6733,8822,7370 59450,59500,8833,6739,8833,7381 59500,59550,8844,6745,8844,7392 59550,59600,8855,6751,8855,7403 59600,59650,8866,6757,8866,7414 59650,59700,8877,6763,8877,7425 59700,59750,8888,6769,8888,7436 59750,59800,8899,6775,8899,7447 59800,59850,8910,6781,8910,7458 59850,59900,8921,6787,8921,7469 59900,59950,8932,6793,8932,7480 59950,60000,8943,6799,8943,7491 60000,60050,8954,6805,8954,7502 60050,60100,8965,6811,8965,7513 60100,60150,8976,6817,8976,7524 60150,60200,8987,6823,8987,7535 60200,60250,8998,6829,8998,7546 60250,60300,9009,6835,9009,7557 60300,60350,9020,6841,9020,7568 60350,60400,9031,6847,9031,7579 60400,60450,9042,6853,9042,7590 60450,60500,9053,6859,9053,7601 60500,60550,9064,6865,9064,7612 60550,60600,9075,6871,9075,7623 60600,60650,9086,6877,9086,7634 60650,60700,9097,6883,9097,7645 60700,60750,9108,6889,9108,7656 60750,60800,9119,6895,9119,7667 60800,60850,9130,6901,9130,7678 60850,60900,9141,6907,9141,7689 60900,60950,9152,6913,9152,7700 60950,61000,9163,6919,9163,7711 61000,61050,9174,6925,9174,7722 61050,61100,9185,6931,9185,7733 61100,61150,9196,6937,9196,7744 61150,61200,9207,6943,9207,7755 61200,61250,9218,6949,9218,7766 61250,61300,9229,6955,9229,7777 61300,61350,9240,6961,9240,7788 61350,61400,9251,6967,9251,7799 61400,61450,9262,6973,9262,7810 61450,61500,9273,6979,9273,7821 61500,61550,9284,6985,9284,7832 61550,61600,9295,6991,9295,7843 61600,61650,9306,6997,9306,7854 61650,61700,9317,7003,9317,7865 61700,61750,9328,7009,9328,7876 61750,61800,9339,7015,9339,7887 61800,61850,9350,7021,9350,7898 61850,61900,9361,7027,9361,7909 61900,61950,9372,7033,9372,7920 61950,62000,9383,7039,9383,7931 62000,62050,9394,7045,9394,7942 62050,62100,9405,7051,9405,7953 62100,62150,9416,7057,9416,7964 62150,62200,9427,7063,9427,7975 62200,62250,9438,7069,9438,7986 62250,62300,9449,7075,9449,7997 62300,62350,9460,7081,9460,8008 62350,62400,9471,7087,9471,8019 62400,62450,9482,7093,9482,8030 62450,62500,9493,7099,9493,8041 62500,62550,9504,7105,9504,8052 62550,62600,9515,7111,9515,8063 62600,62650,9526,7117,9526,8074 62650,62700,9537,7123,9537,8085 62700,62750,9548,7129,9548,8096 62750,62800,9559,7135,9559,8107 62800,62850,9570,7141,9570,8118 62850,62900,9581,7147,9581,8129 62900,62950,9592,7153,9592,8140 62950,63000,9603,7159,9603,8151 63000,63050,9614,7165,9614,8162 63050,63100,9625,7171,9625,8173 63100,63150,9636,7177,9636,8184 63150,63200,9647,7183,9647,8195 63200,63250,9658,7189,9658,8206 63250,63300,9669,7195,9669,8217 63300,63350,9680,7201,9680,8228 63350,63400,9691,7207,9691,8239 63400,63450,9702,7213,9702,8250 63450,63500,9713,7219,9713,8261 63500,63550,9724,7225,9724,8272 63550,63600,9735,7231,9735,8283 63600,63650,9746,7237,9746,8294 63650,63700,9757,7243,9757,8305 63700,63750,9768,7249,9768,8316 63750,63800,9779,7255,9779,8327 63800,63850,9790,7261,9790,8338 63850,63900,9801,7267,9801,8349 63900,63950,9812,7273,9812,8360 63950,64000,9823,7279,9823,8371 64000,64050,9834,7285,9834,8382 64050,64100,9845,7291,9845,8393 64100,64150,9856,7297,9856,8404 64150,64200,9867,7303,9867,8415 64200,64250,9878,7309,9878,8426 64250,64300,9889,7315,9889,8437 64300,64350,9900,7321,9900,8448 64350,64400,9911,7327,9911,8459 64400,64450,9922,7333,9922,8470 64450,64500,9933,7339,9933,8481 64500,64550,9944,7345,9944,8492 64550,64600,9955,7351,9955,8503 64600,64650,9966,7357,9966,8514 64650,64700,9977,7363,9977,8525 64700,64750,9988,7369,9988,8536 64750,64800,9999,7375,9999,8547 64800,64850,10010,7381,10010,8558 64850,64900,10021,7387,10021,8569 64900,64950,10032,7393,10032,8580 64950,65000,10043,7399,10043,8591 65000,65050,10054,7405,10054,8602 65050,65100,10065,7411,10065,8613 65100,65150,10076,7417,10076,8624 65150,65200,10087,7423,10087,8635 65200,65250,10098,7429,10098,8646 65250,65300,10109,7435,10109,8657 65300,65350,10120,7441,10120,8668 65350,65400,10131,7447,10131,8679 65400,65450,10142,7453,10142,8690 65450,65500,10153,7459,10153,8701 65500,65550,10164,7465,10164,8712 65550,65600,10175,7471,10175,8723 65600,65650,10186,7477,10186,8734 65650,65700,10197,7483,10197,8745 65700,65750,10208,7489,10208,8756 65750,65800,10219,7495,10219,8767 65800,65850,10230,7501,10230,8778 65850,65900,10241,7507,10241,8789 65900,65950,10252,7513,10252,8800 65950,66000,10263,7519,10263,8811 66000,66050,10274,7525,10274,8822 66050,66100,10285,7531,10285,8833 66100,66150,10296,7537,10296,8844 66150,66200,10307,7543,10307,8855 66200,66250,10318,7549,10318,8866 66250,66300,10329,7555,10329,8877 66300,66350,10340,7561,10340,8888 66350,66400,10351,7567,10351,8899 66400,66450,10362,7573,10362,8910 66450,66500,10373,7579,10373,8921 66500,66550,10384,7585,10384,8932 66550,66600,10395,7591,10395,8943 66600,66650,10406,7597,10406,8954 66650,66700,10417,7603,10417,8965 66700,66750,10428,7609,10428,8976 66750,66800,10439,7615,10439,8987 66800,66850,10450,7621,10450,8998 66850,66900,10461,7627,10461,9009 66900,66950,10472,7633,10472,9020 66950,67000,10483,7639,10483,9031 67000,67050,10494,7645,10494,9042 67050,67100,10505,7651,10505,9053 67100,67150,10516,7657,10516,9064 67150,67200,10527,7663,10527,9075 67200,67250,10538,7669,10538,9086 67250,67300,10549,7675,10549,9097 67300,67350,10560,7681,10560,9108 67350,67400,10571,7687,10571,9119 67400,67450,10582,7693,10582,9130 67450,67500,10593,7699,10593,9141 67500,67550,10604,7705,10604,9152 67550,67600,10615,7711,10615,9163 67600,67650,10626,7717,10626,9174 67650,67700,10637,7723,10637,9185 67700,67750,10648,7729,10648,9196 67750,67800,10659,7735,10659,9207 67800,67850,10670,7741,10670,9218 67850,67900,10681,7747,10681,9229 67900,67950,10692,7753,10692,9240 67950,68000,10703,7759,10703,9251 68000,68050,10714,7765,10714,9262 68050,68100,10725,7771,10725,9273 68100,68150,10736,7777,10736,9284 68150,68200,10747,7783,10747,9295 68200,68250,10758,7789,10758,9306 68250,68300,10769,7795,10769,9317 68300,68350,10780,7801,10780,9328 68350,68400,10791,7807,10791,9339 68400,68450,10802,7813,10802,9350 68450,68500,10813,7819,10813,9361 68500,68550,10824,7825,10824,9372 68550,68600,10835,7831,10835,9383 68600,68650,10846,7837,10846,9394 68650,68700,10857,7843,10857,9405 68700,68750,10868,7849,10868,9416 68750,68800,10879,7855,10879,9427 68800,68850,10890,7861,10890,9438 68850,68900,10901,7867,10901,9449 68900,68950,10912,7873,10912,9460 68950,69000,10923,7879,10923,9471 69000,69050,10934,7885,10934,9482 69050,69100,10945,7891,10945,9493 69100,69150,10956,7897,10956,9504 69150,69200,10967,7903,10967,9515 69200,69250,10978,7909,10978,9526 69250,69300,10989,7915,10989,9537 69300,69350,11000,7921,11000,9548 69350,69400,11011,7927,11011,9559 69400,69450,11022,7933,11022,9570 69450,69500,11033,7939,11033,9581 69500,69550,11044,7945,11044,9592 69550,69600,11055,7951,11055,9603 69600,69650,11066,7957,11066,9614 69650,69700,11077,7963,11077,9625 69700,69750,11088,7969,11088,9636 69750,69800,11099,7975,11099,9647 69800,69850,11110,7981,11110,9658 69850,69900,11121,7987,11121,9669 69900,69950,11132,7993,11132,9680 69950,70000,11143,7999,11143,9691 70000,70050,11154,8005,11154,9702 70050,70100,11165,8011,11165,9713 70100,70150,11176,8017,11176,9724 70150,70200,11187,8023,11187,9735 70200,70250,11198,8029,11198,9746 70250,70300,11209,8035,11209,9757 70300,70350,11220,8041,11220,9768 70350,70400,11231,8047,11231,9779 70400,70450,11242,8053,11242,9790 70450,70500,11253,8059,11253,9801 70500,70550,11264,8065,11264,9812 70550,70600,11275,8071,11275,9823 70600,70650,11286,8077,11286,9834 70650,70700,11297,8083,11297,9845 70700,70750,11308,8089,11308,9856 70750,70800,11319,8095,11319,9867 70800,70850,11330,8101,11330,9878 70850,70900,11341,8107,11341,9889 70900,70950,11352,8113,11352,9900 70950,71000,11363,8119,11363,9911 71000,71050,11374,8125,11374,9922 71050,71100,11385,8131,11385,9933 71100,71150,11396,8137,11396,9944 71150,71200,11407,8143,11407,9955 71200,71250,11418,8149,11418,9966 71250,71300,11429,8155,11429,9977 71300,71350,11440,8161,11440,9988 71350,71400,11451,8167,11451,9999 71400,71450,11462,8173,11462,10010 71450,71500,11473,8179,11473,10021 71500,71550,11484,8185,11484,10032 71550,71600,11495,8191,11495,10043 71600,71650,11506,8197,11506,10054 71650,71700,11517,8203,11517,10065 71700,71750,11528,8209,11528,10076 71750,71800,11539,8215,11539,10087 71800,71850,11550,8221,11550,10098 71850,71900,11561,8227,11561,10109 71900,71950,11572,8233,11572,10120 71950,72000,11583,8239,11583,10131 72000,72050,11594,8245,11594,10142 72050,72100,11605,8251,11605,10153 72100,72150,11616,8257,11616,10164 72150,72200,11627,8263,11627,10175 72200,72250,11638,8269,11638,10186 72250,72300,11649,8275,11649,10197 72300,72350,11660,8281,11660,10208 72350,72400,11671,8287,11671,10219 72400,72450,11682,8293,11682,10230 72450,72500,11693,8299,11693,10241 72500,72550,11704,8305,11704,10252 72550,72600,11715,8311,11715,10263 72600,72650,11726,8317,11726,10274 72650,72700,11737,8323,11737,10285 72700,72750,11748,8329,11748,10296 72750,72800,11759,8335,11759,10307 72800,72850,11770,8341,11770,10318 72850,72900,11781,8347,11781,10329 72900,72950,11792,8353,11792,10340 72950,73000,11803,8359,11803,10351 73000,73050,11814,8365,11814,10362 73050,73100,11825,8371,11825,10373 73100,73150,11836,8377,11836,10384 73150,73200,11847,8383,11847,10395 73200,73250,11858,8389,11858,10406 73250,73300,11869,8395,11869,10417 73300,73350,11880,8401,11880,10428 73350,73400,11891,8407,11891,10439 73400,73450,11902,8413,11902,10450 73450,73500,11913,8419,11913,10461 73500,73550,11924,8425,11924,10472 73550,73600,11935,8431,11935,10483 73600,73650,11946,8437,11946,10494 73650,73700,11957,8443,11957,10505 73700,73750,11968,8449,11968,10516 73750,73800,11979,8455,11979,10527 73800,73850,11990,8461,11990,10538 73850,73900,12001,8467,12001,10549 73900,73950,12012,8473,12012,10560 73950,74000,12023,8479,12023,10571 74000,74050,12034,8485,12034,10582 74050,74100,12045,8491,12045,10593 74100,74150,12056,8497,12056,10604 74150,74200,12067,8503,12067,10615 74200,74250,12078,8509,12078,10626 74250,74300,12089,8515,12089,10637 74300,74350,12100,8521,12100,10648 74350,74400,12111,8527,12111,10659 74400,74450,12122,8533,12122,10670 74450,74500,12133,8539,12133,10681 74500,74550,12144,8545,12144,10692 74550,74600,12155,8551,12155,10703 74600,74650,12166,8557,12166,10714 74650,74700,12177,8563,12177,10725 74700,74750,12188,8569,12188,10736 74750,74800,12199,8575,12199,10747 74800,74850,12210,8581,12210,10758 74850,74900,12221,8587,12221,10769 74900,74950,12232,8593,12232,10780 74950,75000,12243,8599,12243,10791 75000,75050,12254,8605,12254,10802 75050,75100,12265,8611,12265,10813 75100,75150,12276,8617,12276,10824 75150,75200,12287,8623,12287,10835 75200,75250,12298,8629,12298,10846 75250,75300,12309,8635,12309,10857 75300,75350,12320,8641,12320,10868 75350,75400,12331,8647,12331,10879 75400,75450,12342,8653,12342,10890 75450,75500,12353,8659,12353,10901 75500,75550,12364,8665,12364,10912 75550,75600,12375,8671,12375,10923 75600,75650,12386,8677,12386,10934 75650,75700,12397,8683,12397,10945 75700,75750,12408,8689,12408,10956 75750,75800,12419,8695,12419,10967 75800,75850,12430,8701,12430,10978 75850,75900,12441,8707,12441,10989 75900,75950,12452,8713,12452,11000 75950,76000,12463,8719,12463,11011 76000,76050,12474,8725,12474,11022 76050,76100,12485,8731,12485,11033 76100,76150,12496,8737,12496,11044 76150,76200,12507,8743,12507,11055 76200,76250,12518,8749,12518,11066 76250,76300,12529,8755,12529,11077 76300,76350,12540,8761,12540,11088 76350,76400,12551,8767,12551,11099 76400,76450,12562,8773,12562,11110 76450,76500,12573,8779,12573,11121 76500,76550,12584,8785,12584,11132 76550,76600,12595,8791,12595,11143 76600,76650,12606,8797,12606,11154 76650,76700,12617,8803,12617,11165 76700,76750,12628,8809,12628,11176 76750,76800,12639,8815,12639,11187 76800,76850,12650,8821,12650,11198 76850,76900,12661,8827,12661,11209 76900,76950,12672,8833,12672,11220 76950,77000,12683,8839,12683,11231 77000,77050,12694,8845,12694,11242 77050,77100,12705,8851,12705,11253 77100,77150,12716,8857,12716,11264 77150,77200,12727,8863,12727,11275 77200,77250,12738,8869,12738,11286 77250,77300,12749,8875,12749,11297 77300,77350,12760,8881,12760,11308 77350,77400,12771,8887,12771,11319 77400,77450,12782,8893,12782,11330 77450,77500,12793,8899,12793,11341 77500,77550,12804,8905,12804,11352 77550,77600,12815,8911,12815,11363 77600,77650,12826,8917,12826,11374 77650,77700,12837,8923,12837,11385 77700,77750,12848,8929,12848,11396 77750,77800,12859,8935,12859,11407 77800,77850,12870,8941,12870,11418 77850,77900,12881,8947,12881,11429 77900,77950,12892,8953,12892,11440 77950,78000,12903,8959,12903,11451 78000,78050,12914,8965,12914,11462 78050,78100,12925,8971,12925,11473 78100,78150,12936,8977,12936,11484 78150,78200,12947,8983,12947,11495 78200,78250,12958,8989,12958,11506 78250,78300,12969,8995,12969,11517 78300,78350,12980,9001,12980,11528 78350,78400,12991,9007,12991,11539 78400,78450,13002,9013,13002,11550 78450,78500,13013,9019,13013,11561 78500,78550,13024,9025,13024,11572 78550,78600,13035,9031,13035,11583 78600,78650,13046,9037,13046,11594 78650,78700,13057,9043,13057,11605 78700,78750,13068,9049,13068,11616 78750,78800,13079,9055,13079,11627 78800,78850,13090,9061,13090,11638 78850,78900,13101,9067,13101,11649 78900,78950,13112,9073,13112,11660 78950,79000,13123,9079,13123,11671 79000,79050,13134,9085,13134,11682 79050,79100,13145,9091,13145,11693 79100,79150,13156,9097,13156,11704 79150,79200,13167,9103,13167,11715 79200,79250,13178,9109,13178,11726 79250,79300,13189,9115,13189,11737 79300,79350,13200,9121,13200,11748 79350,79400,13211,9127,13211,11759 79400,79450,13222,9133,13222,11770 79450,79500,13233,9139,13233,11781 79500,79550,13244,9145,13244,11792 79550,79600,13255,9151,13255,11803 79600,79650,13266,9157,13266,11814 79650,79700,13277,9163,13277,11825 79700,79750,13288,9169,13288,11836 79750,79800,13299,9175,13299,11847 79800,79850,13310,9181,13310,11858 79850,79900,13321,9187,13321,11869 79900,79950,13332,9193,13332,11880 79950,80000,13343,9199,13343,11891 80000,80050,13354,9205,13354,11902 80050,80100,13365,9211,13365,11913 80100,80150,13376,9217,13376,11924 80150,80200,13387,9223,13387,11935 80200,80250,13398,9229,13398,11946 80250,80300,13409,9235,13409,11957 80300,80350,13420,9241,13420,11968 80350,80400,13431,9247,13431,11979 80400,80450,13442,9253,13442,11990 80450,80500,13453,9259,13453,12001 80500,80550,13464,9265,13464,12012 80550,80600,13475,9271,13475,12023 80600,80650,13486,9277,13486,12034 80650,80700,13497,9283,13497,12045 80700,80750,13508,9289,13508,12056 80750,80800,13519,9295,13519,12067 80800,80850,13530,9301,13530,12078 80850,80900,13541,9307,13541,12089 80900,80950,13552,9313,13552,12100 80950,81000,13563,9319,13563,12111 81000,81050,13574,9325,13574,12122 81050,81100,13585,9334,13585,12133 81100,81150,13596,9345,13596,12144 81150,81200,13607,9356,13607,12155 81200,81250,13618,9367,13618,12166 81250,81300,13629,9378,13629,12177 81300,81350,13640,9389,13640,12188 81350,81400,13651,9400,13651,12199 81400,81450,13662,9411,13662,12210 81450,81500,13673,9422,13673,12221 81500,81550,13684,9433,13684,12232 81550,81600,13695,9444,13695,12243 81600,81650,13706,9455,13706,12254 81650,81700,13717,9466,13717,12265 81700,81750,13728,9477,13728,12276 81750,81800,13739,9488,13739,12287 81800,81850,13750,9499,13750,12298 81850,81900,13761,9510,13761,12309 81900,81950,13772,9521,13772,12320 81950,82000,13783,9532,13783,12331 82000,82050,13794,9543,13794,12342 82050,82100,13805,9554,13805,12353 82100,82150,13816,9565,13816,12364 82150,82200,13827,9576,13827,12375 82200,82250,13838,9587,13838,12386 82250,82300,13849,9598,13849,12397 82300,82350,13860,9609,13860,12408 82350,82400,13871,9620,13871,12419 82400,82450,13882,9631,13882,12430 82450,82500,13893,9642,13893,12441 82500,82550,13904,9653,13904,12452 82550,82600,13915,9664,13915,12463 82600,82650,13926,9675,13926,12474 82650,82700,13937,9686,13937,12485 82700,82750,13948,9697,13948,12496 82750,82800,13959,9708,13959,12507 82800,82850,13970,9719,13970,12518 82850,82900,13981,9730,13981,12529 82900,82950,13992,9741,13992,12540 82950,83000,14003,9752,14003,12551 83000,83050,14014,9763,14014,12562 83050,83100,14025,9774,14025,12573 83100,83150,14036,9785,14036,12584 83150,83200,14047,9796,14047,12595 83200,83250,14058,9807,14058,12606 83250,83300,14069,9818,14069,12617 83300,83350,14080,9829,14080,12628 83350,83400,14091,9840,14091,12639 83400,83450,14102,9851,14102,12650 83450,83500,14113,9862,14113,12661 83500,83550,14124,9873,14124,12672 83550,83600,14135,9884,14135,12683 83600,83650,14146,9895,14146,12694 83650,83700,14157,9906,14157,12705 83700,83750,14168,9917,14168,12716 83750,83800,14179,9928,14179,12727 83800,83850,14190,9939,14190,12738 83850,83900,14201,9950,14201,12749 83900,83950,14212,9961,14212,12760 83950,84000,14223,9972,14223,12771 84000,84050,14234,9983,14234,12782 84050,84100,14245,9994,14245,12793 84100,84150,14256,10005,14256,12804 84150,84200,14267,10016,14267,12815 84200,84250,14278,10027,14278,12826 84250,84300,14289,10038,14289,12837 84300,84350,14300,10049,14300,12848 84350,84400,14311,10060,14311,12859 84400,84450,14322,10071,14322,12870 84450,84500,14333,10082,14333,12881 84500,84550,14344,10093,14344,12892 84550,84600,14355,10104,14355,12903 84600,84650,14366,10115,14366,12914 84650,84700,14377,10126,14377,12925 84700,84750,14388,10137,14388,12936 84750,84800,14399,10148,14399,12947 84800,84850,14410,10159,14410,12958 84850,84900,14421,10170,14421,12969 84900,84950,14432,10181,14432,12980 84950,85000,14443,10192,14443,12991 85000,85050,14454,10203,14454,13002 85050,85100,14465,10214,14465,13013 85100,85150,14476,10225,14476,13024 85150,85200,14487,10236,14487,13035 85200,85250,14498,10247,14498,13046 85250,85300,14509,10258,14509,13057 85300,85350,14520,10269,14520,13068 85350,85400,14531,10280,14531,13079 85400,85450,14542,10291,14542,13090 85450,85500,14553,10302,14553,13101 85500,85550,14564,10313,14564,13112 85550,85600,14575,10324,14575,13123 85600,85650,14586,10335,14586,13134 85650,85700,14597,10346,14597,13145 85700,85750,14608,10357,14608,13156 85750,85800,14619,10368,14619,13167 85800,85850,14630,10379,14630,13178 85850,85900,14641,10390,14641,13189 85900,85950,14652,10401,14652,13200 85950,86000,14663,10412,14663,13211 86000,86050,14674,10423,14674,13222 86050,86100,14685,10434,14685,13233 86100,86150,14696,10445,14696,13244 86150,86200,14707,10456,14707,13255 86200,86250,14718,10467,14718,13266 86250,86300,14729,10478,14729,13277 86300,86350,14740,10489,14740,13288 86350,86400,14751,10500,14751,13299 86400,86450,14763,10511,14763,13311 86450,86500,14775,10522,14775,13323 86500,86550,14787,10533,14787,13335 86550,86600,14799,10544,14799,13347 86600,86650,14811,10555,14811,13359 86650,86700,14823,10566,14823,13371 86700,86750,14835,10577,14835,13383 86750,86800,14847,10588,14847,13395 86800,86850,14859,10599,14859,13407 86850,86900,14871,10610,14871,13419 86900,86950,14883,10621,14883,13431 86950,87000,14895,10632,14895,13443 87000,87050,14907,10643,14907,13455 87050,87100,14919,10654,14919,13467 87100,87150,14931,10665,14931,13479 87150,87200,14943,10676,14943,13491 87200,87250,14955,10687,14955,13503 87250,87300,14967,10698,14967,13515 87300,87350,14979,10709,14979,13527 87350,87400,14991,10720,14991,13539 87400,87450,15003,10731,15003,13551 87450,87500,15015,10742,15015,13563 87500,87550,15027,10753,15027,13575 87550,87600,15039,10764,15039,13587 87600,87650,15051,10775,15051,13599 87650,87700,15063,10786,15063,13611 87700,87750,15075,10797,15075,13623 87750,87800,15087,10808,15087,13635 87800,87850,15099,10819,15099,13647 87850,87900,15111,10830,15111,13659 87900,87950,15123,10841,15123,13671 87950,88000,15135,10852,15135,13683 88000,88050,15147,10863,15147,13695 88050,88100,15159,10874,15159,13707 88100,88150,15171,10885,15171,13719 88150,88200,15183,10896,15183,13731 88200,88250,15195,10907,15195,13743 88250,88300,15207,10918,15207,13755 88300,88350,15219,10929,15219,13767 88350,88400,15231,10940,15231,13779 88400,88450,15243,10951,15243,13791 88450,88500,15255,10962,15255,13803 88500,88550,15267,10973,15267,13815 88550,88600,15279,10984,15279,13827 88600,88650,15291,10995,15291,13839 88650,88700,15303,11006,15303,13851 88700,88750,15315,11017,15315,13863 88750,88800,15327,11028,15327,13875 88800,88850,15339,11039,15339,13887 88850,88900,15351,11050,15351,13899 88900,88950,15363,11061,15363,13911 88950,89000,15375,11072,15375,13923 89000,89050,15387,11083,15387,13935 89050,89100,15399,11094,15399,13947 89100,89150,15411,11105,15411,13959 89150,89200,15423,11116,15423,13971 89200,89250,15435,11127,15435,13983 89250,89300,15447,11138,15447,13995 89300,89350,15459,11149,15459,14007 89350,89400,15471,11160,15471,14019 89400,89450,15483,11171,15483,14031 89450,89500,15495,11182,15495,14043 89500,89550,15507,11193,15507,14055 89550,89600,15519,11204,15519,14067 89600,89650,15531,11215,15531,14079 89650,89700,15543,11226,15543,14091 89700,89750,15555,11237,15555,14103 89750,89800,15567,11248,15567,14115 89800,89850,15579,11259,15579,14127 89850,89900,15591,11270,15591,14139 89900,89950,15603,11281,15603,14151 89950,90000,15615,11292,15615,14163 90000,90050,15627,11303,15627,14175 90050,90100,15639,11314,15639,14187 90100,90150,15651,11325,15651,14199 90150,90200,15663,11336,15663,14211 90200,90250,15675,11347,15675,14223 90250,90300,15687,11358,15687,14235 90300,90350,15699,11369,15699,14247 90350,90400,15711,11380,15711,14259 90400,90450,15723,11391,15723,14271 90450,90500,15735,11402,15735,14283 90500,90550,15747,11413,15747,14295 90550,90600,15759,11424,15759,14307 90600,90650,15771,11435,15771,14319 90650,90700,15783,11446,15783,14331 90700,90750,15795,11457,15795,14343 90750,90800,15807,11468,15807,14355 90800,90850,15819,11479,15819,14367 90850,90900,15831,11490,15831,14379 90900,90950,15843,11501,15843,14391 90950,91000,15855,11512,15855,14403 91000,91050,15867,11523,15867,14415 91050,91100,15879,11534,15879,14427 91100,91150,15891,11545,15891,14439 91150,91200,15903,11556,15903,14451 91200,91250,15915,11567,15915,14463 91250,91300,15927,11578,15927,14475 91300,91350,15939,11589,15939,14487 91350,91400,15951,11600,15951,14499 91400,91450,15963,11611,15963,14511 91450,91500,15975,11622,15975,14523 91500,91550,15987,11633,15987,14535 91550,91600,15999,11644,15999,14547 91600,91650,16011,11655,16011,14559 91650,91700,16023,11666,16023,14571 91700,91750,16035,11677,16035,14583 91750,91800,16047,11688,16047,14595 91800,91850,16059,11699,16059,14607 91850,91900,16071,11710,16071,14619 91900,91950,16083,11721,16083,14631 91950,92000,16095,11732,16095,14643 92000,92050,16107,11743,16107,14655 92050,92100,16119,11754,16119,14667 92100,92150,16131,11765,16131,14679 92150,92200,16143,11776,16143,14691 92200,92250,16155,11787,16155,14703 92250,92300,16167,11798,16167,14715 92300,92350,16179,11809,16179,14727 92350,92400,16191,11820,16191,14739 92400,92450,16203,11831,16203,14751 92450,92500,16215,11842,16215,14763 92500,92550,16227,11853,16227,14775 92550,92600,16239,11864,16239,14787 92600,92650,16251,11875,16251,14799 92650,92700,16263,11886,16263,14811 92700,92750,16275,11897,16275,14823 92750,92800,16287,11908,16287,14835 92800,92850,16299,11919,16299,14847 92850,92900,16311,11930,16311,14859 92900,92950,16323,11941,16323,14871 92950,93000,16335,11952,16335,14883 93000,93050,16347,11963,16347,14895 93050,93100,16359,11974,16359,14907 93100,93150,16371,11985,16371,14919 93150,93200,16383,11996,16383,14931 93200,93250,16395,12007,16395,14943 93250,93300,16407,12018,16407,14955 93300,93350,16419,12029,16419,14967 93350,93400,16431,12040,16431,14979 93400,93450,16443,12051,16443,14991 93450,93500,16455,12062,16455,15003 93500,93550,16467,12073,16467,15015 93550,93600,16479,12084,16479,15027 93600,93650,16491,12095,16491,15039 93650,93700,16503,12106,16503,15051 93700,93750,16515,12117,16515,15063 93750,93800,16527,12128,16527,15075 93800,93850,16539,12139,16539,15087 93850,93900,16551,12150,16551,15099 93900,93950,16563,12161,16563,15111 93950,94000,16575,12172,16575,15123 94000,94050,16587,12183,16587,15135 94050,94100,16599,12194,16599,15147 94100,94150,16611,12205,16611,15159 94150,94200,16623,12216,16623,15171 94200,94250,16635,12227,16635,15183 94250,94300,16647,12238,16647,15195 94300,94350,16659,12249,16659,15207 94350,94400,16671,12260,16671,15219 94400,94450,16683,12271,16683,15231 94450,94500,16695,12282,16695,15243 94500,94550,16707,12293,16707,15255 94550,94600,16719,12304,16719,15267 94600,94650,16731,12315,16731,15279 94650,94700,16743,12326,16743,15291 94700,94750,16755,12337,16755,15303 94750,94800,16767,12348,16767,15315 94800,94850,16779,12359,16779,15327 94850,94900,16791,12370,16791,15339 94900,94950,16803,12381,16803,15351 94950,95000,16815,12392,16815,15363 95000,95050,16827,12403,16827,15375 95050,95100,16839,12414,16839,15387 95100,95150,16851,12425,16851,15399 95150,95200,16863,12436,16863,15411 95200,95250,16875,12447,16875,15423 95250,95300,16887,12458,16887,15435 95300,95350,16899,12469,16899,15447 95350,95400,16911,12480,16911,15459 95400,95450,16923,12491,16923,15471 95450,95500,16935,12502,16935,15483 95500,95550,16947,12513,16947,15495 95550,95600,16959,12524,16959,15507 95600,95650,16971,12535,16971,15519 95650,95700,16983,12546,16983,15531 95700,95750,16995,12557,16995,15543 95750,95800,17007,12568,17007,15555 95800,95850,17019,12579,17019,15567 95850,95900,17031,12590,17031,15579 95900,95950,17043,12601,17043,15591 95950,96000,17055,12612,17055,15603 96000,96050,17067,12623,17067,15615 96050,96100,17079,12634,17079,15627 96100,96150,17091,12645,17091,15639 96150,96200,17103,12656,17103,15651 96200,96250,17115,12667,17115,15663 96250,96300,17127,12678,17127,15675 96300,96350,17139,12689,17139,15687 96350,96400,17151,12700,17151,15699 96400,96450,17163,12711,17163,15711 96450,96500,17175,12722,17175,15723 96500,96550,17187,12733,17187,15735 96550,96600,17199,12744,17199,15747 96600,96650,17211,12755,17211,15759 96650,96700,17223,12766,17223,15771 96700,96750,17235,12777,17235,15783 96750,96800,17247,12788,17247,15795 96800,96850,17259,12799,17259,15807 96850,96900,17271,12810,17271,15819 96900,96950,17283,12821,17283,15831 96950,97000,17295,12832,17295,15843 97000,97050,17307,12843,17307,15855 97050,97100,17319,12854,17319,15867 97100,97150,17331,12865,17331,15879 97150,97200,17343,12876,17343,15891 97200,97250,17355,12887,17355,15903 97250,97300,17367,12898,17367,15915 97300,97350,17379,12909,17379,15927 97350,97400,17391,12920,17391,15939 97400,97450,17403,12931,17403,15951 97450,97500,17415,12942,17415,15963 97500,97550,17427,12953,17427,15975 97550,97600,17439,12964,17439,15987 97600,97650,17451,12975,17451,15999 97650,97700,17463,12986,17463,16011 97700,97750,17475,12997,17475,16023 97750,97800,17487,13008,17487,16035 97800,97850,17499,13019,17499,16047 97850,97900,17511,13030,17511,16059 97900,97950,17523,13041,17523,16071 97950,98000,17535,13052,17535,16083 98000,98050,17547,13063,17547,16095 98050,98100,17559,13074,17559,16107 98100,98150,17571,13085,17571,16119 98150,98200,17583,13096,17583,16131 98200,98250,17595,13107,17595,16143 98250,98300,17607,13118,17607,16155 98300,98350,17619,13129,17619,16167 98350,98400,17631,13140,17631,16179 98400,98450,17643,13151,17643,16191 98450,98500,17655,13162,17655,16203 98500,98550,17667,13173,17667,16215 98550,98600,17679,13184,17679,16227 98600,98650,17691,13195,17691,16239 98650,98700,17703,13206,17703,16251 98700,98750,17715,13217,17715,16263 98750,98800,17727,13228,17727,16275 98800,98850,17739,13239,17739,16287 98850,98900,17751,13250,17751,16299 98900,98950,17763,13261,17763,16311 98950,99000,17775,13272,17775,16323 99000,99050,17787,13283,17787,16335 99050,99100,17799,13294,17799,16347 99100,99150,17811,13305,17811,16359 99150,99200,17823,13316,17823,16371 99200,99250,17835,13327,17835,16383 99250,99300,17847,13338,17847,16395 99300,99350,17859,13349,17859,16407 99350,99400,17871,13360,17871,16419 99400,99450,17883,13371,17883,16431 99450,99500,17895,13382,17895,16443 99500,99550,17907,13393,17907,16455 99550,99600,17919,13404,17919,16467 99600,99650,17931,13415,17931,16479 99650,99700,17943,13426,17943,16491 99700,99750,17955,13437,17955,16503 99750,99800,17967,13448,17967,16515 99800,99850,17979,13459,17979,16527 99850,99900,17991,13470,17991,16539 99900,99950,18003,13481,18003,16551 99950,100000,18015,13492,18015,16563 ================================================ FILE: src/forms/YearForms.ts ================================================ import { Information, Asset } from 'ustaxes/core/data' import { Either, isLeft, isRight, left, run, runAsync } from 'ustaxes/core/util' import { TaxYear } from 'ustaxes/core/data' import { create1040 as create1040For2020 } from 'ustaxes/forms/Y2020/irsForms/Main' import { create1040 as create1040For2021 } from 'ustaxes/forms/Y2021/irsForms/Main' import F1040For2020 from 'ustaxes/forms/Y2020/irsForms/F1040' import F1040For2021 from 'ustaxes/forms/Y2021/irsForms/F1040' import Form from 'ustaxes/core/irsForms/Form' import StateForm from 'ustaxes/core/stateForms/Form' import { createStateReturn as createStateReturn2020 } from 'ustaxes/forms/Y2020/stateForms' import { createStateReturn as createStateReturn2021 } from 'ustaxes/forms/Y2021/stateForms' import { PDFDocument } from 'pdf-lib' import { fillPDF } from 'ustaxes/core/pdfFiller/fillPdf' import { combinePdfs, downloadPDF, PDFDownloader } from 'ustaxes/core/pdfFiller/pdfHandler' import { F1040Error } from './errors' import { StateFormError } from './StateForms' import { validate } from './F1040Base' interface CreateFormConfig { createF1040: ( info: Information, assets: Asset[] ) => Either getPDF: (f: Form) => Promise getStatePDF: (f: StateForm) => Promise createStateReturn: ( f1040: Form ) => Either, StateForm[]> } export class YearCreateForm { year: TaxYear unvalidatedInfo: Information assets: Asset[] config: CreateFormConfig constructor( year: TaxYear, info: Information, assets: Asset[], config: CreateFormConfig ) { this.year = year this.unvalidatedInfo = info this.assets = assets this.config = config } errors = (): F1040Error[] => { const errors = validate(this.unvalidatedInfo) return isLeft(errors) ? errors.left : [] } f1040 = (): Either => run(validate(this.unvalidatedInfo)) .chain((info) => this.config.createF1040(info, this.assets)) .value() f1040Pdfs = async (): Promise> => { const r1 = await run(this.f1040()).mapAsync((forms) => Promise.all( forms.map(async (form) => fillPDF(await this.config.getPDF(form), form.renderedFields()) ) ) ) return r1.value() } f1040Pdf = async (): Promise> => { const r1 = await runAsync(this.f1040Pdfs()) const r2 = await r1.mapAsync(combinePdfs) return r2.value() } f1040Bytes = async (): Promise> => { const r1 = await runAsync(this.f1040Pdf()) const r2 = await r1.mapAsync((pdf) => pdf.save()) return r2.value() } makeStateReturn = (): Either<(F1040Error | StateFormError)[], StateForm[]> => run(this.f1040()) .mapLeft>((e) => e) .chain((forms) => { if (forms.length < 1) { throw new Error('No forms to create state return') } const f1040 = forms.find((f) => f.tag === 'f1040') if (f1040 === undefined) { throw new Error('Fed forms sent to state creator without 1040') } return this.config.createStateReturn(f1040) }) .value() stateReturnPDFs = async (): Promise> => { const r1 = run(this.makeStateReturn()) const r2 = await r1.mapAsync((forms) => Promise.all( forms.map(async (form) => fillPDF(await this.config.getStatePDF(form), form.renderedFields()) ) ) ) return r2.value() } stateReturnPDF = async (): Promise> => { const r1 = await runAsync(this.stateReturnPDFs()) const r2 = await r1.mapAsync(combinePdfs) return r2.value() } stateReturnBytes = async (): Promise> => { const r1 = await runAsync(this.stateReturnPDF()) const r2 = await r1.mapAsync((pdf) => pdf.save()) return r2.value() } canCreateFederal = (): boolean => isRight(this.f1040()) canCreateState = (): boolean => isRight(this.makeStateReturn()) } export class CreateForms { readonly year: TaxYear downloader: PDFDownloader constructor(year: TaxYear) { this.year = year this.downloader = (url: string) => downloadPDF(`/forms/${year}/${url}`) } setDownloader = (downloader: PDFDownloader): CreateForms => { this.downloader = downloader return this } build = (info: Information, assets: Asset[]): YearCreateForm => { const takeSecond = ( f: (a: A, aa: AA) => Either ): ((a: A, aa: AA) => Either) => (a: A, aa: AA): Either => run(f(a, aa)) .map(([, c]) => c) .value() const baseConfig = { getPDF: (form: Form): Promise => this.downloader(`irs/${form.tag}.pdf`), getStatePDF: (form: StateForm): Promise => this.downloader(`states/${form.state}/${form.formName}.pdf`) } const configs: { [K in TaxYear]: CreateFormConfig } = { Y2019: { ...baseConfig, createF1040: () => left([F1040Error.unsupportedTaxYear]), createStateReturn: () => left([StateFormError.unsupportedTaxYear]) }, Y2020: { ...baseConfig, createF1040: takeSecond(create1040For2020), createStateReturn: (f: Form) => createStateReturn2020(f as F1040For2020) }, Y2021: { ...baseConfig, createF1040: takeSecond(create1040For2021), createStateReturn: (f: Form) => createStateReturn2021(f as F1040For2021) } } return new YearCreateForm(this.year, info, assets, configs[this.year]) } } export const yearFormBuilder = (year: TaxYear): CreateForms => new CreateForms(year) export default ( year: TaxYear, info: Information, assets: Asset[] ): YearCreateForm => yearFormBuilder(year).build(info, assets) ================================================ FILE: src/forms/errors.ts ================================================ export enum F1040Error { unsupportedTaxYear = 'Tax year not supported', incompletePrimaryTaxpayer = 'Taxpayer information not completed', filingStatusUndefined = 'Select a filing status', filingStatusRequirementsNotMet = 'Filing status does not match dependents or spouse requirements', unknown = 'Unknown error' } ================================================ FILE: src/forms/tests/CommonTests.ts ================================================ import { Information, Asset, FilingStatus } from 'ustaxes/core/data' import Form from 'ustaxes/core/irsForms/Form' import { run } from 'ustaxes/core/util' import { F1040Error } from '../errors' import F1040Base, { validate } from '../F1040Base' import TestKit from './TestKit' export abstract class FormTestInfo { abstract getAssets: (a: A) => Asset[] abstract getInfo: (a: A) => Information getErrors: (info: Information) => F1040Error[] = (info) => run(validate(info)).fold( (errors) => errors, () => [] ) } beforeAll(() => { jest.spyOn(console, 'warn').mockImplementation((x: string) => { if (!x.includes('Removing XFA form data as pdf-lib')) { console.warn(x) } }) }) export default class CommonTests { testKit: TestKit formTestInfo: FormTestInfo constructor(testKit: TestKit, formTestInfo: FormTestInfo) { this.testKit = testKit this.formTestInfo = formTestInfo } findF1040 = (forms: Form[]): F1040 | undefined => forms.find((v) => v.tag === 'f1040') as F1040 | undefined findF1040OrFail = (forms: Form[]): F1040 => { const res = this.findF1040(forms) if (res === undefined) { throw new Error( `Looked for F1040 in ${forms.map((f) => f.tag).join(',')}, not found` ) } return res } withValid1040 = async ( f: (f1040: F1040, fs: FilingStatus) => void, filter: (info: Information) => boolean = () => true ): Promise => this.testKit.with1040Assert( async (forms): Promise => { const f1040 = this.findF1040OrFail(forms) const fs = f1040.info.taxPayer.filingStatus await Promise.resolve(f(f1040, fs)) }, {}, filter ) run = (): void => { it('should be created in', async () => { await this.testKit.with1040Assert((forms) => { const f1040 = this.findF1040(forms) expect(f1040).not.toBeUndefined() return Promise.resolve() }) }) it('should arrange attachments according to sequence order', async () => { await this.testKit.with1040Assert((forms) => { expect(forms.sort((a, b) => a.sequenceIndex - b.sequenceIndex)).toEqual( forms ) return Promise.resolve() }) }) it('should create a PDF without failing', async () => { await this.testKit.with1040Assert(async (forms) => { const f1040 = this.findF1040(forms) expect(f1040).not.toBeUndefined() if (f1040 !== undefined) { const builder = this.testKit.builder.build( this.formTestInfo.getInfo(f1040), this.formTestInfo.getAssets(f1040) ) const pdfs = await builder.f1040Pdfs() run(pdfs).fold( (e) => fail(e), (pdfs) => { expect(pdfs).not.toHaveLength(0) } ) } }) }) } } ================================================ FILE: src/forms/tests/TestKit.ts ================================================ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import fc, { Parameters } from 'fast-check' import { Information, Asset, TaxYear, TaxYears } from 'ustaxes/core/data' import Form from 'ustaxes/core/irsForms/Form' import { run } from 'ustaxes/core/util' import * as arbitraries from 'ustaxes/core/tests/arbitraries' import * as ustarbitraries from 'ustaxes/tests/arbitraries' import { localPDFs } from 'ustaxes/core/tests/LocalForms' import fs from 'fs/promises' import path from 'path' import { PDFDocument } from 'pdf-lib' import { insertFormDataToPdfs } from 'ustaxes/core/irsForms' import { CreateForms, yearFormBuilder } from '../YearForms' import { PDFDownloader } from 'ustaxes/core/pdfFiller/pdfHandler' import { ValidatedInformation } from '../F1040Base' const logsDir = path.resolve(__dirname, '../../../logs/errors') export default class TestKit { year: TaxYear downloader: PDFDownloader arbitaries: arbitraries.Arbitraries builder: CreateForms constructor(year: TaxYear) { this.year = year this.downloader = localPDFs(year) this.arbitaries = new arbitraries.Arbitraries(TaxYears[year]) this.builder = yearFormBuilder(year).setDownloader(this.downloader) } /** * This is used when there is some error generated in a test * The calculated forms will be used to generate a return and * saved to the /logs directory in the project root. */ log1040 = async ( info: Information, assets: Asset[], logstr?: string ): Promise => { try { const builder = this.builder.build(info, assets) const pdfs = await builder.f1040Pdfs() const today = new Date() const dateStr = `${today.getFullYear()}-${ today.getMonth() + 1 }-${today.getDate()}` const saveDirName = `Errors ${dateStr} ${today.toLocaleTimeString()}` const saveDir = path.resolve(logsDir, saveDirName) await fs.mkdir(saveDir, { recursive: true }) await run(pdfs).fold( (e: string[]) => fs.writeFile(path.resolve(saveDir, 'failure.txt'), e.join('\n')), (pdfs) => Promise.all( pdfs.map(async (pdf, i) => { await fs.writeFile( path.resolve(saveDir, pdf.getTitle() ?? `Form ${i}`), await pdf.save() ) }) ) ) await fs.writeFile( path.resolve(saveDir, 'info.json'), JSON.stringify(info, null, 2) ) if (logstr !== undefined) { await fs.writeFile( path.resolve(saveDir, 'error.txt'), logstr.toString() ) } } catch (e) { console.info('Got another error trying to log testing error') console.error(e) console.error('Giving up!') throw e } } /** * Run a property test on generated PDFs * **Must** be awaited */ with1040Pdfs = async ( f: (pdfs: PDFDocument[], info: Information, assets: Asset[]) => void, params: Parameters<[Information, Asset[]]> = { // This might take a long time as many pdfs have to be rendered // So by default just run for 1 minute max and hope interruptAfterTimeLimit: 60 * 1000 } ): Promise => await this.with1040Assert(async (forms, info, assets) => { const pdfs = await insertFormDataToPdfs(forms, this.downloader) f(pdfs, info, assets) }, params) with1040Property = ( f: ( forms: Form[], info: ValidatedInformation, assets: Asset[] ) => Promise, filter: (info: ValidatedInformation) => boolean = () => true ): fc.IAsyncPropertyWithHooks<[ValidatedInformation, Asset[]]> => fc.asyncProperty( this.arbitaries.information().filter(filter), fc.array(ustarbitraries.positionDate), async (information, assets): Promise => { const builder = this.builder.build(information, assets) await run(builder.f1040()).fold( async (e: string[]): Promise => { await Promise.resolve(expect(e).not.toEqual([])) }, async (forms: Form[]): Promise => { return await f(forms, information, assets) } ) } ) /** * Run a property test on 1040 data. * Must** be awaited. */ with1040Assert = async ( f: ( forms: Form[], info: ValidatedInformation, assets: Asset[] ) => Promise, params: Parameters<[Information, Asset[]]> = {}, filter: (info: ValidatedInformation) => boolean = () => true ): Promise => { let lastCallWithInfo: [ValidatedInformation, Asset[]] | undefined await fc .assert( this.with1040Property(async (forms, info, assets) => { await f(forms, info, assets).catch((e) => { // Save the last failing test info for logging lastCallWithInfo = [info, assets] // Hand it back to fc's assert. // We're only saving the last form data after // fast-check has done its shrinking. So we need // fast-check to catch this exception and decide // whether to run it again. throw e }) }, filter), params ) .catch(async (e: Error) => { console.error('Logging 1040 errors.') if (lastCallWithInfo !== undefined) { await this.log1040(...lastCallWithInfo, e.message) } else { console.error('trying to log error but no info is available') } throw e }) } } interface Access { [k: string]: A | (() => B) } export const showForm = (f: Access): string => { return Object.keys(f) .filter((k) => k.startsWith('l')) .map((k) => { if (typeof f[k] === 'function') { return `${k}=${(f[k] as () => number)()}` } }) .join('\n') } ================================================ FILE: src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: src/index.js ================================================ import { StrictMode } from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' import App from './App' import * as serviceWorker from './serviceWorker' import { BrowserRouter as Router } from 'react-router-dom' import { store, persistor } from './redux/store' import { PersistGate } from 'redux-persist/integration/react' import './index.css' ReactDOM.render( Loading from Local Storage} persistor={persistor} > , document.getElementById('root') ) // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister() ================================================ FILE: src/log.ts ================================================ import log from 'loglevel' const { TRACE, ERROR } = log.levels log.setDefaultLevel(process.env.NODE_ENV === 'development' ? TRACE : ERROR) export default log ================================================ FILE: src/pdfHandler.ts ================================================ import { save } from '@tauri-apps/api/dialog' import { writeBinaryFile } from '@tauri-apps/api/fs' export async function savePDF( contents: Uint8Array, defaultFilename: string ): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access if ((window as any).__TAURI__ === undefined) { // To set the download file name, we create a temporary link element, // use download property of an anchor tag, supported for most people const blob = new Blob([contents], { type: 'application/pdf' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = defaultFilename document.body.appendChild(a) a.click() URL.revokeObjectURL(url) a.remove() return await Promise.resolve() } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call const defaultPath = await (window as any).__TAURI__.path.documentDir() // path can be null if user cancels save. const path: string | null = (await save({ filters: [{ name: 'PDF Documents (.pdf)', extensions: ['pdf'] }], // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment defaultPath })) as string | null if (path !== null) { return await writeBinaryFile({ contents, path }, {}) } // user canceled save. return await Promise.resolve() } } ================================================ FILE: src/react-app-env.d.ts ================================================ import 'react-scripts' ================================================ FILE: src/redux/TaxesState.ts ================================================ import { Information } from 'ustaxes/core/data' import { YearsTaxesState } from './data' export default class TaxesStateMethods { ts: YearsTaxesState constructor(ts: YearsTaxesState) { this.ts = ts } info = (): Information => this.ts[this.ts.activeYear] } ================================================ FILE: src/redux/actions.ts ================================================ import { Person, IncomeW2, Refund, DependentDateString, FilingStatus, PrimaryPersonDateString, ContactInfo, Supported1099, F1098e, SpouseDateString, Property, StateResidency, Information, EstimatedTaxPayments, Responses, Ira, Asset, ItemizedDeductions, F3921, ScheduleK1Form1065, TaxYear, HealthSavingsAccountDateString, InformationDateString, Credit, EditCreditAction } from 'ustaxes/core/data' import { EditDependentAction, EditPropertyAction, Edit1099Action, EditW2Action, EditEstimatedTaxesAction, Edit1098eAction, EditHSAAction, EditIraAction, EditAssetAction, EditF3921Action, EditScheduleK1Form1065Action } from 'ustaxes/core/data' import * as validators from 'ustaxes/core/data/validate' import { index as indexValidator } from 'ustaxes/core/data/validate' import { ValidateFunction } from 'ajv' import { infoToStringInfo } from './data' export enum ActionName { SAVE_REFUND_INFO = 'SAVE_REFUND_INFO', SAVE_PRIMARY_PERSON_INFO = 'SAVE_TAXPAYER_INFO', SAVE_CONTACT_INFO = 'SAVE_CONTACT_INFO', SAVE_STATE_RESIDENCY = 'SAVE_STATE_RESIDENCY', SAVE_FILING_STATUS_INFO = 'SAFE_FILING_STATUS_INFO', ADD_DEPENDENT = 'TAXPAYER/ADD_DEPENDENT', EDIT_DEPENDENT = 'TAXPAYER/EDIT_DEPENDENT', REMOVE_DEPENDENT = 'TAXPAYER/REMOVE_DEPENDENT', ADD_SPOUSE = 'TAXPAYER/ADD_SPOUSE', REMOVE_SPOUSE = 'TAXPAYER/REMOVE_SPOUSE', ADD_W2 = 'ADD_W2', EDIT_W2 = 'EDIT_W2', REMOVE_W2 = 'REMOVE_W2', ADD_ESTIMATED_TAX = 'ADD_ESTIMATED_TAX', EDIT_ESTIMATED_TAX = 'EDIT_ESTIMATED_TAX', REMOVE_ESTIMATED_TAX = 'REMOVE_ESTIMATED_TAX', ADD_1099 = 'ADD_1099', EDIT_1099 = 'EDIT_1099', REMOVE_1099 = 'REMOVE_1099', ADD_PROPERTY = 'ADD_PROPERTY', EDIT_PROPERTY = 'EDIT_PROPERTY', REMOVE_PROPERTY = 'REMOVE_PROPERTY', ANSWER_QUESTION = 'ANSWER_QUESTION', ADD_1098e = 'ADD_1098e', EDIT_1098e = 'EDIT_1098e', REMOVE_1098e = 'REMOVE_1098e', SET_ITEMIZED_DEDUCTIONS = 'SET_ITEMIZED_DEDUCTIONS', ADD_HSA = 'ADD_HSA', EDIT_HSA = 'EDIT_HSA', REMOVE_HSA = 'REMOVE_HSA', SET_INFO = 'SET_INFO', SET_ACTIVE_YEAR = 'SET_ACTIVE_YEAR', PROPAGATE_YEAR_DATA = 'PROPAGATE_YEAR_DATA', ADD_IRA = 'ADD_IRA', EDIT_IRA = 'EDIT_IRA', REMOVE_IRA = 'REMOVE_IRA', ADD_ASSET = 'ASSETS/ADD', ADD_ASSETS = 'ASSETS/ADD_MANY', EDIT_ASSET = 'ASSETS/EDIT', REMOVE_ASSET = 'ASSETS/REMOVE', REMOVE_ASSETS = 'ASSETS/REMOVE_MANY', ADD_F3921 = 'F3921/ADD', EDIT_F3921 = 'F3921/EDIT', REMOVE_F3921 = 'F3921/REMOVE', ADD_SCHEDULE_K1_F1065 = 'SCHEDULE_K1_F1065/ADD', EDIT_SCHEDULE_K1_F1065 = 'SCHEDULE_K1_F1065/EDIT', REMOVE_SCHEDULE_K1_F1065 = 'SCHEDULE_K1_F1065/REMOVE', ADD_CREDIT = 'CREDIT/ADD', EDIT_CREDIT = 'CREDIT/EDIT', REMOVE_CREDIT = 'CREDIT/REMOVE' } interface Save { type: T year: TaxYear formData: R } type SaveRefundInfo = Save type SavePrimaryPersonInfo = Save< typeof ActionName.SAVE_PRIMARY_PERSON_INFO, PrimaryPersonDateString > type SaveFilingStatusInfo = Save< typeof ActionName.SAVE_FILING_STATUS_INFO, FilingStatus > type SaveContactInfo = Save type SaveStateResidencyInfo = Save< typeof ActionName.SAVE_STATE_RESIDENCY, StateResidency > type AddDependent = Save type EditDependent = Save type RemoveDependent = Save type AddSpouse = Save type RemoveSpouse = Save> type AddW2 = Save type EditW2 = Save type RemoveW2 = Save type AddEstimatedTaxes = Save< typeof ActionName.ADD_ESTIMATED_TAX, EstimatedTaxPayments > type EditEstimatedTaxes = Save< typeof ActionName.EDIT_ESTIMATED_TAX, EditEstimatedTaxesAction > type RemoveEstimatedTaxes = Save type AddHSA = Save type EditHSA = Save type RemoveHSA = Save type Add1099 = Save type Edit1099 = Save type Remove1099 = Save type AddProperty = Save type EditProperty = Save type RemoveProperty = Save type AnswerQuestion = Save type Add1098e = Save type Edit1098e = Save type Remove1098e = Save type SetItemizedDeductions = Save< typeof ActionName.SET_ITEMIZED_DEDUCTIONS, ItemizedDeductions > type SetInfo = Save type SetActiveYear = Save type AddIRA = Save type EditIRA = Save type RemoveIRA = Save type AddAsset = Save> type AddAssets = Save[]> type EditAsset = Save type RemoveAsset = Save type RemoveAssets = Save type AddF3921 = Save type EditF3921 = Save type RemoveF3921 = Save type AddScheduleK1Form1065 = Save< typeof ActionName.ADD_SCHEDULE_K1_F1065, ScheduleK1Form1065 > type EditScheduleK1Form1065 = Save< typeof ActionName.EDIT_SCHEDULE_K1_F1065, EditScheduleK1Form1065Action > type RemoveScheduleK1Form1065 = Save< typeof ActionName.REMOVE_SCHEDULE_K1_F1065, number > type AddCredit = Save type EditCredit = Save type RemoveCredit = Save export type Actions = | SaveRefundInfo | SavePrimaryPersonInfo | SaveFilingStatusInfo | SaveContactInfo | SaveStateResidencyInfo | AddDependent | EditDependent | RemoveDependent | AddSpouse | RemoveSpouse | AddW2 | EditW2 | RemoveW2 | AddEstimatedTaxes | EditEstimatedTaxes | RemoveEstimatedTaxes | Add1099 | Edit1099 | Remove1099 | AddProperty | EditProperty | RemoveProperty | AnswerQuestion | Add1098e | Edit1098e | Remove1098e | SetItemizedDeductions | AddHSA | EditHSA | RemoveHSA | SetInfo | SetActiveYear | AddIRA | EditIRA | RemoveIRA | AddAsset | AddAssets | EditAsset | RemoveAsset | RemoveAssets | AddF3921 | EditF3921 | RemoveF3921 | AddScheduleK1Form1065 | EditScheduleK1Form1065 | RemoveScheduleK1Form1065 | AddCredit | EditCredit | RemoveCredit export type SignalAction = (year: TaxYear) => Actions export type ActionCreator = (formData: A) => SignalAction function signalAction( t: T ): (year: TaxYear) => Save> { return (year: TaxYear) => ({ type: t, year, formData: {} }) } /** * Create an action constructor given an action name and a validator * for the action's payload. The validator checks the payload against * the schema at runtime so we can see errors if data of the wrong types * about to be inserted into the model */ const makeActionCreator = (t: T, validate?: ValidateFunction) => (formData: A) => (year: TaxYear): Save => ({ type: t, year, formData: validate !== undefined ? validators.checkType(formData, validate) : formData }) /** * This variant includes a preprocessor function that can be used to * apply formatting changes to provided data, for example. */ const makePreprocessActionCreator = ( t: T, validate: ValidateFunction | undefined, clean: (d: A) => AA ) => (formData: A) => (year: TaxYear): Save => ({ type: t, year, formData: validate !== undefined ? validators.checkType(clean(formData), validate) : { ...formData, ...clean(formData) } }) export const saveRefundInfo: ActionCreator = makeActionCreator( ActionName.SAVE_REFUND_INFO, validators.refund ) const cleanPerson =

>(p: P): P => ({ ...p, ssid: p.ssid.replace(/-/g, '') }) export const savePrimaryPersonInfo: ActionCreator = makePreprocessActionCreator( ActionName.SAVE_PRIMARY_PERSON_INFO, validators.primaryPerson, cleanPerson ) export const saveStateResidencyInfo: ActionCreator = makeActionCreator(ActionName.SAVE_STATE_RESIDENCY, validators.stateResidency) export const saveFilingStatusInfo: ActionCreator = makeActionCreator(ActionName.SAVE_FILING_STATUS_INFO, validators.filingStatus) export const saveContactInfo: ActionCreator = makePreprocessActionCreator( ActionName.SAVE_CONTACT_INFO, validators.contactInfo, (t) => ({ ...t, contactPhoneNumber: t.contactPhoneNumber?.replace(/-/g, '') }) ) export const addDependent: ActionCreator = makePreprocessActionCreator( ActionName.ADD_DEPENDENT, validators.dependent, (t: DependentDateString) => cleanPerson(t) ) export const editDependent: ActionCreator = makePreprocessActionCreator( ActionName.EDIT_DEPENDENT, undefined, ({ index, value }: EditDependentAction) => ({ index, value: cleanPerson(value) }) ) export const removeDependent: ActionCreator = makeActionCreator( ActionName.REMOVE_DEPENDENT, indexValidator ) export const addSpouse: ActionCreator = makePreprocessActionCreator( ActionName.ADD_SPOUSE, validators.spouse, cleanPerson ) export const removeSpouse: SignalAction = signalAction(ActionName.REMOVE_SPOUSE) export const addW2: ActionCreator = makeActionCreator( ActionName.ADD_W2, validators.incomeW2 ) export const editW2: ActionCreator = makeActionCreator( ActionName.EDIT_W2 ) export const removeW2: ActionCreator = makeActionCreator( ActionName.REMOVE_W2, indexValidator ) export const addEstimatedPayment: ActionCreator = makeActionCreator( ActionName.ADD_ESTIMATED_TAX, validators.estimatedTaxPayments ) export const editEstimatedPayment: ActionCreator = makeActionCreator(ActionName.EDIT_ESTIMATED_TAX) export const removeEstimatedPayment: ActionCreator = makeActionCreator( ActionName.REMOVE_ESTIMATED_TAX, indexValidator ) export const addHSA: ActionCreator = makeActionCreator(ActionName.ADD_HSA, validators.healthSavingsAccount) export const editHSA: ActionCreator = makeActionCreator( ActionName.EDIT_HSA, validators.editHSAAction ) export const removeHSA: ActionCreator = makeActionCreator( ActionName.REMOVE_HSA, indexValidator ) export const add1099: ActionCreator = makeActionCreator( ActionName.ADD_1099, validators.supported1099 ) export const edit1099: ActionCreator = makeActionCreator( ActionName.EDIT_1099 ) export const remove1099: ActionCreator = makeActionCreator( ActionName.REMOVE_1099, indexValidator ) export const addProperty: ActionCreator = makeActionCreator( ActionName.ADD_PROPERTY, validators.property ) export const editProperty: ActionCreator = makeActionCreator(ActionName.EDIT_PROPERTY) export const removeProperty: ActionCreator = makeActionCreator( ActionName.REMOVE_PROPERTY, indexValidator ) export const answerQuestion: ActionCreator = makeActionCreator( ActionName.ANSWER_QUESTION, validators.responses ) export const add1098e: ActionCreator = makeActionCreator( ActionName.ADD_1098e, validators.f1098e ) export const edit1098e: ActionCreator = makeActionCreator( ActionName.EDIT_1098e ) export const remove1098e: ActionCreator = makeActionCreator( ActionName.REMOVE_1098e, indexValidator ) export const setItemizedDeductions: ActionCreator = makeActionCreator( ActionName.SET_ITEMIZED_DEDUCTIONS, validators.itemizedDeductions ) // debugging purposes only, leaving unchecked. export const setInfo = makePreprocessActionCreator< Information, InformationDateString, ActionName.SET_INFO >(ActionName.SET_INFO, validators.information, (info) => infoToStringInfo(info)) export const setActiveYear: ActionCreator = makeActionCreator( ActionName.SET_ACTIVE_YEAR, validators.taxYear ) export const addIRA: ActionCreator = makeActionCreator( ActionName.ADD_IRA, validators.ira ) export const editIRA: ActionCreator = makeActionCreator( ActionName.EDIT_IRA, validators.editIraAction ) export const removeIRA: ActionCreator = makeActionCreator( ActionName.REMOVE_IRA, indexValidator ) export const addAsset: ActionCreator> = makeActionCreator( ActionName.ADD_ASSET ) export const addAssets: ActionCreator[]> = makeActionCreator( ActionName.ADD_ASSETS ) export const editAsset: ActionCreator = makeActionCreator( ActionName.EDIT_ASSET ) export const removeAsset: ActionCreator = makeActionCreator( ActionName.REMOVE_ASSET, indexValidator ) export const removeAssets: ActionCreator = makeActionCreator( ActionName.REMOVE_ASSETS ) export const addF3921: ActionCreator = makeActionCreator( ActionName.ADD_F3921 ) export const editF3921: ActionCreator = makeActionCreator( ActionName.EDIT_F3921 ) export const removeF3921: ActionCreator = makeActionCreator( ActionName.REMOVE_F3921, indexValidator ) export const addScheduleK1Form1065: ActionCreator = makeActionCreator(ActionName.ADD_SCHEDULE_K1_F1065) export const editScheduleK1Form1065: ActionCreator = makeActionCreator(ActionName.EDIT_SCHEDULE_K1_F1065) export const removeScheduleK1Form1065: ActionCreator = makeActionCreator(ActionName.REMOVE_SCHEDULE_K1_F1065, indexValidator) export const addCredit: ActionCreator = makeActionCreator( ActionName.ADD_CREDIT, validators.credit ) export const editCredit: ActionCreator = makeActionCreator( ActionName.EDIT_CREDIT, validators.editCreditAction ) export const removeCredit: ActionCreator = makeActionCreator( ActionName.REMOVE_CREDIT, indexValidator ) ================================================ FILE: src/redux/data.ts ================================================ import { Asset, Information, Person, TaxYear } from 'ustaxes/core/data' import { blankState } from './reducer' /** * This is a simplified form of our global TaxesState * which allows TaxesState to be viewed as if if contained * data for a single year. */ export type TaxesState = { information: Information } export type YearsTaxesState = { [K in TaxYear]: Information } & { assets: Asset[] activeYear: TaxYear } export const blankYearTaxesState: YearsTaxesState = { assets: [], Y2019: blankState, Y2020: blankState, Y2021: blankState, activeYear: 'Y2020' } export const dateToStringPerson =

>( p: P ): Omit & { dateOfBirth: string } => ({ ...p, dateOfBirth: p.dateOfBirth.toISOString() }) export const stringToDatePerson =

>( p: P ): Omit & { dateOfBirth: Date } => ({ ...p, dateOfBirth: new Date(p.dateOfBirth) }) export const stringToDateInfo = >( info: I ): Information => ({ ...info, healthSavingsAccounts: info.healthSavingsAccounts.map((h) => ({ ...h, startDate: new Date(h.startDate), endDate: new Date(h.endDate) })), taxPayer: { ...info.taxPayer, primaryPerson: info.taxPayer.primaryPerson ? stringToDatePerson(info.taxPayer.primaryPerson) : undefined, dependents: info.taxPayer.dependents.map((d) => stringToDatePerson(d)), spouse: info.taxPayer.spouse ? stringToDatePerson(info.taxPayer.spouse) : undefined } }) export const infoToStringInfo = >( info: I ): Information => ({ ...info, healthSavingsAccounts: info.healthSavingsAccounts.map((h) => ({ ...h, startDate: h.startDate.toISOString(), endDate: h.endDate.toISOString() })), taxPayer: { ...info.taxPayer, primaryPerson: info.taxPayer.primaryPerson ? dateToStringPerson(info.taxPayer.primaryPerson) : undefined, dependents: info.taxPayer.dependents.map((d) => dateToStringPerson(d)), spouse: info.taxPayer.spouse ? dateToStringPerson(info.taxPayer.spouse) : undefined } }) ================================================ FILE: src/redux/fs/Actions.ts ================================================ type FSActionName = 'fs/persist' | 'fs/recover' export interface FSAction { readonly type: FSActionName } export interface FSPersist extends FSAction { readonly type: 'fs/persist' } export interface FSRecover extends FSAction { readonly type: 'fs/recover' data: string } export const fsPersist = (): FSPersist => ({ type: 'fs/persist' }) export const fsRecover = (data: string): FSRecover => ({ type: 'fs/recover', data }) ================================================ FILE: src/redux/fs/FSReducer.ts ================================================ import { AnyAction, Reducer } from 'redux' import { migrateEachYear, migrateAgeAndBlindness } from '../migration' import { download, stateToString, stringToState } from '.' import { USTState } from '../store' import { FSPersist, FSRecover } from './Actions' type PersistActions = FSPersist | FSRecover /** * Extends a reducer to persist and load data * to/from an external JSON file. * This behaves like a Redux "Middleware", which * is basically just a function from reducer to reducer. * It will overwrite whatever state exists with whatever * state it finds, but needs none of its own state. */ export const fsReducer = ( filename: string, reducer: Reducer ): Reducer => { return (state: S | undefined, action: A & PersistActions): S => { const newState = reducer(state, action) switch (action.type) { case 'fs/recover': { // we know now that the action is a FSRecover, // so we can safely cast it to a FSRecover. return { ...newState, ...migrateAgeAndBlindness( migrateEachYear( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument stringToState(action.data) ) ) } as S // migrations return any, must coerce. } case 'fs/persist': { download(filename, stateToString(newState)) return newState } default: { return newState } } } } ================================================ FILE: src/redux/fs/Load.tsx ================================================ import { Button, ButtonProps } from '@material-ui/core' import { ChangeEvent, PropsWithChildren, ReactElement } from 'react' import { intentionallyFloat } from 'ustaxes/core/util' import { loadFile } from '.' interface LoadProps { handleData: (s: S) => void } interface Accept { accept?: string } export const LoadRaw = ( props: PropsWithChildren & Accept & ButtonProps> ): ReactElement => { const { children, handleData, accept = '.*,text', ...rest } = props const onClick = async (e: ChangeEvent): Promise => { e.preventDefault() const files = e.target.files if (files === null || files.length < 1) { return } const file = files[0] handleData(await loadFile(file)) } return ( ) } const Load = ( props: PropsWithChildren & ButtonProps & Accept> ): ReactElement => { const { children, handleData, ...rest } = props return ( handleData(JSON.parse(contents) as S)} > {children} ) } export default Load ================================================ FILE: src/redux/fs/index.ts ================================================ /* eslint-disable @typescript-eslint/no-explicit-any */ import { deserializeTransform, serializeTransform, USTState } from '../store' import Load from './Load' // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types export const stateToString = (state: any): string => JSON.stringify(serializeTransform(state)) /** * Reuse the same functionality as reloading the state from redux-persist, * to reload state from a file */ export const stringToState = (str: string): USTState => deserializeTransform(JSON.parse(str)) as USTState export const download = (filename: string, text: string): void => { const element = document.createElement('a') element.setAttribute( 'href', 'data:text/json;charset=utf-8,' + encodeURIComponent(text) ) element.setAttribute('download', filename) element.style.display = 'none' document.body.appendChild(element) element.click() document.body.removeChild(element) } export const loadFile = async (file: File): Promise => { const fileReader: FileReader = new FileReader() return new Promise((resolve, reject) => { fileReader.onload = function ( this: FileReader, e: ProgressEvent ): any { e.preventDefault() // type known here because of readAsText const contents: string | null = fileReader.result as string | null if (contents === null) { reject('file contents were null') } else { resolve(contents) } } fileReader.readAsText(file) }) } export { Load } ================================================ FILE: src/redux/index.ts ================================================ // override default useDispatch with our // version that lets us curry the tax year import { useYearDispatch as useDispatch, useYearSelector as useSelector } from './yearDispatch' export * from './data' export * from './TaxesState' export { useDispatch, useSelector } ================================================ FILE: src/redux/migration.ts ================================================ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ import { enumKeys } from 'ustaxes/core/util' import { Person, Dependent, Spouse, PrimaryPerson, TaxYears, FilingStatus } from 'ustaxes/core/data' import { blankState } from './reducer' import { USTState } from './store' export interface QualifyingInformationV0 { numberOfMonths: number isStudent: boolean birthYear: number } export interface DependentV0 extends Omit { relationship: string qualifyingInfo?: QualifyingInformationV0 } export type PrimaryPersonV0 = Omit export type SpouseV0 = Omit export type USTStateV0 = { [P in keyof Omit]: USTState[P] } & { taxPayer: { primaryPerson?: PrimaryPersonV0 dependents: DependentV0[] filingStatus?: FilingStatus spouse?: SpouseV0 } } function migrateDependent(p: DependentV0): Dependent { const birthYear = p.qualifyingInfo?.birthYear const q: Dependent = { ...p, qualifyingInfo: { numberOfMonths: p.qualifyingInfo?.numberOfMonths ?? 0, isStudent: p.qualifyingInfo?.isStudent ?? false }, dateOfBirth: birthYear !== undefined ? new Date(birthYear, 0, 1) : new Date(), isBlind: false } return q } function migratePrimaryOrSpouse(p: Spouse | PrimaryPerson) { if (p.isBlind === undefined) { p.isBlind = false } if (p.dateOfBirth === undefined) { p.dateOfBirth = new Date() } return p } export const migrateAgeAndBlindness = (state: S): S => enumKeys(TaxYears).reduce((acc, year) => { if (acc[year].taxPayer !== undefined) { const primaryPerson = acc[year].taxPayer.primaryPerson if (primaryPerson !== undefined) { acc[year].taxPayer.primaryPerson = migratePrimaryOrSpouse( primaryPerson ) as PrimaryPerson } const spouse = acc[year].taxPayer.spouse if (spouse !== undefined) { acc[year].taxPayer.spouse = migratePrimaryOrSpouse(spouse) as Spouse } acc[year].taxPayer.dependents = acc[year].taxPayer.dependents.map((d) => migrateDependent(d as DependentV0) ) } return { ...acc, [year]: { ...blankState, ...acc[year] } } }, state) /** * Ensures all default fields are present on each year's data * @returns state */ export const migrateEachYear = (state: S): S => enumKeys(TaxYears).reduce((acc, year) => { // Make sure SS wages are set on W2s acc[year].w2s.forEach((w2) => { if (w2.ssWages === undefined) { w2.ssWages = 0 } }) // Ensure interestIncome on K1s acc[year].scheduleK1Form1065s.forEach((k1) => { if (k1.interestIncome === undefined) { k1.interestIncome = 0 } }) return { ...acc, [year]: { ...blankState, ...acc[year] } } }, state) ================================================ FILE: src/redux/reducer.ts ================================================ /* eslint-disable indent */ import { CombinedState, combineReducers, Reducer } from 'redux' import { Asset, FilingStatus, Information, TaxYear } from 'ustaxes/core/data' import { YearsTaxesState } from '.' import { ActionName, Actions } from './actions' import { stringToDateInfo } from './data' const DEFAULT_TAX_YEAR: TaxYear = 'Y2021' export const blankState: Information = { f1099s: [], w2s: [], estimatedTaxes: [], realEstate: [], taxPayer: { dependents: [] }, questions: {}, f1098es: [], f3921s: [], scheduleK1Form1065s: [], itemizedDeductions: undefined, stateResidencies: [], healthSavingsAccounts: [], credits: [], individualRetirementArrangements: [] } const formReducer = ( state: Information | undefined, action: Actions ): Information => { const newState: Information = state ?? blankState switch (action.type) { case ActionName.SAVE_PRIMARY_PERSON_INFO: { return { ...newState, taxPayer: { ...newState.taxPayer, primaryPerson: { ...action.formData, dateOfBirth: new Date(action.formData.dateOfBirth) } } } } case ActionName.SAVE_CONTACT_INFO: { return { ...newState, taxPayer: { ...newState.taxPayer, ...action.formData } } } case ActionName.SAVE_STATE_RESIDENCY: { return { ...newState, stateResidencies: [action.formData] } } case ActionName.SAVE_FILING_STATUS_INFO: { return { ...newState, taxPayer: { ...newState.taxPayer, filingStatus: action.formData } } } case ActionName.ADD_DEPENDENT: { return { ...newState, taxPayer: { ...newState.taxPayer, dependents: [ ...newState.taxPayer.dependents, { ...action.formData, dateOfBirth: new Date(action.formData.dateOfBirth) } ] } } } // Replace dependent by index with a new object. case ActionName.EDIT_DEPENDENT: { const newDependents = [...newState.taxPayer.dependents] newDependents.splice(action.formData.index, 1, { ...action.formData.value, dateOfBirth: new Date(action.formData.value.dateOfBirth) }) return { ...newState, taxPayer: { ...newState.taxPayer, dependents: newDependents } } } case ActionName.REMOVE_DEPENDENT: { const newDependents = [...newState.taxPayer.dependents] newDependents.splice(action.formData, 1) const filingStatus = (() => { if ( newDependents.length === 0 && newState.taxPayer.filingStatus === FilingStatus.HOH ) { return undefined } return newState.taxPayer.filingStatus })() return { ...newState, taxPayer: { ...newState.taxPayer, filingStatus, dependents: newDependents } } } case ActionName.SAVE_REFUND_INFO: { return { ...newState, refund: action.formData } } case ActionName.ADD_W2: { return { ...newState, w2s: [...newState.w2s, action.formData] } } case ActionName.EDIT_W2: { const newW2s = [...newState.w2s] newW2s.splice(action.formData.index, 1, action.formData.value) return { ...newState, w2s: newW2s } } case ActionName.REMOVE_W2: { const newW2s = [...newState.w2s] newW2s.splice(action.formData, 1) return { ...newState, w2s: newW2s } } case ActionName.ADD_ESTIMATED_TAX: { return { ...newState, estimatedTaxes: [...newState.estimatedTaxes, action.formData] } } case ActionName.EDIT_ESTIMATED_TAX: { const newEstimatedTaxes = [...newState.estimatedTaxes] newEstimatedTaxes.splice(action.formData.index, 1, action.formData.value) return { ...newState, estimatedTaxes: newEstimatedTaxes } } case ActionName.REMOVE_ESTIMATED_TAX: { const newEstimatedTaxes = [...newState.estimatedTaxes] newEstimatedTaxes.splice(action.formData, 1) return { ...newState, estimatedTaxes: newEstimatedTaxes } } case ActionName.ADD_1099: { return { ...newState, f1099s: [...newState.f1099s, action.formData] } } case ActionName.EDIT_1099: { const new1099s = [...newState.f1099s] new1099s.splice(action.formData.index, 1, action.formData.value) return { ...newState, f1099s: new1099s } } case ActionName.REMOVE_1099: { const new1099s = [...newState.f1099s] new1099s.splice(action.formData, 1) return { ...newState, f1099s: new1099s } } case ActionName.ADD_SPOUSE: { return { ...newState, taxPayer: { ...newState.taxPayer, spouse: { ...action.formData, dateOfBirth: new Date(action.formData.dateOfBirth) } } } } case ActionName.REMOVE_SPOUSE: { const filingStatus = (() => { const fs = newState.taxPayer.filingStatus if ([FilingStatus.MFS, FilingStatus.MFJ, undefined].includes(fs)) { return undefined } return fs })() return { ...newState, taxPayer: { ...newState.taxPayer, filingStatus, spouse: undefined } } } case ActionName.ADD_PROPERTY: { return { ...newState, realEstate: [...newState.realEstate, action.formData] } } case ActionName.EDIT_PROPERTY: { const newProperties = [...newState.realEstate] newProperties.splice(action.formData.index, 1, action.formData.value) return { ...newState, realEstate: newProperties } } case ActionName.REMOVE_PROPERTY: { const newProperties = [...newState.realEstate] newProperties.splice(action.formData, 1) return { ...newState, realEstate: newProperties } } case ActionName.ANSWER_QUESTION: { // must reset all questions return { ...newState, questions: action.formData } } case ActionName.ADD_1098e: { return { ...newState, f1098es: [...newState.f1098es, action.formData] } } case ActionName.EDIT_1098e: { const new1098es = [...newState.f1098es] new1098es.splice(action.formData.index, 1, action.formData.value) return { ...newState, f1098es: new1098es } } case ActionName.REMOVE_1098e: { const new1098es = [...newState.f1098es] new1098es.splice(action.formData, 1) return { ...newState, f1098es: new1098es } } case ActionName.ADD_F3921: { return { ...newState, f3921s: [...newState.f3921s, action.formData] } } case ActionName.EDIT_F3921: { const newf3921s = [...newState.f3921s] newf3921s.splice(action.formData.index, 1, action.formData.value) return { ...newState, f3921s: newf3921s } } case ActionName.REMOVE_F3921: { const newf3921s = [...newState.f3921s] newf3921s.splice(action.formData, 1) return { ...newState, f3921s: newf3921s } } case ActionName.ADD_SCHEDULE_K1_F1065: { return { ...newState, scheduleK1Form1065s: [...newState.scheduleK1Form1065s, action.formData] } } case ActionName.EDIT_SCHEDULE_K1_F1065: { const newK1s = [...newState.scheduleK1Form1065s] newK1s.splice(action.formData.index, 1, action.formData.value) return { ...newState, scheduleK1Form1065s: newK1s } } case ActionName.REMOVE_SCHEDULE_K1_F1065: { const newK1s = [...newState.scheduleK1Form1065s] newK1s.splice(action.formData, 1) return { ...newState, scheduleK1Form1065s: newK1s } } case ActionName.SET_ITEMIZED_DEDUCTIONS: { return { ...newState, itemizedDeductions: action.formData } } case ActionName.SET_INFO: { return { ...newState, ...stringToDateInfo(action.formData) } } case ActionName.ADD_HSA: { return { ...newState, healthSavingsAccounts: [ ...newState.healthSavingsAccounts, { ...action.formData, endDate: new Date(action.formData.endDate), startDate: new Date(action.formData.startDate) } ] } } case ActionName.EDIT_HSA: { const newHsa = [...newState.healthSavingsAccounts] newHsa.splice(action.formData.index, 1, { ...action.formData.value, endDate: new Date(action.formData.value.endDate), startDate: new Date(action.formData.value.startDate) }) return { ...newState, healthSavingsAccounts: newHsa } } case ActionName.REMOVE_HSA: { const newHsa = [...newState.healthSavingsAccounts] newHsa.splice(action.formData, 1) return { ...newState, healthSavingsAccounts: newHsa } } case ActionName.ADD_IRA: { return { ...newState, individualRetirementArrangements: [ ...newState.individualRetirementArrangements, action.formData ] } } case ActionName.EDIT_IRA: { const newIra = [...newState.individualRetirementArrangements] newIra.splice(action.formData.index, 1, action.formData.value) return { ...newState, individualRetirementArrangements: newIra } } case ActionName.REMOVE_IRA: { const newIra = [...newState.individualRetirementArrangements] newIra.splice(action.formData, 1) return { ...newState, individualRetirementArrangements: newIra } } case ActionName.ADD_CREDIT: { return { ...newState, credits: [...newState.credits, action.formData] } } case ActionName.EDIT_CREDIT: { const newCredits = [...newState.credits] newCredits.splice(action.formData.index, 1, action.formData.value) return { ...newState, credits: newCredits } } case ActionName.REMOVE_CREDIT: { const newCredits = [...newState.credits] newCredits.splice(action.formData, 1) return { ...newState, credits: newCredits } } default: { return newState } } } const guardByYear = (year: TaxYear) => (state: Information | undefined, action: Actions): Information => { const newState: Information = state ?? blankState if (action.year !== year) { return newState } return formReducer(newState, action) } const activeYear = (state: TaxYear | undefined, action: Actions): TaxYear => { const newState = state ?? DEFAULT_TAX_YEAR switch (action.type) { case ActionName.SET_ACTIVE_YEAR: { return action.formData } default: { return newState } } } const assetReducer = ( state: Asset[] | undefined, action: Actions ): Asset[] => { const newState = state ?? [] switch (action.type) { case ActionName.ADD_ASSET: { return [...newState, action.formData] } case ActionName.ADD_ASSETS: { return [...newState, ...action.formData] } case ActionName.EDIT_ASSET: { const newAssets = [...newState] newAssets.splice(action.formData.index, 1, action.formData.value) return newAssets } case ActionName.REMOVE_ASSET: { const newAssets = [...newState] newAssets.splice(action.formData, 1) return newAssets } case ActionName.REMOVE_ASSETS: { return newState.filter((_, i) => !action.formData.includes(i)) } default: { return newState } } } const rootReducer: Reducer< CombinedState, Actions > = combineReducers({ assets: assetReducer, Y2019: guardByYear('Y2019'), Y2020: guardByYear('Y2020'), Y2021: guardByYear('Y2021'), activeYear }) as Reducer, Actions> export default rootReducer ================================================ FILE: src/redux/store.ts ================================================ import { createStore as reduxCreateStore, applyMiddleware, Store, CombinedState } from 'redux' import logger from 'redux-logger' import rootReducer from './reducer' import _ from 'lodash' import { persistStore, persistReducer, PersistedState, createMigrate } from 'redux-persist' import storage from 'redux-persist/lib/storage' // defaults to localStorage for web import { Asset, Information, TaxYear } from 'ustaxes/core/data' import { blankYearTaxesState, YearsTaxesState } from '.' import { Actions } from './actions' import { PersistPartial } from 'redux-persist/es/persistReducer' import { createTransform } from 'redux-persist' import { FSAction } from './fs/Actions' import { fsReducer } from './fs/FSReducer' import { migrateEachYear, migrateAgeAndBlindness } from './migration' type SerializedState = { [K in TaxYear]: Information } & { assets: Asset[] activeYear: TaxYear } export type USTSerializedState = NonNullable & SerializedState export type USTState = NonNullable & YearsTaxesState /** * Redux-persist calls the transform function not for * the entire state, but for each reducer in our state. */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ // A key that must indicate a date value. // eg: dateOfBirth, or openDate const dateKey = /(^date)|(Date)/ const serializeDeserialize = (f: (d: Date | string) => Date | string) => (s: any): any => { const recur = serializeDeserialize(f) if (_.isArray(s)) { return s.map((p) => recur(p)) } else if (_.isObject(s)) { const ob = s as { [k: string]: any } return Object.keys(ob).reduce((acc, k) => { const newValue = (() => { if (dateKey.exec(k) !== null) { return f(ob[k] as Date | string) } return recur(ob[k]) })() return { ...acc, [k]: newValue } }, {}) } else { return s } } /** * Look for all the Dates that need to be turned to strings */ export const serializeTransform: (s: any) => any = serializeDeserialize((d) => (d as Date).toISOString() ) /** * Look for all strings that need to be turned to Dates */ export const deserializeTransform: (s: any) => any = serializeDeserialize( (d) => new Date(d as string) ) /* eslint-enable @typescript-eslint/no-unsafe-assignment */ /* eslint-enable @typescript-eslint/no-unsafe-argument */ /* eslint-enable @typescript-eslint/no-unsafe-return */ /* eslint-enable @typescript-eslint/no-unsafe-call */ const dateStringTransform = createTransform( serializeTransform, deserializeTransform ) //const migrate = async (state: USTState): Promise => // migrateEachYear(state) // Keys are the version numbers. They must be integers. // Each version takes a state and applies a function that // generates the state for that version from the previous // version. // The below migrations deal with types that don't exist in this // project anymore, and the hope is that after applying the // migrations to the user's data, the resulting data will match // the YearsTaxesState type. But without maintaining // type definititions for every possible version, we can't // really say anything about the type of the incoming data. /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-return */ const migrations = { 0: (state: any) => migrateEachYear(state), 1: (state: any) => migrateAgeAndBlindness(state) } /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-enable @typescript-eslint/no-explicit-any */ const persistedReducer = fsReducer( 'ustaxes_save.json', persistReducer, Actions>( { key: 'root', // Changing the version here will set the version used // in the app. When the data is rehydrated the version // number will be compared and all migrations between // the persisted version and the version here will be // applied in order version: 1, storage, migrate: createMigrate(migrations, { debug: false }), transforms: [dateStringTransform] }, rootReducer ) ) export type InfoStore = Store & { dispatch: unknown } export type PersistedStore = Store< YearsTaxesState & PersistPartial, FSAction & Actions > & { dispatch: unknown } export const createWholeStoreUnpersisted = ( state: YearsTaxesState ): InfoStore => reduxCreateStore(rootReducer, state, undefined) export const createStoreUnpersisted = (information: Information): InfoStore => createWholeStoreUnpersisted({ ...blankYearTaxesState, Y2020: information }) export const createStore = (): PersistedStore => reduxCreateStore(persistedReducer, applyMiddleware(logger)) export const store = createStore() export const persistor = persistStore(store) ================================================ FILE: src/redux/yearDispatch.ts ================================================ import { Dispatch } from 'react' import { useDispatch, useSelector } from 'react-redux' import { SignalAction } from './actions' import { YearsTaxesState } from '.' import { TaxYear } from 'ustaxes/core/data' import { TaxesState } from '.' /** * Provides an override over the default redux dispatch * so that the user can send events and watch state * ignoring the current active year. Modifications * are posted to the current selected year. */ const useYearDispatch = (): Dispatch => { const dispatch = useDispatch() const year: TaxYear = useSelector( (state: YearsTaxesState) => state.activeYear ) return (v: SignalAction) => dispatch(v(year)) } /** * Provides an override of the useSelector hook for * UI that doesn't care which year's data is currently * being accessed. */ const useYearSelector = (f: (t: TaxesState) => R): R => f( useSelector((state: YearsTaxesState) => ({ information: state[state.activeYear] })) ) export { useYearDispatch, useYearSelector } ================================================ FILE: src/serviceWorker.js ================================================ // This optional code is used to register a service worker. // register() is not called by default. // This lets the app load faster on subsequent visits in production, and gives // it offline capabilities. However, it also means that developers (and users) // will only see deployed updates on subsequent visits to a page, after all the // existing tabs open on the page have been closed, since previously cached // resources are updated in the background. // To learn more about the benefits of this model and instructions on how to // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( window.location.hostname === 'localhost' || // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. window.location.hostname.match( /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ ) ) function registerValidSW(swUrl, config) { navigator.serviceWorker .register(swUrl) .then((registration) => { registration.onupdatefound = () => { const installingWorker = registration.installing if (installingWorker == null) { return } installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { if (navigator.serviceWorker.controller) { // At this point, the updated precached content has been fetched, // but the previous service worker will still serve the older // content until all client tabs are closed. console.log( 'New content is available and will be used when all ' + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' ) // Execute callback if (config && config.onUpdate) { config.onUpdate(registration) } } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. console.log('Content is cached for offline use.') // Execute callback if (config && config.onSuccess) { config.onSuccess(registration) } } } } } }) .catch((error) => { console.error('Error during service worker registration:', error) }) } function checkValidServiceWorker(swUrl, config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { headers: { 'Service-Worker': 'script' } }) .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. const contentType = response.headers.get('content-type') if ( response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1) ) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { window.location.reload() }) }) } else { // Service worker found. Proceed as normal. registerValidSW(swUrl, config) } }) .catch(() => { console.log( 'No internet connection found. App is running in offline mode.' ) }) } export function register(config) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href) if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebook/create-react-app/issues/2374 return } window.addEventListener('load', () => { const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. checkValidServiceWorker(swUrl, config) // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + 'worker. To learn more, visit https://bit.ly/CRA-PWA' ) }) } else { // Is not localhost. Just register service worker registerValidSW(swUrl, config) } }) } } export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready .then((registration) => { registration.unregister() }) .catch((error) => { console.error(error.message) }) } } ================================================ FILE: src/setupTests.js ================================================ /* eslint-disable */ // jest-dom adds custom jest matchers for asserting on DOM nodes. // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom/extend-expect' const localStorageMock = { getItem: jest.fn(), setItem: jest.fn(), clear: jest.fn() } global.localStorage = localStorageMock global.console = { log: jest.fn(), error: jest.fn(), // Keep native behaviour for other methods, use those to print out things in your own tests warn: console.warn, info: console.info, debug: console.debug } ================================================ FILE: src/testUtil.tsx ================================================ import { ReactElement } from 'react' import { render, RenderOptions, RenderResult } from '@testing-library/react' import { BrowserRouter as Router } from 'react-router-dom' export const resizeWindow = (x: number, y: number): void => { global.innerWidth = x global.innerHeight = y window.dispatchEvent(new Event('resize')) } export const renderWithProviders = ( ui: ReactElement, options: RenderOptions = {} ): RenderResult => render({ui}, options) ================================================ FILE: src/tests/MultipleYears.test.tsx ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import * as fc from 'fast-check' import * as utarbitraries from './arbitraries' import { cleanup, waitFor } from '@testing-library/react' import { blankYearTaxesState, YearsTaxesState } from 'ustaxes/redux' import { ReactElement } from 'react' import TaxPayer from 'ustaxes/components/TaxPayer' import { tests as TaxPayerTests } from './components/Taxpayer.test' import { blankState } from 'ustaxes/redux/reducer' import { FakePagerProvider, PagerMethods } from './common/FakePager' import YearStatusBar from 'ustaxes/components/YearStatusBar' import YearStatusBarMethods from './common/YearsStatusBarMethods' import { PersonMethods } from './common/PersonMethods' import TestPage from './common/Page' import TaxPayerMethods from './common/TaxPayerMethods' // RTL's cleanup method only after each // jest test is done. (Not each property test) afterEach(() => { cleanup() }) jest.setTimeout(40000) fc.configureGlobal({ interruptAfterTimeLimit: 35000, markInterruptAsFailure: false // When set to true, timeout during initial cases (1) will be marked as an error // When set to false, timeout during initial cases (1) will not be considered as a failure }) class TestForm extends TestPage { yearStatus: YearStatusBarMethods person: PersonMethods taxPayer: TaxPayerMethods pager: PagerMethods constructor(state: YearsTaxesState) { super(state) this.yearStatus = new YearStatusBarMethods(() => this.rendered().getByTestId('year-status-bar') ) this.person = new PersonMethods(() => this.rendered().getByTestId('taxpayer') ) this.taxPayer = new TaxPayerMethods(() => this.rendered().getByTestId('taxpayer') ) this.pager = new PagerMethods(() => this.rendered().getByTestId('taxpayer')) } component: ReactElement = (

) } const withForm = (state: YearsTaxesState) => async ( f: (form: TestForm) => Promise ): Promise => { try { const form = new TestForm(state) try { const res = await f(form).catch((e) => { console.info('Error caught in handling promise.') console.info(e) console.info(form.rendered().container.innerHTML) form.cleanup() throw e }) form.cleanup() return res ?? true } catch (e) { console.info('Error caught in handling outer.') console.info(e) console.info(form.rendered().container.innerHTML) form.cleanup() throw e } } catch (e) { console.error('Caught exception') console.info(state) throw e } } describe('years', () => { it('should have open feature', async () => { const state = utarbitraries.justOneState() await withForm(state)(async (form): Promise => { await waitFor(() => expect(form.yearStatus.yearDropdownButton()).toBeInTheDocument() ) form.yearStatus.openDropdown() await waitFor(() => expect(form.yearStatus.yearDropdownButton()).not.toBeInTheDocument() ) expect(form.yearStatus.yearSelect()).toBeInTheDocument() return true }) }) it('should start with correct selected', async () => { const state = utarbitraries.justOneState() await withForm(state)(async (form): Promise => { await waitFor(() => { expect(form.store.getState().activeYear).toBe(state.activeYear) expect(form.yearStatus.yearValue()).toEqual(state.activeYear) }) return true }) }) it('should set active year in model', async () => { await fc .assert( fc.asyncProperty( utarbitraries.taxYear, utarbitraries.taxYear, async (startYear, year) => { // Generating states can lead to long runtimes as the generator // tries to fiddle with all of state's parameters to induce a failure. // We're just testing the year part of the state now. const state = { ...blankYearTaxesState, [startYear]: blankState, activeYear: startYear } await withForm(state)(async (form): Promise => { await form.yearStatus.setYear(year) await waitFor(() => expect(form.store.getState().activeYear).toEqual(year) ) form.yearStatus.openDropdown() await waitFor(() => expect(form.yearStatus.yearValue()).toEqual(year) ) return true }) } ) ) .catch((e) => { console.info('exception from property') throw e }) }) it('should update form data on select', async () => await fc.assert( fc.asyncProperty(utarbitraries.taxYear, async (year) => { const state = utarbitraries.justOneState() await withForm(state)(async (form) => { await form.yearStatus.setYear(year) await waitFor(() => expect(form.person.firstNameField()?.value).toEqual( state[year].taxPayer.primaryPerson?.firstName ) ) return true }) }) )) it('selecting year should not impact taxpayer form error handling after changing year', async () => { const form = new TestForm({ ...blankYearTaxesState, Y2020: blankState, activeYear: 'Y2020' }) await TaxPayerTests.incompleteData(form) await form.yearStatus.setYear('Y2021') await TaxPayerTests.incompleteData(form) form.cleanup() }) }) ================================================ FILE: src/tests/Recover.test.ts ================================================ import * as fc from 'fast-check' import * as arbitraries from 'ustaxes/core/tests/arbitraries' import { stateToString, stringToState } from 'ustaxes/redux/fs' describe('FS Recover / Save', () => { it('should restore the same data it created', () => { fc.assert( fc.property(arbitraries.yearsTaxesState, (state) => { expect(stringToState(stateToString(state))).toEqual(state) }) ) }) }) ================================================ FILE: src/tests/arbitraries.ts ================================================ import * as fc from 'fast-check' import * as util from 'ustaxes/core/util' import * as arbitraries from 'ustaxes/core/tests/arbitraries' import { YearsTaxesState } from 'ustaxes/redux' import prand from 'pure-rand' import { Asset, AssetType, TaxYear, TaxYears } from 'ustaxes/core/data' export const taxYear: fc.Arbitrary = fc.constantFrom( ...util.enumKeys(TaxYears) ) export const taxYearNumber: fc.Arbitrary = taxYear.map( (y) => TaxYears[y] ) const dateInYear = (year: number) => fc.date({ min: new Date(year, 0, 1), max: new Date(year, 11, 31) }) const taxYearDate = taxYearNumber.chain(dateInYear) const orUndefined = (arb: fc.Arbitrary) => fc.oneof(arb, fc.constant(undefined)) export const positionDate: fc.Arbitrary> = fc .tuple( fc.date(), orUndefined(taxYearDate), fc.oneof( fc.constant('Security'), fc.constant('Real Estate') ) ) .chain(([openDate, closeDate, positionType]) => fc .tuple( arbitraries.word, orUndefined(fc.date({ min: openDate })), fc.nat(), fc.nat(), fc.nat(), fc.nat(), fc.nat(), arbitraries.state ) .map( ([ name, giftedDate, openPrice, closePrice, quantity, openFee, closeFee, state ]) => ({ name, openDate, closeDate, giftedDate: closeDate === undefined ? giftedDate : undefined, openPrice, closePrice, openFee, closeFee: closeDate === undefined ? undefined : closeFee, positionType, quantity: positionType === 'Real Estate' ? 1 : quantity, state }) ) ) export const position: fc.Arbitrary> = positionDate.map((p) => ({ ...p, openDate: p.openDate.toISOString(), closeDate: p.closeDate?.toISOString(), giftedDate: p.giftedDate?.toISOString() })) export const taxesState: fc.Arbitrary = taxYear.chain( (activeYear) => { const information = arbitraries.forYear(TaxYears[activeYear]).information() return fc .tuple(fc.array(positionDate), information, information, information) .map(([assets, Y2019, Y2020, Y2021]) => ({ assets, Y2019, Y2020, Y2021, activeYear })) } ) const gen = new fc.Random(prand.mersenne(new Date().getMilliseconds())) export const justOneState = (): YearsTaxesState => taxesState.noShrink().generate(gen).value ================================================ FILE: src/tests/common/DomMethods.ts ================================================ export default class DomMethods { dom: () => HTMLElement constructor(dom: () => HTMLElement) { this.dom = dom } } ================================================ FILE: src/tests/common/FakePager.tsx ================================================ import { Button } from '@material-ui/core' import { within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { PropsWithChildren, ReactElement } from 'react' import { PagerContext, PagerProps } from 'ustaxes/components/pager' import DomMethods from './DomMethods' const constPagerProps: PagerProps = { onAdvance: () => { // not needed }, navButtons: ( ) } export class PagerMethods extends DomMethods { saveButton = (): HTMLButtonElement => within(this.dom()).getByRole('button', { name: /Save/i }) save = (): void => userEvent.click(this.saveButton()) } export const FakePagerProvider = ({ children }: PropsWithChildren>): ReactElement => ( {children} ) ================================================ FILE: src/tests/common/Page.tsx ================================================ import { YearsTaxesState } from 'ustaxes/redux' import { ReactElement } from 'react' import { createWholeStoreUnpersisted, InfoStore } from 'ustaxes/redux/store' import { Provider } from 'react-redux' import * as Queries from '@testing-library/dom/types/queries' import { RenderResult } from '@testing-library/react' import { renderWithProviders } from 'ustaxes/testUtil' export type TestRenderResult = RenderResult export abstract class TestPage { private _rendered: TestRenderResult | undefined private _baseElement: HTMLElement | undefined abstract component: ReactElement initialState: YearsTaxesState store: InfoStore constructor(state: YearsTaxesState) { this.initialState = state this.store = createWholeStoreUnpersisted(state) } renderComponent(): ReactElement { return {this.component} } rendered = (): TestRenderResult => { if (this._rendered === undefined) { // Attempt to fully isolate the rendered component // so that this rendered component may be safely // accessed asynchronously const baseElement: HTMLElement = document.createElement('div') document.getElementsByTagName('body')[0].appendChild(baseElement) const rendered = renderWithProviders(this.renderComponent(), { baseElement }) rendered.debug(undefined, Infinity) this._baseElement = baseElement this._rendered = rendered } return this._rendered } allFieldNames = (): string[] => this.rendered() .getAllByRole('textbox') .flatMap((x) => { const name = x.getAttribute('name') return name !== null ? [name] : [] }) cleanup = (): void => { this._rendered?.unmount() this._rendered = undefined this._baseElement?.remove() this._baseElement = undefined } } /** * Attempts to help with the cryptic error messages that can be * thrown out by `waitFor`. * @param makePage * @returns */ export const withPage =

(makePage: (state: YearsTaxesState) => P) => (state: YearsTaxesState) => async (f: (page: P) => Promise): Promise => { try { const page = makePage(state) try { const res = await f(page).catch((e) => { console.info('Error caught in handling promise.') console.info(e) console.info(page.rendered().container.innerHTML) page.cleanup() throw e }) page.cleanup() return res ?? true } catch (e) { console.info('Error caught in handling outer.') console.info(e) console.info(page.rendered().container.innerHTML) page.cleanup() throw e } } catch (e) { console.error('Caught exception') console.info(state) throw e } } export default TestPage ================================================ FILE: src/tests/common/PersonMethods.tsx ================================================ import { labels as personLabels } from 'ustaxes/components/TaxPayer/PersonFields' import { within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import DomMethods from './DomMethods' export class PersonMethods extends DomMethods { firstNameField = (): HTMLInputElement | null => within(this.dom()).queryByLabelText(personLabels.fname) setIfAble = (f: HTMLInputElement | null, v: string): boolean => { if (f !== null) { userEvent.type(f, v) return true } return false } setFirstName = (v: string): boolean => this.setIfAble(this.firstNameField(), v) lastNameField = (): HTMLInputElement | null => within(this.dom()).queryByLabelText(personLabels.lname) setLastName = (v: string): boolean => this.setIfAble(this.lastNameField(), v) ssnField = (): HTMLInputElement | null => within(this.dom()).queryByLabelText(personLabels.ssn) dateOfBirthField = (): HTMLInputElement | null => // not sure why querying by label is not working. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion within(this.dom()).queryByTestId('dateOfBirth')!.children[1] .children[0] as HTMLInputElement | null requiredErrors = (): HTMLElement[] => within(this.dom()).queryAllByText('Input is required') dateOfBirthErrors = (): HTMLElement[] => within(this.dom()).queryAllByText('Invalid Date Format') saveButton = (): HTMLButtonElement | null => within(this.dom()).queryByRole('button', { name: /Save/ }) closeButton = (): HTMLButtonElement | null => within(this.dom()).queryByRole('button', { name: /Discard/i }) deleteButtons = (): HTMLButtonElement[] => within(this.dom()).queryAllByRole('button', { name: /delete/ }) } ================================================ FILE: src/tests/common/TaxPayerMethods.tsx ================================================ import { within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { ReactElement } from 'react' import TaxPayer from 'ustaxes/components/TaxPayer' import DomMethods from './DomMethods' export default class TaxPayerMethods extends DomMethods { component: ReactElement = () g = { foreignCountryBox: (): HTMLInputElement => within(this.dom()).getByLabelText('Do you have a foreign address?') } setIsForeignCountry = (value: boolean): void => (value ? userEvent.click : userEvent.clear)(this.g.foreignCountryBox()) } ================================================ FILE: src/tests/common/YearsStatusBarMethods.tsx ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { waitFor, waitForElementToBeRemoved, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { enumKeys } from 'ustaxes/core/util' import { TaxYear, TaxYears } from 'ustaxes/core/data' import DomMethods from './DomMethods' export default class YearStatusBarMethods extends DomMethods { yearDropdownButton = (): HTMLElement | null => within(this.dom()).queryByTestId('year-dropdown-button') openDropdown = (): void => { userEvent.click(this.yearDropdownButton()!) } yearSelect = (): HTMLSelectElement => within(this.dom()).getByRole('combobox') year = (): HTMLInputElement | null => within(this.dom()).queryByLabelText('Select Tax Year') yearValue = (): TaxYear | undefined => { const y = this.year() if (y !== null) { return y.value as TaxYear } else { const year = this.yearDropdownButton()?.textContent?.trim() if (year !== undefined) { return enumKeys(TaxYears).find((v) => TaxYears[v] === parseInt(year)) } else { throw new Error('Cannot read year in form') } } } yearSelectConfirm = (): HTMLButtonElement | null => within(this.dom()).queryByRole('button', { name: /Update/ }) getOption = (y: TaxYear): HTMLOptionElement | null => (within(this.dom()) .getAllByRole('option') .find((x) => x.getAttribute('value') === y) as | HTMLOptionElement | undefined) ?? null setYear = async (y: TaxYear): Promise => { this.openDropdown() await waitFor(() => { expect(this.yearSelectConfirm()).toBeInTheDocument() }) userEvent.selectOptions(this.yearSelect(), [y]) userEvent.click(this.yearSelectConfirm()!) await waitForElementToBeRemoved(() => this.yearSelectConfirm()) } } ================================================ FILE: src/tests/components/CreatePdf.test.tsx ================================================ import { waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { ReactElement } from 'react' import CreatePDF from 'ustaxes/components/CreatePDF' import { Information } from 'ustaxes/core/data' import { F1040Error } from 'ustaxes/forms/errors' import { blankState } from 'ustaxes/redux/reducer' import { FakePagerProvider, PagerMethods } from '../common/FakePager' import * as arbitraries from 'ustaxes/core/tests/arbitraries' import * as fc from 'fast-check' import TestPage from '../common/Page' import { blankYearTaxesState, YearsTaxesState } from 'ustaxes/redux' afterEach(async () => { await waitFor(() => localStorage.clear()) jest.resetAllMocks() }) jest.setTimeout(10000) export default class CreatePDFTestPage extends TestPage { pager: PagerMethods constructor(state: YearsTaxesState) { super(state) this.pager = new PagerMethods(() => this.rendered().container) } component: ReactElement = ( ) } export const tests = { incompleteData: async ({ pager }: CreatePDFTestPage): Promise => { await waitFor(() => expect(pager.saveButton()).toBeInTheDocument()) userEvent.click(pager.saveButton()) } } describe('CreatePDF Page', () => { const taxpayerComponent = (information: Information = blankState) => new CreatePDFTestPage({ ...blankYearTaxesState, Y2020: information, activeYear: 'Y2020' }) it('should show no data error if no data is entered', async () => { const page = taxpayerComponent() await waitFor(() => expect( page.rendered().queryByText(F1040Error.filingStatusUndefined) ).toBeInTheDocument() ) page.cleanup() }) it('should show filing status error if some data is entered', async () => { const information = arbitraries.forYear(2020).information() await fc.assert( fc.asyncProperty(information, async (info) => { const newInfo: Information = info newInfo.taxPayer.filingStatus = undefined const page = taxpayerComponent(newInfo) await waitFor(() => expect( page .rendered() .queryByText(F1040Error.filingStatusRequirementsNotMet) ).toBeInTheDocument() ) page.cleanup() }) ) }) }) ================================================ FILE: src/tests/components/Menu.test.tsx ================================================ import { screen } from '@testing-library/react' import Menu, { drawerSections } from 'ustaxes/components/Menu' import { renderWithProviders } from 'ustaxes/testUtil' import { Provider } from 'react-redux' import { createWholeStoreUnpersisted } from 'ustaxes/redux/store' import { blankYearTaxesState } from 'ustaxes/redux' const heading = drawerSections[0].title const blankStore = createWholeStoreUnpersisted(blankYearTaxesState) const component = (

) describe('Menu', () => { describe('desktop view', () => { it('renders', () => { renderWithProviders(component) expect(screen.getByText(heading)).toBeInTheDocument() }) }) }) ================================================ FILE: src/tests/components/Questions.test.tsx ================================================ /* eslint @typescript-eslint/no-empty-function: "off" */ import { ReactElement } from 'react' import { fireEvent, render, waitFor } from '@testing-library/react' import { Provider } from 'react-redux' import Questions from 'ustaxes/components/Questions' import { InfoStore, createStoreUnpersisted } from 'ustaxes/redux/store' import { questions } from 'ustaxes/core/data/questions' import { PagerButtons, PagerContext } from 'ustaxes/components/pager' import { Information } from 'ustaxes/core/data' import { blankState } from 'ustaxes/redux/reducer' import TaxesStateMethods from 'ustaxes/redux/TaxesState' afterEach(async () => { await waitFor(() => localStorage.clear()) jest.resetAllMocks() }) jest.mock('redux-persist', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const real = jest.requireActual('redux-persist') // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...real, // eslint-disable-next-line @typescript-eslint/no-unsafe-return persistReducer: jest.fn().mockImplementation((config, reducers) => reducers) } }) describe('Questions', () => { const navButtons = const testComponent = ( info: Information = blankState ): [InfoStore, ReactElement] => { const store = createStoreUnpersisted(info) const component = ( {}, navButtons }}> ) return [store, component] } const cryptoQuestion = questions.find((q) => q.tag === 'CRYPTO') if (cryptoQuestion === undefined) { throw new Error('crypto question undefined') } it('should always show crypto question', async () => { const [, component] = testComponent() const result = render(component) const questionComponent = await result.findAllByText(cryptoQuestion.text) expect(questionComponent[0]).toBeInTheDocument() }) it('should propagate to model', async () => { const [store, component] = testComponent() const result = render(component) const checkBoxes = await result.findAllByRole('checkbox') const save = await result.findByRole('button', { name: /Save/ }) checkBoxes.forEach((b) => fireEvent.click(b)) fireEvent.click(save) await waitFor(() => {}) expect( new TaxesStateMethods(store.getState()).info().questions.CRYPTO ).toEqual(true) }) }) ================================================ FILE: src/tests/components/SpouseAndDependent/Dependent.test.tsx ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ import { waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { store } from 'ustaxes/redux/store' import log from 'ustaxes/core/log' import { SpouseAndDependentTestPage } from './Pages' afterEach(async () => { await waitFor(() => localStorage.clear()) jest.resetAllMocks() }) beforeAll(() => { log.setLevel(log.levels.TRACE) }) jest.setTimeout(10000) jest.mock('redux-persist', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const real = jest.requireActual('redux-persist') // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...real, // eslint-disable-next-line @typescript-eslint/no-unsafe-return persistReducer: jest.fn().mockImplementation((config, reducers) => reducers) } }) describe('Dependents', () => { it('renders an empty dependent list initially', () => { const spouseAndDependent = new SpouseAndDependentTestPage(store.getState()) const { spouse, dependent } = spouseAndDependent // Both spouse and dependent add buttons should appear expect(dependent.addButton()).toBeInTheDocument() expect(spouse.addButton()).toBeInTheDocument() // initial state does not have forms or labels expect(dependent.firstNameField()).not.toBeInTheDocument() expect(dependent.lastNameField()).not.toBeInTheDocument() expect(dependent.ssnField()).not.toBeInTheDocument() spouseAndDependent.cleanup() }) it('renders dependent form element when add button is clicked', async () => { const spouseAndDependent = new SpouseAndDependentTestPage(store.getState()) const { spouse, dependent } = spouseAndDependent await waitFor(() => { expect(spouse.addButton()).toBeInTheDocument() expect(dependent.addButton()).toBeInTheDocument() }) userEvent.click(dependent.addButton()!) const dependentFormLabels = [ 'First Name and Initial', 'Last Name', 'SSN / TIN', 'Relationship to Taxpayer', 'Date of Birth', 'How many months did you live together this year?', 'Is this person a full-time student?' ] // Assert all form labels appear await waitFor(() => { dependentFormLabels.forEach((label) => expect( spouseAndDependent.rendered().getByText(label) ).toBeInTheDocument() ) }) userEvent.click(spouseAndDependent.dependent.closeButton()!) // assert all the labels are now gone await waitFor(() => { dependentFormLabels.forEach((label) => { expect( spouseAndDependent.rendered().queryByText(label) ).not.toBeInTheDocument() }) }) spouseAndDependent.cleanup() }) it('saves multiple dependents', async () => { const spouseAndDependent = new SpouseAndDependentTestPage(store.getState()) const dependent = spouseAndDependent.dependent await waitFor(() => { expect(dependent.addButton()).toBeInTheDocument() }) userEvent.click(dependent.addButton()!) await waitFor(() => { expect(dependent.firstNameField()).toBeInTheDocument() }) expect(dependent.isStudent()).toBeInTheDocument() // add values for each input userEvent.type(dependent.firstNameField()!, 'Charlie') userEvent.type(dependent.lastNameField()!, 'Brown') userEvent.clear(dependent.ssnField()!) userEvent.type(dependent.ssnField()!, '222222222') userEvent.type(dependent.relationField()!, 'Son') userEvent.clear(dependent.dateOfBirthField()!) userEvent.type(dependent.dateOfBirthField()!, '01/01/2007') userEvent.type(dependent.durationField()!, '12') userEvent.click(dependent.isStudent()!) userEvent.click(dependent.saveButton()!) await waitFor(async () => expect( await spouseAndDependent.rendered().findByText('Charlie Brown') ).toBeInTheDocument() ) expect( spouseAndDependent.rendered().getByText('222-22-2222') ).toBeInTheDocument() userEvent.click(dependent.addButton()!) await waitFor(() => expect(dependent.isStudent()).toBeInTheDocument()) userEvent.type(dependent.firstNameField()!, 'Sally') userEvent.type(dependent.lastNameField()!, 'Brown') userEvent.clear(dependent.ssnField()!) userEvent.type(dependent.ssnField()!, '333333333') userEvent.type(dependent.relationField()!, 'Daughter') userEvent.clear(dependent.dateOfBirthField()!) userEvent.type(dependent.dateOfBirthField()!, '03/01/2000') userEvent.type(dependent.durationField()!, '12') userEvent.click(dependent.isStudent()!) userEvent.click(dependent.saveButton()!) await waitFor(async () => { expect( await spouseAndDependent.rendered().findByText('Charlie Brown') ).toBeInTheDocument() }) expect( spouseAndDependent.rendered().getByText('222-22-2222') ).toBeInTheDocument() await waitFor(() => { expect( spouseAndDependent.rendered().getByText('Sally Brown') ).toBeInTheDocument() }) expect( spouseAndDependent.rendered().getByText('333-33-3333') ).toBeInTheDocument() expect(dependent.deleteButtons()).toHaveLength(2) const [deleteCharlie, deleteSally] = dependent.deleteButtons() userEvent.click(deleteSally) await waitFor(() => expect( spouseAndDependent.rendered().queryByText('Sally Brown') ).not.toBeInTheDocument() ) userEvent.click(deleteCharlie) await waitFor(() => expect( spouseAndDependent.rendered().queryByText('Charlie Brown') ).not.toBeInTheDocument() ) spouseAndDependent.cleanup() }) it('saves and edits multiple dependents', async (): Promise => { const spouseAndDependent = new SpouseAndDependentTestPage(store.getState()) const dependent = spouseAndDependent.dependent await waitFor(() => { expect(dependent.addButton()).toBeInTheDocument() }) userEvent.click(dependent.addButton()!) await waitFor(() => expect(dependent.firstNameField()).toBeInTheDocument()) userEvent.type(dependent.firstNameField()!, 'Charlie') userEvent.type(dependent.lastNameField()!, 'Brown') userEvent.clear(dependent.ssnField()!) userEvent.type(dependent.ssnField()!, '222222222') userEvent.type(dependent.relationField()!, 'Son') userEvent.type(dependent.durationField()!, '12') userEvent.clear(dependent.dateOfBirthField()!) userEvent.type(dependent.dateOfBirthField()!, '09/11/2001') userEvent.click(dependent.isStudent()!) userEvent.click(dependent.saveButton()!) await waitFor(async () => expect( await spouseAndDependent.rendered().findByText('Charlie Brown') ).toBeInTheDocument() ) expect( spouseAndDependent.rendered().getByText('222-22-2222') ).toBeInTheDocument() userEvent.click(dependent.addButton()!) await waitFor(() => expect(dependent.firstNameField()).toBeInTheDocument()) await waitFor(() => expect(dependent.dateOfBirthField()).toBeInTheDocument() ) userEvent.type(dependent.firstNameField()!, 'Sally') userEvent.type(dependent.lastNameField()!, 'Brown') userEvent.clear(dependent.ssnField()!) userEvent.type(dependent.ssnField()!, '333333333') userEvent.type(dependent.relationField()!, 'Daughter') userEvent.type(dependent.durationField()!, '12') userEvent.clear(dependent.dateOfBirthField()!) userEvent.type(dependent.dateOfBirthField()!, '08/12/1999') userEvent.click(dependent.isStudent()!) userEvent.click(dependent.saveButton()!) expect( spouseAndDependent.rendered().queryByText('Input is required') ).not.toBeInTheDocument() await waitFor(() => expect(dependent.dateOfBirthErrors()).toHaveLength(0)) await waitFor(async () => { expect( await spouseAndDependent.rendered().findByText('Charlie Brown') ).toBeInTheDocument() expect( spouseAndDependent.rendered().getByText('222-22-2222') ).toBeInTheDocument() expect( spouseAndDependent.rendered().getByText('Sally Brown') ).toBeInTheDocument() expect( spouseAndDependent.rendered().getByText('333-33-3333') ).toBeInTheDocument() }) await waitFor(() => { expect(dependent.editButtons()).toHaveLength(2) }) const editCharlie = dependent.editButtons()[0] userEvent.click(editCharlie) expect(dependent.firstNameField()!.value).toBe('Charlie') expect(dependent.lastNameField()!.value).toBe('Brown') expect(dependent.ssnField()!.value).toBe('222-22-2222') expect(dependent.relationField()!.value).toBe('Son') expect(dependent.durationField()!.value).toBe('12') expect(dependent.firstNameField()!.value).toBe('Charlie') expect(dependent.dateOfBirthField()!.value).toBe('09/11/2001') userEvent.type(dependent.firstNameField()!, '{selectall}{del}Deebo') userEvent.clear(dependent.ssnField()!) userEvent.type(dependent.ssnField()!, '777777777') userEvent.click(dependent.saveButton()!) await spouseAndDependent.rendered().findByText('Deebo Brown') await spouseAndDependent.rendered().findByText('777-77-7777') expect(dependent.deleteButtons()).toHaveLength(2) const [deleteDeebo, deleteSally] = dependent.deleteButtons() userEvent.click(deleteSally) await waitFor(() => expect( spouseAndDependent.rendered().queryByText('Sally Brown') ).not.toBeInTheDocument() ) userEvent.click(deleteDeebo) await waitFor(() => expect( spouseAndDependent.rendered().queryByText('Deebo Brown') ).not.toBeInTheDocument() ) spouseAndDependent.cleanup() }) it('renders appropriate input errors', async () => { const spouseAndDependent = new SpouseAndDependentTestPage(store.getState()) const dependent = spouseAndDependent.dependent await waitFor(() => expect(dependent.addButton()).toBeInTheDocument()) userEvent.click(dependent.addButton()!) const labels = [ 'First Name and Initial', 'Last Name', 'SSN / TIN', 'Relationship to Taxpayer', 'Date of Birth', 'How many months did you live together this year?', 'Is this person a full-time student?' ] // Assert all form labels appear await waitFor(() => { labels.forEach((label) => expect( spouseAndDependent.rendered().getByText(label) ).toBeInTheDocument() ) }) // click the save button with empty inputs userEvent.click(dependent.saveButton()!) // expect six `Input is required` errors await waitFor(() => expect(dependent.requiredErrors()).toHaveLength(6)) userEvent.type(dependent.firstNameField()!, '8675309') userEvent.click(dependent.saveButton()!) await waitFor(() => expect(dependent.requiredErrors()).toHaveLength(5)) userEvent.clear(dependent.firstNameField()!) userEvent.type(dependent.firstNameField()!, 'Booker T') userEvent.click(dependent.saveButton()!) await waitFor(() => { expect(dependent.requiredErrors()).toHaveLength(5) }) userEvent.type(dependent.lastNameField()!, '{selectall}{del}Washington') userEvent.click(dependent.saveButton()!) await waitFor(() => expect(dependent.requiredErrors()).toHaveLength(4)) userEvent.clear(dependent.ssnField()!) userEvent.type(dependent.ssnField()!, '123') userEvent.click(dependent.saveButton()!) await waitFor(async () => expect( await spouseAndDependent .rendered() .findAllByText('Input should be filled with 9 digits') ).toHaveLength(1) ) expect(dependent.requiredErrors()).toHaveLength(3) userEvent.clear(dependent.ssnField()!) userEvent.type(dependent.ssnField()!, '123456789') userEvent.click(dependent.saveButton()!) await waitFor(() => expect( spouseAndDependent .rendered() .queryByText('Input should be filled with 9 digits') ).not.toBeInTheDocument() ) await waitFor(() => expect(dependent.requiredErrors()).toHaveLength(3)) userEvent.type(dependent.relationField()!, '1111') userEvent.click(dependent.saveButton()!) await waitFor(async () => expect( await spouseAndDependent .rendered() .findAllByText('Input should only include letters and spaces') ).toHaveLength(1) ) expect(dependent.requiredErrors()).toHaveLength(2) userEvent.type(dependent.relationField()!, '{selectall}{del}stepchild') userEvent.click(dependent.saveButton()!) await waitFor(() => expect( spouseAndDependent .rendered() .queryByText('Input should only include letters and spaces') ).not.toBeInTheDocument() ) expect(dependent.requiredErrors()).toHaveLength(2) userEvent.clear(dependent.dateOfBirthField()!) userEvent.type(dependent.dateOfBirthField()!, '31/12/2011') userEvent.click(dependent.saveButton()!) await waitFor(() => expect(dependent.requiredErrors()).toHaveLength(1)) await waitFor(() => { const dateErr = spouseAndDependent .rendered() .queryAllByText('Invalid date format') expect(dateErr).toHaveLength(1) }) userEvent.clear(dependent.dateOfBirthField()!) userEvent.type(dependent.dateOfBirthField()!, '01/01/2000') userEvent.click(dependent.saveButton()!) await waitFor(() => expect(spouseAndDependent.rendered().queryByText('Invalid date format')) ) userEvent.type(dependent.durationField()!, 'abcd') userEvent.click(dependent.saveButton()!) await waitFor(() => expect(dependent.requiredErrors()).toHaveLength(1)) userEvent.type(dependent.durationField()!, '{selectall}{del}15') userEvent.click(dependent.saveButton()!) await waitFor(() => expect( spouseAndDependent .rendered() .queryAllByText('Input must be less than or equal to 12') ).toHaveLength(1) ) expect(dependent.requiredErrors()).toHaveLength(0) userEvent.type(dependent.durationField()!, '{selectall}{del}10') userEvent.click(dependent.saveButton()!) await waitFor(() => expect( spouseAndDependent .rendered() .queryByText('Input must be less than or equal to 12') ).not.toBeInTheDocument() ) expect(dependent.requiredErrors()).toHaveLength(0) await spouseAndDependent.rendered().findByText('Booker T Washington') expect( spouseAndDependent.rendered().getByText('123-45-6789') ).toBeInTheDocument() const deleteBooker = dependent.deleteButtons()[0] userEvent.click(deleteBooker) await waitFor(() => expect( spouseAndDependent.rendered().queryByText('Booker T Washington') ).not.toBeInTheDocument() ) spouseAndDependent.cleanup() }) }) ================================================ FILE: src/tests/components/SpouseAndDependent/FilingStatus.test.tsx ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ import log from 'ustaxes/core/log' import * as yarbitraries from '../../arbitraries' import { SpouseAndDependentTestPage } from './Pages' import { filingStatuses, TaxPayer, TaxYear } from 'ustaxes/core/data' import { waitFor } from '@testing-library/react' beforeEach(() => { log.setLevel(log.levels.TRACE) }) jest.mock('redux-persist', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const real = jest.requireActual('redux-persist') // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...real, // eslint-disable-next-line @typescript-eslint/no-unsafe-return persistReducer: jest.fn().mockImplementation((config, reducers) => reducers) } }) jest.setTimeout(10000) describe('FilingStatus', () => { jest.setTimeout(20000) it('should update on year change', async () => { const state = yarbitraries.justOneState() const y1: TaxYear = 'Y2020' const y2: TaxYear = 'Y2021' state.activeYear = y1 const page = new SpouseAndDependentTestPage(state) expect(page.filingStatus.dropdown()).toBeInTheDocument() const checkFs = (tp: TaxPayer) => { if ( tp.filingStatus !== undefined && filingStatuses(tp).includes(tp.filingStatus) ) { expect(page.filingStatus.selected()).toEqual(tp.filingStatus) } else { expect(page.filingStatus.selected()).toEqual('') } } expect(state[y1].taxPayer).not.toBeUndefined() expect(state[y2].taxPayer).not.toBeUndefined() checkFs(state[y1].taxPayer) await page.yearStatus.setYear(y2) await waitFor(() => { expect(page.store.getState().activeYear).toEqual(y2) }) await waitFor(() => { expect(page.yearStatus.yearValue()).toEqual(y2) }) const tp = state[y2].taxPayer await waitFor(() => { checkFs(tp) }) return true }) }) ================================================ FILE: src/tests/components/SpouseAndDependent/Methods.ts ================================================ import { PersonMethods } from '../../common/PersonMethods' import { within } from '@testing-library/react' import DomMethods from '../../common/DomMethods' import { FilingStatus } from 'ustaxes/core/data' export class SpouseMethods extends PersonMethods { addButton = (): HTMLButtonElement | null => within(this.dom()).queryByRole('button', { name: /Add/ }) editButton = (): HTMLButtonElement | null => within(this.dom()).queryByLabelText('edit') } export class DependentMethods extends PersonMethods { relationField = (): HTMLInputElement | null => within(this.dom()).queryByLabelText('Relationship to Taxpayer') durationField = (): HTMLInputElement | null => within(this.dom()).queryByLabelText(/How many months/) isStudent = (): HTMLInputElement | null => within(this.dom()).queryByText('Is this person a full-time student?') addButton = (): HTMLButtonElement | null => within(this.dom()).queryByRole('button', { name: /Add/ }) editButtons = (): HTMLButtonElement[] => within(this.dom()).queryAllByLabelText('edit') } export class FilingStatusMethods extends DomMethods { options = (): FilingStatus[] => within(this.dom()) .getAllByRole('option') .flatMap((x) => { const v = x.getAttribute('value') as FilingStatus | null return v === null ? [] : [v] }) dropdown = (): HTMLSelectElement | null => within(this.dom()).queryByRole('combobox') selected = (): FilingStatus | undefined => this.dropdown()?.value as FilingStatus | undefined } ================================================ FILE: src/tests/components/SpouseAndDependent/Pages.tsx ================================================ import { ReactElement } from 'react' import { AddDependentForm, FilingStatusDropdown, SpouseInfo } from 'ustaxes/components/TaxPayer/SpouseAndDependent' import YearStatusBar from 'ustaxes/components/YearStatusBar' import { YearsTaxesState } from 'ustaxes/redux' import TestPage from 'ustaxes/tests/common/Page' import YearStatusBarMethods from 'ustaxes/tests/common/YearsStatusBarMethods' import { DependentMethods, SpouseMethods, FilingStatusMethods } from './Methods' export class SpouseTestPage extends TestPage { spouse: SpouseMethods constructor(state: YearsTaxesState) { super(state) this.spouse = new SpouseMethods(() => this.rendered().getByTestId('spouse-info') ) } component: ReactElement = (
) } export class SpouseAndDependentTestPage extends TestPage { yearStatus: YearStatusBarMethods spouse: SpouseMethods dependent: DependentMethods filingStatus: FilingStatusMethods constructor(state: YearsTaxesState) { super(state) const testId = (id: string) => (): HTMLElement => this.rendered().getByTestId(id) this.yearStatus = new YearStatusBarMethods(testId('year-status-bar')) this.spouse = new SpouseMethods(testId('spouse-info')) this.dependent = new DependentMethods(testId('add-dependent-form')) this.filingStatus = new FilingStatusMethods( testId('filing-status-dropdown') ) } component: ReactElement = ( <>
) } ================================================ FILE: src/tests/components/SpouseAndDependent/Spouse.test.tsx ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ import { waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { store } from 'ustaxes/redux/store' import log from 'ustaxes/core/log' import { SpouseTestPage } from './Pages' afterEach(async () => { await waitFor(() => localStorage.clear()) jest.resetAllMocks() }) beforeEach(() => { log.setLevel(log.levels.TRACE) }) jest.setTimeout(30000) jest.mock('redux-persist', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const real = jest.requireActual('redux-persist') // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...real, // eslint-disable-next-line @typescript-eslint/no-unsafe-return persistReducer: jest.fn().mockImplementation((config, reducers) => reducers) } }) describe('SpouseInfo', () => { it('renders an `Add` button when no spouse has been added', () => { const spousePage = new SpouseTestPage(store.getState()) const spouseTest = spousePage.spouse // initial state has an add button expect(spouseTest.addButton()).toBeInTheDocument() // initial state does not have any forms or labels expect(spouseTest.firstNameField()).not.toBeInTheDocument() expect(spouseTest.lastNameField()).not.toBeInTheDocument() expect(spouseTest.ssnField()).not.toBeInTheDocument() spousePage.cleanup() }) it('renders form elements when `Add` button is clicked', async () => { const spousePage = new SpouseTestPage(store.getState()) const spouseTest = spousePage.spouse userEvent.click(spouseTest.addButton()!) // forms and labels appear after clicking the add button await waitFor(() => { expect(spouseTest.firstNameField()).toBeInTheDocument() expect(spouseTest.lastNameField()).toBeInTheDocument() expect(spouseTest.ssnField()).toBeInTheDocument() expect(spouseTest.saveButton()).toBeInTheDocument() expect(spouseTest.closeButton()).toBeInTheDocument() }) spousePage.cleanup() }) it('saves and edits a spouse', async () => { const spousePage = new SpouseTestPage(store.getState()) const { spouse } = spousePage await waitFor(() => expect(spouse.addButton()).toBeInTheDocument()) userEvent.click(spouse.addButton()!) // add values for each input userEvent.type(spouse.firstNameField()!, 'Sally K') userEvent.type(spouse.lastNameField()!, 'Ride') await waitFor(() => expect(spouse.dateOfBirthField()).toBeInTheDocument()) userEvent.clear(spouse.dateOfBirthField()!) userEvent.type(spouse.dateOfBirthField()!, '12311988') userEvent.clear(spouse.ssnField()!) userEvent.type(spouse.ssnField()!, '123456789') // click the save button userEvent.click(spouse.saveButton()!) // expect first and last name to be concatenated expect( await spousePage.rendered().findByText('Sally K Ride') ).toBeInTheDocument() // expect ssn to appear with hyphens expect(spousePage.rendered().getByText('123-45-6789')).toBeInTheDocument() userEvent.click(spouse.editButton()!) // expect the edit button to no longer be in the document await waitFor(() => expect(spouse.editButton()).not.toBeInTheDocument()) // assert that the input values match what was entered expect(spouse.firstNameField()!.value).toBe('Sally K') expect(spouse.lastNameField()!.value).toBe('Ride') expect(spouse.ssnField()!.value).toBe('123-45-6789') // delete the old values and add new ones userEvent.type(spouse.firstNameField()!, '{selectall}{del}Fella') userEvent.type(spouse.lastNameField()!, '{selectall}{del}McGee') userEvent.clear(spouse.ssnField()!) userEvent.type(spouse.ssnField()!, '987-65-4321') // click the save button to save the new values userEvent.click(spouse.saveButton()!) // expect the new names to be concatenated and new ssn to appear with hyphens expect( await spousePage.rendered().findByText('Fella McGee') ).toBeInTheDocument() expect(spousePage.rendered().getByText('987-65-4321')).toBeInTheDocument() // click the delete button userEvent.click(spouse.deleteButtons()[0]) // the add button is back expect(spouse.addButton()).toBeInTheDocument() // expect input fields to not be in the document expect(spousePage.rendered().queryAllByRole('textbox')).toHaveLength(0) spousePage.cleanup() }) it('does not save when required fields not completed', async () => { const spousePage = new SpouseTestPage(store.getState()) const { spouse } = spousePage await waitFor(() => expect(spouse.addButton()).toBeInTheDocument()) userEvent.click(spouse.addButton()!) expect(spouse.saveButton()!).toBeInTheDocument() expect(spouse.closeButton()).toBeInTheDocument() // click the save button with empty inputs userEvent.click(spouse.saveButton()!) // expect four `Input is required` errors (including date input) await waitFor(() => expect(spouse.requiredErrors()).toHaveLength(4)) // fill in the first name incorrectly userEvent.type(spouse.firstNameField()!, 'F$LF(#)& ##3') userEvent.click(spouse.saveButton()!) await waitFor(() => expect(spouse.requiredErrors()).toHaveLength(3)) // fill in the first name correctly userEvent.type(spouse.firstNameField()!, '{selectall}{del}Sally K') userEvent.click(spouse.saveButton()!) await waitFor(() => expect(spouse.requiredErrors()).toHaveLength(3)) // add a name with restricted characters userEvent.type(spouse.lastNameField()!, 'R5$%84') userEvent.click(spouse.saveButton()!) await waitFor(() => expect(spouse.requiredErrors()).toHaveLength(2)) // correctly enter a last name userEvent.type(spouse.lastNameField()!, '{selectall}{del}Ride') userEvent.click(spouse.saveButton()!) expect(spouse.requiredErrors()).toHaveLength(2) userEvent.clear(spouse.dateOfBirthField()!) userEvent.type(spouse.dateOfBirthField()!, '12/31/1989') // only ssn error remains expect(spouse.requiredErrors()).toHaveLength(1) // incorrectly enter ssn userEvent.clear(spouse.ssnField()!) userEvent.type(spouse.ssnField()!, '123sc') userEvent.click(spouse.saveButton()!) expect( await spousePage .rendered() .findByText('Input should be filled with 9 digits') ).toBeInTheDocument() // clear ssn and add a valid value userEvent.clear(spouse.ssnField()!) userEvent.type(spouse.ssnField()!, '123456789') userEvent.click(spouse.saveButton()!) userEvent.clear(spouse.dateOfBirthField()!) userEvent.type(spouse.dateOfBirthField()!, '03011989') // expect saved values to be formatted correctly expect(await spousePage.rendered().findByText('Sally K Ride')) expect(spousePage.rendered().getByText('123-45-6789')) // expect ssn error to be gone expect( spousePage.rendered().queryByText('Input should be filled with 9 digits') ).not.toBeInTheDocument() // delete the entry userEvent.click(spouse.deleteButtons()[0]) const inputsAfterDelete = spousePage.rendered().queryAllByRole('textbox') expect(inputsAfterDelete).toHaveLength(0) spousePage.cleanup() }) }) ================================================ FILE: src/tests/components/Taxpayer.test.tsx ================================================ import { waitFor } from '@testing-library/react' import TaxPayer from 'ustaxes/components/TaxPayer' import { Information } from 'ustaxes/core/data' import { blankYearTaxesState, YearsTaxesState } from 'ustaxes/redux' import { blankState } from 'ustaxes/redux/reducer' import { FakePagerProvider, PagerMethods } from '../common/FakePager' import TestPage from '../common/Page' import { PersonMethods } from '../common/PersonMethods' import TaxPayerMethods from '../common/TaxPayerMethods' jest.setTimeout(1000 * 60 * 10) afterEach(async () => { await waitFor(() => localStorage.clear()) jest.resetAllMocks() }) jest.mock('redux-persist', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const real = jest.requireActual('redux-persist') // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...real, // eslint-disable-next-line @typescript-eslint/no-unsafe-return persistReducer: jest.fn().mockImplementation((_, reducers) => reducers) } }) class TaxPayerTestPage extends TestPage { taxPayer: TaxPayerMethods person: PersonMethods pager: PagerMethods constructor(state: YearsTaxesState) { super(state) const dom = () => this.rendered().getByTestId('taxpayer') this.taxPayer = new TaxPayerMethods(dom) this.person = new PersonMethods(dom) this.pager = new PagerMethods(dom) } component = (
) } interface Person { person: PersonMethods } interface Pager { pager: PagerMethods } interface TaxPayer { taxPayer: TaxPayerMethods } export const tests = { incompleteData: async ({ person, pager }: Person & Pager): Promise => { person.setFirstName('Bob') await waitFor(() => expect(pager.saveButton()).toBeInTheDocument()) pager.save() await waitFor(() => expect(person.requiredErrors()).not.toHaveLength(0)) }, checkboxForeignCountryFields: async ( page: TestPage & TaxPayer ): Promise => { expect(page.allFieldNames()).not.toContain('address.province') expect(page.allFieldNames()).toContain('address.zip') page.taxPayer.setIsForeignCountry(true) await waitFor(() => { expect(page.allFieldNames()).toContain('address.province') expect(page.allFieldNames()).not.toContain('address.zip') }) } } describe('Taxpayer', () => { const taxpayerComponent = (information: Information = blankState) => new TaxPayerTestPage({ ...blankYearTaxesState, Y2020: information, activeYear: 'Y2020' }) it('should show errors if incomplete data is entered', async () => { const page = taxpayerComponent() await tests.incompleteData(page) page.cleanup() }) it('checkbox should open foreign country fields', async () => { const page = taxpayerComponent() await tests.checkboxForeignCountryFields(page) page.cleanup() }) }) ================================================ FILE: src/tests/components/income/F1099Info.test.tsx ================================================ import F1099Info from 'ustaxes/components/income/F1099Info' import { fireEvent, screen, waitFor, act } from '@testing-library/react' import { Provider } from 'react-redux' import { InfoStore, createStoreUnpersisted } from 'ustaxes/redux/store' import { PagerButtons, PagerContext } from 'ustaxes/components/pager' import { FilingStatus, Income1099Type, Information, PersonRole, IncomeW2 } from 'ustaxes/core/data' import { blankState } from 'ustaxes/redux/reducer' import userEvent from '@testing-library/user-event' import { renderWithProviders } from 'ustaxes/testUtil' const testW2sSpouse: IncomeW2 = { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.SPOUSE, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, ssWithholding: 444, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 } const testInformationState: Information = { ...blankState, f1099s: [ { payer: 'payer-name', type: Income1099Type.INT, form: { income: 1111111 }, personRole: PersonRole.PRIMARY } ], w2s: [testW2sSpouse], estimatedTaxes: [], realEstate: [], taxPayer: { primaryPerson: { address: { address: '0001', aptNo: '', city: 'AR city', state: 'AR', zip: '1234567' }, firstName: 'payer-first-name', lastName: 'payer-last-name', isTaxpayerDependent: false, role: PersonRole.PRIMARY, ssid: '111111111' }, spouse: { firstName: 'spouse-first-name', isTaxpayerDependent: false, lastName: 'spouse-last-name', role: PersonRole.SPOUSE, ssid: '222222222' }, dependents: [], filingStatus: FilingStatus.MFS }, questions: {}, f1098es: [], stateResidencies: [{ state: 'AL' }], healthSavingsAccounts: [] } describe('F1099Info', () => { afterEach(async () => { await waitFor(() => localStorage.clear()) jest.resetAllMocks() }) const setup = ( info: Information | undefined = blankState ): { store: InfoStore labelTextChange: (labelText: string | RegExp, input: string) => void selectOption: (labelText: string | RegExp, input: string) => void buttonClick: (buttonText: string) => void } => { const store = createStoreUnpersisted(info) const navButtons = renderWithProviders( ) const labelTextChange = (labelText: string | RegExp, input: string) => { act(() => { fireEvent.change(screen.getByLabelText(labelText), { target: { value: input } }) }) } const selectOption = ( labelText: string | RegExp, input: string, index = 0 ) => { act(() => { fireEvent.change(screen.getAllByLabelText(labelText)[index], { target: { value: input } }) }) } const buttonClick = (buttonText: string, index = 0) => { userEvent.click(screen.getAllByText(buttonText)[index]) } return { store, labelTextChange, selectOption, buttonClick } } describe('validations work', () => { it('shows empty error messages', async () => { const { buttonClick } = setup() buttonClick('Add') buttonClick('Save') await waitFor(() => { expect(screen.getAllByText('Make a selection')).toHaveLength(2) expect(screen.getAllByText('Input is required')).toHaveLength(1) }) }) it('Payer name', async () => { const { labelTextChange, buttonClick } = setup() buttonClick('Add') labelTextChange('Enter name of bank, broker firm, or other payer', '') buttonClick('Save') await waitFor(() => expect(screen.getByText('Input is required')).toBeInTheDocument() ) }) it('Form Type', () => { const { selectOption, buttonClick } = setup() buttonClick('Add') selectOption('Form Type', '1099-B') buttonClick('Save') }) it('Recipient', () => { const { selectOption, buttonClick } = setup() buttonClick('Add') selectOption('Recipient', 'John') buttonClick('Save') }) it('saves information', async () => { const { labelTextChange, selectOption, buttonClick } = setup(testInformationState) buttonClick('Add') selectOption('Form Type', '1099-B') labelTextChange( 'Enter name of bank, broker firm, or other payer', 'payer-name' ) selectOption(/Recipient/, 'John') buttonClick('Save') await waitFor(() => { expect(screen.getByText('1099-B')).toBeInTheDocument() }) }) it('updates information', async () => { const { labelTextChange, selectOption, buttonClick } = setup(testInformationState) userEvent.click(screen.getAllByRole('button')[0]) selectOption('Form Type', '1099-B') labelTextChange( 'Enter name of bank, broker firm, or other payer', 'payer-name' ) selectOption(/Recipient/, 'John') buttonClick('Save') await waitFor(() => { expect(screen.getByText('1099-B')).toBeInTheDocument() }) }) }) }) ================================================ FILE: src/tests/components/income/W2JobInfo.test.tsx ================================================ import { fireEvent, screen, render, waitFor, act } from '@testing-library/react' import { Provider } from 'react-redux' import { InfoStore, createStoreUnpersisted } from 'ustaxes/redux/store' import { PagerButtons, PagerContext } from 'ustaxes/components/pager' import { FilingStatus, Income1099Type, PersonRole, IncomeW2, Information } from 'ustaxes/core/data' import { blankState } from 'ustaxes/redux/reducer' import W2JobInfo from 'ustaxes/components/income/W2JobInfo' import userEvent from '@testing-library/user-event' jest.mock('redux-persist', () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const real = jest.requireActual('redux-persist') // eslint-disable-next-line @typescript-eslint/no-unsafe-return return { ...real, persistReducer: jest .fn() // eslint-disable-next-line @typescript-eslint/no-unsafe-return .mockImplementation((_config, reducers) => reducers) } }) const testW2sSpouse: IncomeW2 = { employer: { EIN: '111111111', employerName: 'w2s employer name' }, personRole: PersonRole.SPOUSE, occupation: 'w2s-occupation', state: 'AL', income: 111, medicareIncome: 222, fedWithholding: 333, ssWages: 111, ssWithholding: 444, medicareWithholding: 555, stateWages: 666, stateWithholding: 777 } const testInfo: Information = { ...blankState, f1099s: [ { payer: 'payer-name', type: Income1099Type.INT, form: { income: 1111111 }, personRole: PersonRole.PRIMARY } ], w2s: [testW2sSpouse], estimatedTaxes: [], realEstate: [], taxPayer: { primaryPerson: { address: { address: '0001', aptNo: '', city: 'AR city', state: 'AR', zip: '1234567' }, firstName: 'payer-first-name', lastName: 'payer-last-name', isTaxpayerDependent: false, role: PersonRole.PRIMARY, ssid: '111111111' }, spouse: { firstName: 'spouse-first-name', isTaxpayerDependent: false, lastName: 'spouse-last-name', role: PersonRole.SPOUSE, ssid: '222222222' }, dependents: [], filingStatus: FilingStatus.MFS }, stateResidencies: [{ state: 'AL' }] } const errors = { inputRequired: () => screen.queryAllByText('Input is required'), selectionRequired: () => screen.queryAllByText('Make a selection'), inputWordFormat: () => screen.queryAllByText('Input should only include letters and spaces'), einFormat: () => screen.queryAllByText('Input should be filled with 9 digits'), all: () => { // just a moment } } errors.all = () => [ ...errors.inputRequired(), ...errors.selectionRequired(), ...errors.inputWordFormat(), ...errors.einFormat() ] describe('W2JobInfo', () => { afterEach(async () => { await waitFor(() => localStorage.clear()) jest.resetAllMocks() }) const setup = ( info: Information = blankState ): { store: InfoStore changeByLabelText: (labelText: string | RegExp, input: string) => void selectOption: (labelText: string | RegExp, input: string) => void clickButton: (buttonText: string) => void } => { const store = createStoreUnpersisted(info) const navButtons = render( ) const changeByLabelText = (labelText: string | RegExp, input: string) => { act(() => { fireEvent.change(screen.getByLabelText(labelText), { target: { value: input } }) }) } const selectOption = ( labelText: string | RegExp, input: string, index = 0 ) => { act(() => { fireEvent.change(screen.getAllByLabelText(labelText)[index], { target: { value: input } }) }) } const clickButton = (buttonText: string, index = 0) => { userEvent.click(screen.getAllByText(buttonText)[index]) } return { store, changeByLabelText, selectOption, clickButton } } describe('validations work', () => { it('shows empty error messages', async () => { const { clickButton } = setup() clickButton('Add') clickButton('Save') await waitFor(() => { expect(errors.inputRequired()).toHaveLength(11) expect(errors.selectionRequired()).toHaveLength(2) }) }) it('Employer name can be any string', async () => { const { changeByLabelText, clickButton } = setup() clickButton('Add') changeByLabelText('Employer name', '123') clickButton('Save') await waitFor(() => expect(errors.inputWordFormat()).toHaveLength(0)) }) it('Employers Identification Number', async () => { const { changeByLabelText, clickButton } = setup() clickButton('Add') changeByLabelText(/Employer's Identification Number/, '123') clickButton('Save') await waitFor(() => expect(errors.einFormat()).toHaveLength(1)) }) it('Occupation', async () => { const { changeByLabelText, clickButton } = setup() clickButton('Add') changeByLabelText('Occupation', '123') clickButton('Save') await waitFor(() => expect(errors.inputWordFormat()).toHaveLength(1)) }) }) it('shows spouse W2 message', () => { setup(testInfo) expect( screen.getByText(/Filing status is set to Married Filing Separately./) ) }) it('saves information', async () => { const { changeByLabelText, selectOption, clickButton } = setup(testInfo) clickButton('Add') changeByLabelText('Employer name', 'test employer') changeByLabelText(/Employer's Identification Number/, '111111111') changeByLabelText('Occupation', 'test occupation') changeByLabelText(/Wages, tips, other compensation/, '123456') changeByLabelText(/Federal income tax withheld/, '3333') changeByLabelText(/Social security wages/, '12345') changeByLabelText(/Social security tax withheld/, '4444') changeByLabelText(/Medicare Income/, '5555') changeByLabelText(/Medicare tax withheld/, '6666') changeByLabelText(/State wages, tips, etc/, '7777') changeByLabelText(/State income tax/, '8888') selectOption('Employee', 'PRIMARY') selectOption(/State/, 'AL') clickButton('Save') await waitFor(() => { expect(screen.getByText('test employer')).toBeInTheDocument() expect(screen.getByText('$123,456')).toBeInTheDocument() }) }) it('removes item of list', async () => { if (testW2sSpouse.employer?.employerName) { setup(testInfo) expect( screen.getByText(testW2sSpouse.employer.employerName) ).toBeInTheDocument() userEvent.click(screen.getAllByRole('button')[1]) await waitFor(() => expect(screen.queryByText('w2s employer name')).not.toBeInTheDocument() ) } }) it('sets current information when editing', () => { setup(testInfo) userEvent.click(screen.getAllByRole('button')[0]) expect(screen.getByLabelText('Employer name')).toHaveValue( testW2sSpouse.employer?.employerName ) expect( screen.getByLabelText(/Employer's Identification Number/) ).toHaveValue( (testW2sSpouse.employer?.EIN?.slice(0, 2) ?? '') + '-' + (testW2sSpouse.employer?.EIN?.slice(2) ?? '') ) expect(screen.getByLabelText('Occupation')).toHaveValue( testW2sSpouse.occupation ) expect( screen.getByLabelText(/Wages, tips, other compensation/) ).toHaveValue(testW2sSpouse.income.toLocaleString('en-US')) expect(screen.getByLabelText(/Social security tax withheld/)).toHaveValue( testW2sSpouse.ssWithholding.toLocaleString('en-US') ) expect(screen.getByLabelText(/Medicare Income/)).toHaveValue( testW2sSpouse.medicareIncome.toLocaleString('en-US') ) expect(screen.getByLabelText(/Medicare tax withheld/)).toHaveValue( testW2sSpouse.medicareWithholding.toLocaleString('en-US') ) expect(screen.getAllByLabelText(/State/)[0]).toHaveValue( testW2sSpouse.state ) expect(screen.getByLabelText(/State wages, tips, etc/)).toHaveValue( testW2sSpouse.stateWages?.toLocaleString('en-US') ) expect(screen.getByLabelText(/State income tax/)).toHaveValue( testW2sSpouse.stateWithholding?.toLocaleString('en-US') ) expect(screen.getByLabelText('Employee')).toHaveValue( testW2sSpouse.personRole ) }) it('updates information', async () => { const { changeByLabelText, selectOption, clickButton } = setup(testInfo) userEvent.click(screen.getAllByRole('button')[0]) changeByLabelText('Employer name', 'updated employer name') changeByLabelText(/Employer's Identification Number/, '999999999') changeByLabelText('Occupation', 'updated occupation') changeByLabelText(/Wages, tips, other compensation/, '8888') changeByLabelText(/Federal income tax withheld/, '7777') changeByLabelText(/Social security tax withheld/, '6666') changeByLabelText(/Medicare Income/, '5555') changeByLabelText(/Medicare tax withheld/, '4444') changeByLabelText(/State wages, tips, etc/, '3333') changeByLabelText(/State income tax/, '2222') selectOption('Employee', 'SPOUSE') selectOption(/State/, 'AR') clickButton('Save') await waitFor(() => { expect(screen.getByText('updated employer name')).toBeInTheDocument() expect(screen.getByText('$8,888')).toBeInTheDocument() }) }) }) ================================================ FILE: src/tests/testdata/transactions_test1.csv ================================================ portfolio,trade id,product,side,created at,size,size unit,price,fee,total,price/fee/total unit,,Notes default,1,DEF,BUY,2016-01-01,21,DEF,10,0,210,USD,,extra data default,2,ABC,BUY,2018-03-17T07:35:58.772Z,23.053,ABC,49.9,0,1150.3447,USD,, default,3,ABC,BUY,2018-03-17T07:35:58.772Z,55,ABC,49.9,0,2744.5,USD,, default,4,DEF,BUY,2018-03-17T07:35:58.772Z,119,DEF,49.9,0,5938.1,USD,, default,5,ABC,BUY,2018-03-17T07:35:58.772Z,0.32321,ABC,41.5,0,13.413215,USD,, default,6,ABC,SELL,2018-03-17T07:35:58.772Z,21.2,ABC,52.25,0,1107.7,USD,, default,7,ABC,BUY,2018-03-17T07:35:58.772Z,88,ABC,48.28,0,4248.64,USD,, default,8,ABC,BUY,2018-03-17T07:35:58.772Z,48.212,ABC,46.12,0,2223.53744,USD,, default,9,ABC,BUY,2018-03-17T07:35:58.772Z,201.8,ABC,45.96,0,9274.728,USD,, default,10,ABC,SELL,2018-03-17T07:35:58.772Z,162.91733152,ABC,51.11,0,8326.7048139872,USD,, default,11,ABC,BUY,2018-03-17T07:35:58.772Z,101.12323123,ABC,48.09,0,4863.0161898507,USD,, default,12,ABC,BUY,2018-03-17T07:35:58.772Z,110.7132,ABC,40,0,4428.528,USD,, default,13,ABC,SELL,2018-03-17T07:35:58.772Z,34.18880027,ABC,30,0,1025.6640081,USD,, default,14,ABC,SELL,2018-03-17T07:35:58.772Z,0.6012,ABC,80,0,48.096,USD,, default,15,ABC,SELL,2018-03-17T07:35:58.772Z,33.438906,ABC,88,0,2942.623728,USD,, default,16,ABC,SELL,2018-03-17T07:35:58.772Z,31.62909373,ABC,90,0,2846.6184357,USD,, default,17,ABC,SELL,2018-03-17T07:35:58.772Z,13.3389495,ABC,99.993,0,1333.8015773535,USD,, default,18,ABC,SELL,2018-03-17T07:35:58.772Z,1.888381,ABC,110.21,0,208.11847001,USD,, default,19,ABC,SELL,2018-03-17T07:35:58.772Z,5.8608,ABC,110.21,0,645.918768,USD,, default,20,ABC,SELL,2018-03-17T07:35:58.772Z,0.26699,ABC,100.23,0,26.7604077,USD,, default,21,ABC,SELL,2018-03-17T07:35:58.772Z,49.171473,ABC,90.12,0,4431.33314676,USD,, default,22,ABC,SELL,2021-01-03,1.0065098,ABC,88.23,0,88.804359654,USD,, default,23,ABC,SELL,2021-01-04,23.6808967,ABC,83.32,0.24,1973.092313044,USD,, default,24,ABC,SELL,2021-01-05,0.4884,ABC,89.344,1,43.6356096,USD,, default,25,ABC,SELL,2021-01-06,200,ABC,80.132,0.3,16026.4,USD,, default,26,ABC,SELL,2021-01-07,21.5116,ABC,60.34,2,1298.009944,USD,, default,27,ABC,BUY,2021-01-08,28.91691796,ABC,55.23,0.134,1597.0813789308,USD,, default,28,ABC,BUY,2021-01-09,54.993,ABC,50.123,0.1234,2756.414139,USD,, default,29,ABC,BUY,2021-01-10,1.011648,ABC,40.23,0.123,40.69859904,USD,, default,30,ABC,BUY,2021-01-11,28.20255404,ABC,35.23,1.13,993.5759788292,USD,, default,31,ABC,BUY,2021-01-12,122.24241,ABC,31.31,0.341,3827.4098571,USD,, default,32,ABC,BUY,2021-01-13,266.72692021,ABC,29.43,0.2134,7849.7732617803,USD,, default,33,ABC,BUY,2021-01-14,17.56857139,ABC,26.2314,0.51,460.848223559646,USD,, default,34,ABC,BUY,2021-01-15,22.62554838,ABC,22.231,0.23,502.98856603578,USD,, default,35,ABC,BUY,2021-01-16,29.92998023,ABC,19.23,0.43,575.5535198229,USD,, default,36,ABC,BUY,2021-01-17,45.22658664,ABC,12.1234,0.123,548.300000471376,USD,, default,24,ABC,SELL,2021-01-15,28.91691796,ABC,89.344,4,2583.55311821824,USD,, default,25,ABC,SELL,2021-01-16,54.993,ABC,80.132,1.3,4406.699076,USD,, default,26,DEF,SELL,2021-01-17,1.011648,DEF,60.34,0,61.04284032,USD,, ================================================ FILE: src/tests/testdata/transactions_test2_error.csv ================================================ portfolio,trade id,product,side,created at,size,size unit,price,fee,total,price/fee/total unit,,Notes default,1,DEF,BUY,2016-01-01,21,DEF,10,0,210,USD,,extra data default,2,ABC,BUY,2018-03-17T07:35:58.772Z,23.053,ABC,49.9,0,1150.3447,USD,, default,3,ABC,BUY,2018-03-17T07:35:58.772Z,55,ABC,49.9,0,2744.5,USD,, default,4,DEF,BUY,2018-03-17T07:35:58.772Z,119,DEF,49.9,0,5938.1,USD,, default,5,ABC,BUY,2018-03-17T07:35:58.772Z,0.32321,ABC,41.5,0,13.413215,USD,, default,6,ABC,SELL,2018-03-17T07:35:58.772Z,21.2,ABC,52.25,0,1107.7,USD,, default,7,ABC,BUY,2018-03-17T07:35:58.772Z,88,ABC,48.28,0,4248.64,USD,, default,8,ABC,BUY,2018-03-17T07:35:58.772Z,48.212,ABC,46.12,0,2223.53744,USD,, default,9,ABC,BUY,2018-03-17T07:35:58.772Z,201.8,ABC,45.96,0,9274.728,USD,, default,10,ABC,SELL,2018-03-17T07:35:58.772Z,162.91733152,ABC,51.11,0,8326.7048139872,USD,, default,11,ABC,BUY,2018-03-17T07:35:58.772Z,101.12323123,ABC,48.09,0,4863.0161898507,USD,, default,12,ABC,BUY,2018-03-17T07:35:58.772Z,110.7132,ABC,40,0,4428.528,USD,, default,13,ABC,SELL,2018-03-17T07:35:58.772Z,34.18880027,ABC,30,0,1025.6640081,USD,, default,14,ABC,SELL,2018-03-17T07:35:58.772Z,0.6012,ABC,80,0,48.096,USD,, default,15,ABC,SELL,2018-03-17T07:35:58.772Z,33.438906,ABC,88,0,2942.623728,USD,, default,16,ABC,SELL,2018-03-17T07:35:58.772Z,31.62909373,ABC,90,0,2846.6184357,USD,, default,17,ABC,SELL,2018-03-17T07:35:58.772Z,13.3389495,ABC,99.993,0,1333.8015773535,USD,, default,18,ABC,SELL,2018-03-17T07:35:58.772Z,1.888381,ABC,110.21,0,208.11847001,USD,, default,19,ABC,SELL,2018-03-17T07:35:58.772Z,5.8608,ABC,110.21,0,645.918768,USD,, default,20,ABC,SELL,2018-03-17T07:35:58.772Z,0.26699,ABC,100.23,0,26.7604077,USD,, default,21,ABC,SELL,2018-03-17T07:35:58.772Z,49.171473,ABC,90.12,0,4431.33314676,USD,, default,22,ABC,SELL,2021-01-03,1.0065098,ABC,88.23,0,88.804359654,USD,, default,23,ABC,SELL,2021-01-04,23.6808967,ABC,83.32,0,1973.092313044,USD,, default,24,ABC,SELL,2021-01-05,0.4884,ABC,89.344,0,43.6356096,USD,, default,25,ABC,SELL,2021-01-06,200,ABC,80.132,0,16026.4,USD,, default,26,Q,SELL,2021-01-07,21.5116,ABC,60.34,0,1298.009944,USD,, default,27,ABC,BUY,2021-01-08,28.91691796,ABC,55.23,0,1597.0813789308,USD,, default,28,ABC,BUY,2021-01-09,54.993,ABC,50.123,0,2756.414139,USD,, default,29,ABC,BUY,2021-01-10,1.011648,ABC,40.23,0,40.69859904,USD,, default,30,ABC,BUY,2021-01-11,28.20255404,ABC,35.23,0,993.5759788292,USD,, default,31,ABC,BUY,2021-01-12,122.24241,ABC,31.31,0,3827.4098571,USD,, default,32,ABC,BUY,2021-01-13,266.72692021,ABC,29.43,0,7849.7732617803,USD,, default,33,ABC,BUY,2021-01-14,17.56857139,ABC,26.2314,0,460.848223559646,USD,, default,34,ABC,BUY,2021-01-15,22.62554838,ABC,22.231,0,502.98856603578,USD,, default,35,ABC,BUY,2021-01-16,29.92998023,ABC,19.23,0,575.5535198229,USD,, default,36,ABC,BUY,2021-01-17,45.22658664,ABC,12.1234,0,548.300000471376,USD,, default,24,ABC,SELL,2021-01-15,28.91691796,ABC,89.344,0,2583.55311821824,USD,, default,25,ABC,SELL,2021-01-16,54.993,ABC,80.132,0,4406.699076,USD,, default,26,DEF,SELL,2021-01-17,1.011648,DEF,60.34,0,61.04284032,USD,, ================================================ FILE: src/tests/transactions/Transactions.test.tsx ================================================ import * as arbitraries from './arbitraries' import fc, { Arbitrary } from 'fast-check' import { Portfolio, Transaction, processTransaction, Security } from 'ustaxes/data/transactions' describe('Transactions', () => { test('For any portfolio, selling a security not held throws', () => { const testArb: Arbitrary<[Security, Portfolio]> = arbitraries .securities({ minLength: 2 }) .chain((securities) => arbitraries .portfolio(securities.slice(1)) .map((portfolio) => [securities[0], portfolio]) ) fc.assert( fc.property( testArb, arbitraries.transaction(), ([newSecurity, portfolio], transaction) => { const applyTransaction: Transaction = { ...transaction, side: 'SELL', security: newSecurity } expect(() => processTransaction(portfolio, applyTransaction)).toThrow( new Error('Transaction list failed') ) } ) ) }) test('For any portfolio, buying anything appends one position to the portfolio', () => { fc.assert( fc.property( arbitraries.portfolio(), arbitraries.transaction(), (portfolio, transaction) => { const purchase: Transaction = { ...transaction, side: 'BUY' } const newPortfolio = processTransaction(portfolio, purchase) const dropOne = { ...newPortfolio, positions: newPortfolio.positions.slice( 0, newPortfolio.positions.length - 1 ) } const last = newPortfolio.positions[newPortfolio.positions.length - 1] // Portfolio before buy is unaffected expect(portfolio).toEqual(dropOne) // Buy adds a position equivalent to buying in an empty portfolio expect(processTransaction({ positions: [] }, purchase)).toEqual({ positions: [last] }) } ) ) }) const buyTransactions: Arbitrary = arbitraries .securities() .chain((securities) => fc.array( arbitraries.transaction(securities).map((t) => ({ ...t, side: 'BUY' })) ) ) // Add a strictly smaller list of the same transactions changed to sells. const buysAndSells: Arbitrary<[Transaction[], Transaction[]]> = buyTransactions.chain((buys) => fc .subarray(buys.map((buy) => ({ ...buy, side: 'SELL' }))) .map((sells) => [buys, sells]) ) const buysAndScaledSells: Arbitrary<[Transaction[], Transaction[]]> = buysAndSells.chain(([buys, sells]) => { return fc .tuple(fc.nat({ max: 1000 }), fc.float({ min: 0.2, max: 1.0 })) .map(([numDays, scale]) => [ buys, sells.map((t) => { const newDate = new Date(t.date) newDate.setDate(newDate.getDate() + numDays) return { ...t, quantity: Math.floor(t.quantity * scale), date: newDate.toISOString().slice(0, 10) } }) ]) }) const transactions: Arbitrary = buysAndScaledSells.map( ([buys, sells]) => [...buys, ...sells].sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() ) ) test('For any (valid) list of transactions, the final portfolio has the same sum of quanities as the transaction list', () => { fc.assert( fc.property(transactions, (transactions) => { const portfolio = transactions.reduce( (p, t) => processTransaction(p, t), { positions: [] } ) const transactionCounts = transactions .map<[string, number]>((t) => [ t.security.name, t.side === 'BUY' ? t.quantity : -t.quantity ]) .reduce< Partial<{ [name: string]: number }> >( (acc, [name, quantity]) => ({ ...acc, [name]: (acc[name] ?? 0) + quantity }), {} ) const portfolioCounts = portfolio.positions .filter((p) => p.closeDate === undefined) .reduce< Partial<{ [name: string]: number }> >( (acc, p) => ({ ...acc, [p.security.name]: (acc[p.security.name] ?? 0) + p.quantity }), {} ) expect(transactionCounts).toEqual(portfolioCounts) }) ) }) test('For any (valid) list of transactions, the final portfolio has the same sum of buys and sells', () => { fc.assert( fc.property(transactions, (transactions) => { const portfolio = transactions.reduce( (p, t) => processTransaction(p, t), { positions: [] } ) const transactionCounts = transactions.reduce< Partial<{ [name: string]: { basis: number; proceeds: number } }> >( (acc, t) => ({ ...acc, [t.security.name]: { basis: (acc[t.security.name]?.basis ?? 0) + (t.side === 'BUY' ? t.quantity * t.price : 0), proceeds: (acc[t.security.name]?.proceeds ?? 0) + (t.side === 'SELL' ? t.quantity * t.price : 0) } }), {} ) const portfolioCounts = portfolio.positions.reduce< Partial<{ [name: string]: { basis: number; proceeds: number } }> >( (acc, p) => ({ ...acc, [p.security.name]: { basis: (acc[p.security.name]?.basis ?? 0) + p.price * p.quantity, proceeds: (acc[p.security.name]?.proceeds ?? 0) + (p.closePrice ?? 0) * p.quantity } }), {} ) expect(transactionCounts).toEqual(portfolioCounts) }) ) }) }) ================================================ FILE: src/tests/transactions/arbitraries.ts ================================================ import fc, { Arbitrary } from 'fast-check' import { Portfolio, Position, Security, Side, Transaction } from 'ustaxes/data/transactions' export const security = (): Arbitrary => fc.string({ minLength: 1, maxLength: 5 }).map((name) => ({ name })) export const securities = (config = { minLength: 1 }): Arbitrary => fc .set(fc.string({ minLength: 1, maxLength: 5 }), { minLength: config.minLength }) .map((names) => names.map((name) => ({ name }))) const dateStr = (): Arbitrary => fc .date({ min: new Date('2018-01-01'), max: new Date('2022-12-31') }) .map((d) => d.toISOString().substring(0, 10)) export const transaction = (securities?: Security[]): Arbitrary => fc .tuple(fc.float({ min: 0 }), fc.integer({ min: 1, max: 1000 })) .chain(([price, quantity]) => fc .tuple( securities === undefined ? security() : fc.constantFrom(...securities), dateStr(), fc.constantFrom('BUY', 'SELL'), fc.float({ min: 0, max: (quantity * price) / 2 }) ) .map(([security, date, side, fee]) => ({ security, date, side, price, quantity, fee })) ) export const position = (securities?: Security[]): Arbitrary => fc .tuple( fc.nat(), fc.float({ min: 0 }), fc.float({ min: 0 }), fc.oneof(dateStr(), fc.constant(undefined)) ) .chain(([quantity, price, closePrice, closeDate]) => fc .tuple( securities === undefined ? security() : fc.constantFrom(...securities), dateStr(), fc.float({ min: 0, max: (quantity * price) / 2 }), fc.float({ min: 0, max: (quantity * closePrice) / 2 }) ) .map(([security, openDate, openFee, closeFee]) => ({ security, quantity, price, openDate, closeDate, closePrice: closeDate === undefined ? undefined : closePrice, openFee, closeFee: closeDate === undefined ? undefined : closeFee })) ) export const portfolio = (securities?: Security[]): Arbitrary => fc .array(position(securities)) .map((positions) => positions.sort( (a, b) => new Date(a.openDate).getTime() - new Date(b.openDate).getTime() ) ) .map((positions) => ({ positions })) ================================================ FILE: src-tauri/.gitignore ================================================ # Generated by Cargo # will have compiled files and executables /target/ WixTools # These are backup files generated by rustfmt **/*.rs.bk config.json bundle.json ================================================ FILE: src-tauri/Cargo.toml ================================================ [package] name = "us-taxes" version = "0.1.21" description = "UsTaxes is an open source webapp for filing US federal income tax. All tax calculations are performed in the browser, so no personal information is stored on external servers!" license = "" repository = "" default-run = "us-taxes" edition = "2018" build = "src/build.rs" [build-dependencies] tauri-build = { version = "1.0.0-rc.5", features = [] } [dependencies] serde_json = "1.0.85" serde = { version = "1.0.145", features = [ "derive" ] } tauri = { version = "1.0.0-rc.6", features = ["api-all"] } [features] custom-protocol = [ "tauri/custom-protocol" ] default = [ "custom-protocol" ] [[bin]] name = "us-taxes" path = "src/main.rs" ================================================ FILE: src-tauri/rustfmt.toml ================================================ max_width = 100 hard_tabs = false tab_spaces = 2 newline_style = "Auto" use_small_heuristics = "Default" reorder_imports = true reorder_modules = true remove_nested_parens = true edition = "2018" merge_derives = true use_try_shorthand = false use_field_init_shorthand = false force_explicit_abi = true ================================================ FILE: src-tauri/src/build.rs ================================================ use tauri_build::{try_build, Attributes, WindowsAttributes}; fn main() { if let Err(error) = try_build( Attributes::new() .windows_attributes(WindowsAttributes::new().window_icon_path("icons/favicon.ico")), ) { panic!("error found during tauri-build: {}", error); } } ================================================ FILE: src-tauri/src/cmd.rs ================================================ use serde::Deserialize; #[derive(Deserialize)] #[serde(tag = "cmd", rename_all = "camelCase")] pub enum Cmd { // your custom commands // multiple arguments are allowed // note that rename_all = "camelCase": you need to use "myCustomCommand" on JS LocalLog { log: String }, } ================================================ FILE: src-tauri/src/main.rs ================================================ #![cfg_attr( all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows" )] fn main() { let result = tauri::Builder::default() .run(tauri::generate_context!()); println!("{:?}", result) } ================================================ FILE: src-tauri/tauri.conf.json ================================================ { "build": { "distDir": "../build", "devPath": "http://localhost:3000", "beforeDevCommand": "npm run start", "beforeBuildCommand": "npm run build", "withGlobalTauri": true }, "tauri": { "bundle": { "active": true, "targets": "all", "identifier": "ustaxes", "icon": [ "./icons/favicon.ico" ], "resources": [], "externalBin": [], "copyright": "", "category": "DeveloperTool", "shortDescription": "", "longDescription": "", "deb": { "depends": [] }, "macOS": { "frameworks": [], "exceptionDomain": "", "signingIdentity": null, "entitlements": null, "minimumSystemVersion": "" }, "windows": { "certificateThumbprint": null, "digestAlgorithm": "sha256", "timestampUrl": "" } }, "allowlist": { "all": true }, "windows": [ { "title": "US Taxes", "width": 800, "height": 600, "resizable": true, "fullscreen": false } ], "security": { "csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline' 'self'" } } } ================================================ FILE: tsconfig.json ================================================ { "extends": "./tsconfig.path.json", "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "typeRoots": [ "src/customTypes", "node_modules/@types" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "strictNullChecks": true, "jsx": "react-jsx" }, "include": [ "src/**/*", "scripts" ], "exclude": [ "**/node_modules", "**/.*/" ], "ts-node": { "compilerOptions": { "module": "commonjs" } } } ================================================ FILE: tsconfig.path.json ================================================ { "compilerOptions": { "baseUrl": ".", "paths": { "ustaxes/*": ["src/*"] } } }