├── .gitignore ├── LICENSE ├── README.md ├── src ├── .editorconfig ├── .gitignore ├── BackEnd │ ├── BusinessLogic │ │ ├── AccountBusinessLogic.cs │ │ ├── AccountingReportBusinessLogic.cs │ │ ├── BusinessLogicResponse.cs │ │ ├── ConfigurationExtensions.cs │ │ ├── CustomerBusinessLogic.cs │ │ ├── EmployeeBusinessLogic.cs │ │ ├── ErrorType.cs │ │ ├── IAccountBusinessLogic.cs │ │ ├── IAccountingReportBusinessLogic.cs │ │ ├── IBusinessLogic.cs │ │ ├── IBusinessLogicFacade.cs │ │ ├── ICustomerBusinessLogic.cs │ │ ├── IEmployeeBusinessLogic.cs │ │ ├── IInvoiceBusinessLogic.cs │ │ ├── IJournalEntryBusinessLogic.cs │ │ ├── ILedgerBusinessLogic.cs │ │ ├── IPaymentBusinessLogic.cs │ │ ├── IPaymentFacade.cs │ │ ├── IProductBusinessLogic.cs │ │ ├── ITenantBusinessLogic.cs │ │ ├── ITimeActivityBusinessLogic.cs │ │ ├── ITimeZoneBusinessLogic.cs │ │ ├── InvoiceBusinessLogic.cs │ │ ├── JournalEntryBusinessLogic.cs │ │ ├── LedgerBusinessLogic.cs │ │ ├── PaymentBusinessLogic.cs │ │ ├── PaymentFacade.cs │ │ ├── ProductBusinessLogic.cs │ │ ├── TenantBusinessLogic.cs │ │ ├── TimeActivityBusinessLogic.cs │ │ └── TimeZoneBusinessLogic.cs │ ├── Configuration │ │ └── OptionsHelper.cs │ ├── Controllers │ │ ├── AuthenticationController.cs │ │ ├── BootstrapController.cs │ │ ├── CustomerController.cs │ │ ├── EmployeeController.cs │ │ ├── ErrorController.cs │ │ ├── ExportDownloadController.cs │ │ ├── InvoiceController.cs │ │ ├── JournalEntryController.cs │ │ ├── LedgerController.cs │ │ ├── LookupsController.cs │ │ ├── PaymentController.cs │ │ ├── ProductController.cs │ │ ├── TenantController.cs │ │ ├── TestErrorsController.cs │ │ ├── TimeActivityController.cs │ │ ├── UserInfoController.cs │ │ └── VersionController.cs │ ├── DashAccountingSystemV2.BackEnd.csproj │ ├── Data │ │ ├── ApplicationDbContext.cs │ │ └── Migrations │ │ │ ├── 20240706124033_FullSchema.Designer.cs │ │ │ ├── 20240706124033_FullSchema.cs │ │ │ └── ApplicationDbContextModelSnapshot.cs │ ├── Extensions │ │ ├── ClaimsPrincipalExtensions.cs │ │ ├── Constants.cs │ │ ├── ControllerExtensions.cs │ │ ├── DateTimeExtensions.cs │ │ ├── DecimalExtensions.cs │ │ ├── EnumerationExtensions.cs │ │ ├── IEnumerableExtensions.cs │ │ ├── IntegerExtensions.cs │ │ ├── ObjectExtensions.cs │ │ ├── PaginationExtensions.cs │ │ ├── StringExtensions.cs │ │ └── TimeSpanExtensions.cs │ ├── Models │ │ ├── Account.cs │ │ ├── AccountBalanceDto.cs │ │ ├── AccountSubType.cs │ │ ├── AccountType.cs │ │ ├── AccountWithBalanceDto.cs │ │ ├── Address.cs │ │ ├── AddressType.cs │ │ ├── AmountType.cs │ │ ├── ApplicationRole.cs │ │ ├── ApplicationUser.cs │ │ ├── AssetType.cs │ │ ├── BalanceSheetReportDto.cs │ │ ├── Country.cs │ │ ├── Customer.cs │ │ ├── DateRange.cs │ │ ├── Employee.cs │ │ ├── Entity.cs │ │ ├── EntityType.cs │ │ ├── Gender.cs │ │ ├── Invoice.cs │ │ ├── InvoiceLineItem.cs │ │ ├── InvoiceLineItemTimeActivity.cs │ │ ├── InvoicePayment.cs │ │ ├── InvoiceStatus.cs │ │ ├── InvoiceTerms.cs │ │ ├── JournalEntry.cs │ │ ├── JournalEntryAccount.cs │ │ ├── KnownAccountSubType.cs │ │ ├── KnownAccountType.cs │ │ ├── LedgerReportAccountDto.cs │ │ ├── PagedResult.cs │ │ ├── Pagination.cs │ │ ├── PaginationBase.cs │ │ ├── Payment.cs │ │ ├── PaymentCreationRequestDto.cs │ │ ├── PaymentMethod.cs │ │ ├── Product.cs │ │ ├── ProductCategory.cs │ │ ├── ProductType.cs │ │ ├── ProfitAndLossReportDto.cs │ │ ├── ReconciliationReport.cs │ │ ├── Region.cs │ │ ├── SortDirection.cs │ │ ├── Sorting.cs │ │ ├── Tenant.cs │ │ ├── TimeActivity.cs │ │ ├── TimeActivityDetailsReportDto.cs │ │ ├── TimeZone.cs │ │ └── TransactionStatus.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Repositories │ │ ├── AccountRepository.cs │ │ ├── ConfigurationExtensions.cs │ │ ├── CustomerRepository.cs │ │ ├── EmployeeRepository.cs │ │ ├── IAccountRepository.cs │ │ ├── ICustomerRepository.cs │ │ ├── IEmployeeRepository.cs │ │ ├── IInvoiceRepository.cs │ │ ├── IInvoiceTermsRepository.cs │ │ ├── IJournalEntryRepository.cs │ │ ├── IPaymentRepository.cs │ │ ├── IProductRepository.cs │ │ ├── ISharedLookupRepository.cs │ │ ├── ITenantRepository.cs │ │ ├── ITimeActivityRepository.cs │ │ ├── ITimeZoneRepository.cs │ │ ├── InvoiceRepository.cs │ │ ├── InvoiceTermsRepository.cs │ │ ├── JournalEntryRepository.cs │ │ ├── PaymentRepository.cs │ │ ├── ProductRepository.cs │ │ ├── SharedLookupRepository.cs │ │ ├── TenantRepository.cs │ │ ├── TimeActivityRepository.cs │ │ └── TimeZoneRepository.cs │ ├── Security │ │ ├── Authentication │ │ │ ├── ApplicationAuthenticationHandler.cs │ │ │ └── ApplicationUserManager.cs │ │ ├── Authorization │ │ │ ├── ApiAuthorizeAttribute.cs │ │ │ ├── ApplicationClaimsPrincipalFactory.cs │ │ │ └── ApplicationRoleManager.cs │ │ ├── Constants.cs │ │ └── ExportDownloads │ │ │ ├── ConfigurationExtensions.cs │ │ │ ├── ExportDownloadAuthenticationTicket.cs │ │ │ ├── ExportDownloadSecurityTokenAuthenticationHandler.cs │ │ │ ├── ExportDownloadSecurityTokenAuthenticationHandlerOptions.cs │ │ │ ├── ExportDownloadSecurityTokenService.cs │ │ │ └── IExportDownloadSecurityTokenService.cs │ ├── Services │ │ ├── Caching │ │ │ ├── ConfigurationExtensions.cs │ │ │ ├── Constants.cs │ │ │ ├── GeneralPurposeLocalMemoryCache.cs │ │ │ └── IExtendedDistributedCache.cs │ │ ├── Export │ │ │ ├── ConfigurationExtensions.cs │ │ │ ├── DataExporterFactory.cs │ │ │ ├── DataExporters │ │ │ │ ├── BalanceSheetReportExcelExporter.cs │ │ │ │ ├── InvoicePdfExporter.cs │ │ │ │ └── ProfitAndLossReportExcelExporter.cs │ │ │ ├── ExcelTemplates │ │ │ │ ├── BalanceSheetReport.xlsx │ │ │ │ └── ProfitAndLossReport.xlsx │ │ │ ├── ExportFormat.cs │ │ │ ├── ExportRequestParameters.cs │ │ │ ├── ExportResultDto.cs │ │ │ ├── ExportService.cs │ │ │ ├── ExportType.cs │ │ │ ├── ExportedDataDto.cs │ │ │ ├── IDataExporter.cs │ │ │ ├── IDataExporterFactory.cs │ │ │ └── IExportService.cs │ │ └── Template │ │ │ ├── ConfigurationExtensions.cs │ │ │ ├── DashRazorLightProject.cs │ │ │ ├── DashRazorLightProjectItem.cs │ │ │ ├── FileSystemTemplateProvider.cs │ │ │ ├── ITemplateProvider.cs │ │ │ ├── ITemplateService.cs │ │ │ ├── RazorTemplates │ │ │ └── DefaultInvoiceTemplate.cshtml │ │ │ └── TemplateService.cs │ ├── ViewModels │ │ ├── AccountLiteResponseViewModel.cs │ │ ├── AccountResponseViewModel.cs │ │ ├── AccountSubTypeViewModels.cs │ │ ├── AddressResponseViewModel.cs │ │ ├── AmountViewModel.cs │ │ ├── ApplicationUserLiteViewModel.cs │ │ ├── AssetTypeViewModel.cs │ │ ├── BalanceSheetReportResponseViewModel.cs │ │ ├── BaseExportRequestViewModel.cs │ │ ├── BootstrapResponseViewModel.cs │ │ ├── ChangePasswordRequestViewModel.cs │ │ ├── CountryResponseViewModel.cs │ │ ├── CustomerDetailedResponseViewModel.cs │ │ ├── CustomerLiteResponseViewModel.cs │ │ ├── EmployeeLiteResponseViewModel.cs │ │ ├── ErrorViewModel.cs │ │ ├── ExportDescriptorRequestAndResponseViewModel.cs │ │ ├── ExportRequestWithDateRangeViewModel.cs │ │ ├── ExtendedAssetTypeViewModel.cs │ │ ├── INumberedJournalEntry.cs │ │ ├── InvoiceCreateRequestViewModel.cs │ │ ├── InvoiceFilterRequestViewModel.cs │ │ ├── InvoiceLineItemRequestViewModel.cs │ │ ├── InvoiceLineItemResponseViewModel.cs │ │ ├── InvoiceLiteResponseViewModel.cs │ │ ├── InvoicePaymentCreateRequestViewModel.cs │ │ ├── InvoicePaymentResponseViewModel.cs │ │ ├── InvoiceResponseViewModel.cs │ │ ├── InvoiceStatusUpdateRequestViewModel.cs │ │ ├── InvoiceTermsViewModel.cs │ │ ├── InvoiceUpdateRequestViewModel.cs │ │ ├── JournalEntryAccountCreateRequestViewModel.cs │ │ ├── JournalEntryAccountResponseViewModel.cs │ │ ├── JournalEntryCreateRequestViewModel.cs │ │ ├── JournalEntryLiteResponseViewModel.cs │ │ ├── JournalEntryResponseViewModel.cs │ │ ├── JournalEntryUpdateRequestViewModel.cs │ │ ├── LedgerAccountResponseViewModel.cs │ │ ├── LedgerAccountTransactionResponseViewModel.cs │ │ ├── LookupValueViewModel.cs │ │ ├── LookupsViewModel.cs │ │ ├── PaginationRequestViewModel.cs │ │ ├── PaymentCreateRequestViewModel.cs │ │ ├── PaymentResponseViewModel.cs │ │ ├── PostJournalEntryRequestViewModel.cs │ │ ├── ProductLiteResponseViewModel.cs │ │ ├── ProfitAndLossReportResponseViewModel.cs │ │ ├── RegionResponseViewModel.cs │ │ ├── ReportAccountResponseViewModel.cs │ │ ├── ReportDatesResponseViewModel.cs │ │ ├── Serialization │ │ │ ├── JsonDateConverter.cs │ │ │ ├── JsonNullableDateConverter.cs │ │ │ └── JsonNullableTimeSpanConverter.cs │ │ ├── TenantViewModel.cs │ │ ├── TimeActivitiesReportResponseViewModel.cs │ │ ├── TimeActivityCreateRequestViewModel.cs │ │ ├── TimeActivityResponseViewModel.cs │ │ └── TimeActivityUpdateRequestViewModel.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── DashAccountingSystemV2.sln ├── FrontEnd │ ├── .vscode │ │ └── launch.json │ ├── DashAccountingSystemV2.FrontEnd.esproj │ ├── nuget.config │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── app │ │ │ ├── app.css │ │ │ ├── app.tsx │ │ │ ├── applicationRedux │ │ │ │ ├── application.actionCreators.ts │ │ │ │ ├── application.actions.ts │ │ │ │ ├── application.reducer.ts │ │ │ │ └── index.ts │ │ │ ├── authentication │ │ │ │ ├── loginPage.tsx │ │ │ │ ├── logoutPage.tsx │ │ │ │ ├── models │ │ │ │ │ ├── accessTokenResponse.model.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── loginRequest.model.ts │ │ │ │ └── redux │ │ │ │ │ ├── authentication.actionCreators.ts │ │ │ │ │ ├── authentication.actions.ts │ │ │ │ │ ├── authentication.reducer.ts │ │ │ │ │ └── index.ts │ │ │ ├── export │ │ │ │ ├── export.actionCreators.ts │ │ │ │ ├── export.actions.ts │ │ │ │ ├── export.reducer.ts │ │ │ │ └── index.ts │ │ │ ├── globalReduxStore │ │ │ │ ├── action.interface.ts │ │ │ │ ├── actionType.ts │ │ │ │ ├── configureStore.ts │ │ │ │ ├── index.ts │ │ │ │ └── sessionStorageMiddleware.ts │ │ │ ├── homePage.tsx │ │ │ ├── index.ts │ │ │ ├── layout.tsx │ │ │ ├── lookupValues │ │ │ │ ├── index.ts │ │ │ │ ├── lookupValues.actionCreators.ts │ │ │ │ ├── lookupValues.actions.ts │ │ │ │ └── lookupValues.reducer.ts │ │ │ ├── mainContent.tsx │ │ │ ├── navMenu.css │ │ │ ├── navMenu.tsx │ │ │ ├── navMenuItem.tsx │ │ │ ├── notifications │ │ │ │ ├── index.ts │ │ │ │ ├── notificationLevel.ts │ │ │ │ ├── notifications.actionCreators.ts │ │ │ │ ├── notifications.actions.ts │ │ │ │ └── notifications.reducer.ts │ │ │ ├── privacyPage.tsx │ │ │ ├── selectTenantPage.tsx │ │ │ └── systemNotificationsArea.tsx │ │ ├── assets │ │ │ └── dash-hero-image.jpeg │ │ ├── common │ │ │ ├── components │ │ │ │ ├── amountDisplay.tsx │ │ │ │ ├── dateRangeMacroSelector.tsx │ │ │ │ ├── linkButton.tsx │ │ │ │ ├── loader.tsx │ │ │ │ ├── mainPageContent.tsx │ │ │ │ ├── reportParametersAndControls.tsx │ │ │ │ └── transactionStatusLabel.tsx │ │ │ ├── constants.ts │ │ │ ├── logging │ │ │ │ ├── index.ts │ │ │ │ ├── logger.interface.ts │ │ │ │ └── logger.ts │ │ │ ├── models │ │ │ │ ├── address.model.ts │ │ │ │ ├── addressType.model.ts │ │ │ │ ├── amount.model.ts │ │ │ │ ├── amountType.model.ts │ │ │ │ ├── apiErrorResponse.model.ts │ │ │ │ ├── assetType.model.ts │ │ │ │ ├── bootstrapInfo.model.ts │ │ │ │ ├── country.model.ts │ │ │ │ ├── dateRange.model.ts │ │ │ │ ├── dateRangeMacroType.model.ts │ │ │ │ ├── dateTimeString.model.ts │ │ │ │ ├── exportDownloadInfo.model.ts │ │ │ │ ├── exportFormat.model.ts │ │ │ │ ├── index.ts │ │ │ │ ├── lookupValue.model.ts │ │ │ │ ├── mode.model.ts │ │ │ │ ├── navigationSection.model.ts │ │ │ │ ├── pagedResult.model.ts │ │ │ │ ├── pagination.model.ts │ │ │ │ ├── region.model.ts │ │ │ │ ├── tenant.model.ts │ │ │ │ ├── timeZone.model.ts │ │ │ │ ├── transactionStatus.model.ts │ │ │ │ └── userLite.model.ts │ │ │ └── utilities │ │ │ │ ├── __tests__ │ │ │ │ └── stringUtils.test.ts │ │ │ │ ├── dateRangeMacros.ts │ │ │ │ ├── encoding.ts │ │ │ │ ├── errorHandling │ │ │ │ ├── apiErrorHandler.interface.ts │ │ │ │ ├── apiErrorHandler.tsx │ │ │ │ └── index.ts │ │ │ │ ├── numericUtils.ts │ │ │ │ ├── stringUtils.ts │ │ │ │ ├── useNamedState.ts │ │ │ │ └── usePrevious.ts │ │ ├── features │ │ │ ├── accounting │ │ │ │ ├── balance-sheet │ │ │ │ │ ├── balanceSheetPage.tsx │ │ │ │ │ ├── models │ │ │ │ │ │ ├── balanceSheetReport.model.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── redux │ │ │ │ │ │ ├── balanceSheet.actionCreators.ts │ │ │ │ │ │ ├── balanceSheet.actions.ts │ │ │ │ │ │ ├── balanceSheet.reducer.ts │ │ │ │ │ │ └── index.ts │ │ │ │ ├── chart-of-accounts │ │ │ │ │ ├── accountDetailsPage.tsx │ │ │ │ │ ├── chartOfAccountsPage.tsx │ │ │ │ │ ├── models │ │ │ │ │ │ ├── account.model.ts │ │ │ │ │ │ ├── accountCategoryList.model.ts │ │ │ │ │ │ ├── accountLite.model.ts │ │ │ │ │ │ ├── accountSelectOption.model.ts │ │ │ │ │ │ ├── accountSubType.model.ts │ │ │ │ │ │ ├── accountType.model.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── knownAccountSubType.model.ts │ │ │ │ │ │ ├── knownAccountType.model.ts │ │ │ │ │ │ └── reportAccount.model.ts │ │ │ │ │ └── redux │ │ │ │ │ │ ├── accounts.actionCreators.ts │ │ │ │ │ │ ├── accounts.actions.ts │ │ │ │ │ │ ├── accounts.reducer.ts │ │ │ │ │ │ └── index.ts │ │ │ │ ├── general-ledger │ │ │ │ │ ├── generalLedgerPage.tsx │ │ │ │ │ ├── models │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── ledgerAccount.model.ts │ │ │ │ │ │ └── ledgerAccountTransaction.model.ts │ │ │ │ │ └── redux │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── ledger.actionCreators.ts │ │ │ │ │ │ ├── ledger.actions.ts │ │ │ │ │ │ └── ledger.reducer.ts │ │ │ │ ├── journal │ │ │ │ │ ├── accountSelector.tsx │ │ │ │ │ ├── addJournalEntryPage.tsx │ │ │ │ │ ├── assetTypeSelector.tsx │ │ │ │ │ ├── editJournalEntryPage.tsx │ │ │ │ │ ├── journalEntryAccountsEditor.tsx │ │ │ │ │ ├── journalEntryDetails.tsx │ │ │ │ │ ├── journalEntryEditor.tsx │ │ │ │ │ ├── models │ │ │ │ │ │ ├── draftJournalEntryAccount.model.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── journalEntry.model.ts │ │ │ │ │ │ ├── journalEntryAccount.model.ts │ │ │ │ │ │ └── journalEntryLite.model.ts │ │ │ │ │ ├── postJournalEntryModalDialog.tsx │ │ │ │ │ ├── redux │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── journalEntry.actionCreators.ts │ │ │ │ │ │ ├── journalEntry.actions.ts │ │ │ │ │ │ └── journalEntry.reducer.ts │ │ │ │ │ ├── useAddJournalEntryAccount.ts │ │ │ │ │ └── viewJournalEntryPage.tsx │ │ │ │ └── profit-and-loss │ │ │ │ │ ├── models │ │ │ │ │ ├── index.ts │ │ │ │ │ └── profitAndLossReport.model.ts │ │ │ │ │ ├── profitAndLossPage.tsx │ │ │ │ │ └── redux │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── profitAndLoss.actionCreators.ts │ │ │ │ │ ├── profitAndLoss.actions.ts │ │ │ │ │ └── profitAndLoss.reducer.ts │ │ │ ├── dashboard │ │ │ │ └── dashboardPage.tsx │ │ │ ├── invoicing │ │ │ │ ├── invoices │ │ │ │ │ ├── addInvoicePage.tsx │ │ │ │ │ ├── invoiceLineItemsTable.tsx │ │ │ │ │ ├── invoiceListPage.tsx │ │ │ │ │ ├── invoiceStatusLabel.tsx │ │ │ │ │ ├── models │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── invoice.model.ts │ │ │ │ │ │ ├── invoiceLineItem.model.ts │ │ │ │ │ │ ├── invoiceLite.model.ts │ │ │ │ │ │ ├── invoiceStatus.model.ts │ │ │ │ │ │ └── invoiceTerms.model.ts │ │ │ │ │ ├── redux │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── invoice.actionCreators.ts │ │ │ │ │ │ ├── invoice.actions.ts │ │ │ │ │ │ └── invoice.reducer.ts │ │ │ │ │ ├── selectTimeActivitiesForInvoicingModalDialog.tsx │ │ │ │ │ └── viewInvoicePage.tsx │ │ │ │ └── payments │ │ │ │ │ ├── models │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── invoicePayment.model.ts │ │ │ │ │ ├── payment.model.ts │ │ │ │ │ └── paymentMethod.model.ts │ │ │ │ │ ├── receivePaymentModalDialog.tsx │ │ │ │ │ └── redux │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── payment.actionCreators.ts │ │ │ │ │ ├── payment.actions.ts │ │ │ │ │ └── payment.reducer.ts │ │ │ ├── sales │ │ │ │ ├── customers │ │ │ │ │ ├── models │ │ │ │ │ │ ├── customer.model.ts │ │ │ │ │ │ ├── customerLite.model.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── redux │ │ │ │ │ │ ├── customers.actionCreators.ts │ │ │ │ │ │ ├── customers.actions.ts │ │ │ │ │ │ ├── customers.reducer.ts │ │ │ │ │ │ └── index.ts │ │ │ │ └── products │ │ │ │ │ ├── models │ │ │ │ │ ├── index.ts │ │ │ │ │ └── productLite.model.ts │ │ │ │ │ └── redux │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── products.actionCreators.ts │ │ │ │ │ ├── products.actions.ts │ │ │ │ │ └── products.reducer.ts │ │ │ ├── time-tracking │ │ │ │ ├── employees │ │ │ │ │ ├── models │ │ │ │ │ │ ├── employeeLite.model.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── redux │ │ │ │ │ │ ├── employees.actionCreators.ts │ │ │ │ │ │ ├── employees.actions.ts │ │ │ │ │ │ ├── employees.reducer.ts │ │ │ │ │ │ └── index.ts │ │ │ │ └── time-activities │ │ │ │ │ ├── models │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── timeActivity.model.ts │ │ │ │ │ └── timeActivityDetailsReport.model.ts │ │ │ │ │ ├── redux │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── timeTracking.actionCreators.ts │ │ │ │ │ ├── timeTracking.actions.ts │ │ │ │ │ └── timeTracking.reducer.ts │ │ │ │ │ ├── timeActivityEntryModalDialog.tsx │ │ │ │ │ └── timeTrackingPage.tsx │ │ │ └── user-profile │ │ │ │ └── manageUserAccountPage.tsx │ │ ├── index.tsx │ │ ├── react-app-env.d.ts │ │ ├── reportWebVitals.ts │ │ └── setupTests.ts │ └── tsconfig.json └── Infrastructure │ ├── DashAccountingSystemV2.Aspire.AppHost │ ├── DashAccountingSystemV2.Aspire.AppHost.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json │ └── DashAccountingSystemV2.Aspire.ServiceDefaults │ ├── DashAccountingSystemV2.Aspire.ServiceDefaults.csproj │ └── Extensions.cs └── test ├── DashAccountingSystemV2.Tests ├── DashAccountingSystemV2.Tests.csproj ├── Extensions │ ├── DateTimeExtensionsFixture.cs │ ├── DecimalExtensionsFixture.cs │ ├── StringExtensionsFixture.cs │ └── TimeSpanExtensionsFixture.cs ├── Fakes │ └── FakeCache.cs ├── Repositories │ ├── AccountRepositoryFixture.cs │ ├── CustomerRepositoryFixture.cs │ ├── EmployeeRepositoryFixture.cs │ ├── InvoiceRepositoryFixture.cs │ ├── InvoiceTermsRepositoryFixture.cs │ ├── JournalEntryRepositoryFixture.cs │ ├── PaymentRepositoryFixture.cs │ ├── ProductRepositoryFixture.cs │ ├── SharedLookupRepositoryFixture.cs │ ├── TenantRepositoryFixture.cs │ └── TimeActivityRepositoryFixture.cs ├── Security │ └── ExportDownloads │ │ └── ExportDownloadSecurityTokenServiceFixture.cs ├── Services │ ├── Caching │ │ └── GeneralPurposeLocalMemoryCacheFixture.cs │ └── Template │ │ ├── FileSystemTemplateProviderFixture.cs │ │ ├── RazorTemplates │ │ └── DefaultInvoiceTemplate.cshtml │ │ └── TemplateServiceFixture.cs ├── TestUtilities.cs └── appsettings.UnitTests.json └── Postman ├── Dash Accounting - Local.postman_environment.json └── Dash Accounting System.postman_collection.json /README.md: -------------------------------------------------------------------------------- 1 | # Dash Accounting System V2 2 | ASP.NET Core + React/Redux SPA powered Simple Ledger and Time Tracking System for small businesses 3 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/AccountBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | using DashAccountingSystemV2.BackEnd.Repositories; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 5 | { 6 | public class AccountBusinessLogic : IAccountBusinessLogic 7 | { 8 | private readonly IAccountRepository _accountRepository = null; 9 | 10 | public AccountBusinessLogic(IAccountRepository accountRepository) 11 | { 12 | _accountRepository = accountRepository; 13 | } 14 | 15 | public async Task>> GetAccounts(Guid tenantId, DateTime dateForAccountBalances) 16 | { 17 | var accounts = await _accountRepository.GetAccountsByTenantAsync(tenantId); 18 | var accountBalances = await _accountRepository.GetAccountBalancesAsync(tenantId, dateForAccountBalances); 19 | 20 | var results = accounts.Select(a => new AccountWithBalanceDto() 21 | { 22 | Account = a, 23 | CurrentBalance = accountBalances[a.Id], 24 | }); 25 | 26 | return new BusinessLogicResponse>(results); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/ConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 2 | { 3 | public static class ConfigurationExtensions 4 | { 5 | public static IServiceCollection AddBusinessLogic(this IServiceCollection services) 6 | { 7 | // Business Logic layer objects 8 | services 9 | .AddScoped() 10 | .AddScoped() 11 | .AddScoped() 12 | .AddScoped() 13 | .AddScoped() 14 | .AddScoped() 15 | .AddScoped() 16 | .AddScoped() 17 | .AddScoped() 18 | .AddScoped() 19 | .AddScoped() 20 | .AddScoped(); 21 | 22 | // Business Logic Facades 23 | services 24 | .AddScoped(); 25 | 26 | return services; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/EmployeeBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | using DashAccountingSystemV2.BackEnd.Repositories; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 5 | { 6 | public class EmployeeBusinessLogic : IEmployeeBusinessLogic 7 | { 8 | private readonly IEmployeeRepository _employeeRepository; 9 | private readonly ITenantRepository _tenantRepository; 10 | 11 | public EmployeeBusinessLogic( 12 | IEmployeeRepository employeeRepository, 13 | ITenantRepository tenantRepository) 14 | { 15 | _employeeRepository = employeeRepository; 16 | _tenantRepository = tenantRepository; 17 | } 18 | 19 | public async Task>> GetByTenant(Guid tenantId, IEnumerable? employeeNumbers = null, bool onlyActive = true) 20 | { 21 | var tenant = await _tenantRepository.GetTenantAsync(tenantId); 22 | 23 | if (tenant == null) 24 | { 25 | return new BusinessLogicResponse>(ErrorType.RequestedEntityNotFound, "Tenant not found"); 26 | } 27 | 28 | // TODO: Verify access to tenant and permission for listing employees 29 | 30 | var employees = await _employeeRepository.GetByTenantIdAsync(tenantId, employeeNumbers, onlyActive); 31 | 32 | return new BusinessLogicResponse>(employees); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/ErrorType.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 2 | { 3 | public enum ErrorType 4 | { 5 | None = 0, 6 | RequestNotValid = 1, 7 | RequestedEntityNotFound = 2, 8 | UserNotAuthorized = 3, 9 | Conflict = 4, 10 | RuntimeException = 5, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IAccountBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface IAccountBusinessLogic : IBusinessLogic 6 | { 7 | Task>> GetAccounts( 8 | Guid tenantId, 9 | DateTime dateForAccountBalances); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IAccountingReportBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface IAccountingReportBusinessLogic : IBusinessLogic 6 | { 7 | Task> GetBalanceSheetReport( 8 | Guid tenantId, 9 | DateTime dateRangeStart, 10 | DateTime dateRangeEnd); 11 | 12 | Task> GetProfitAndLossReport( 13 | Guid tenantId, 14 | DateTime dateRangeStart, 15 | DateTime dateRangeEnd); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 2 | { 3 | /// 4 | /// Marker interface for business logic layer classes 5 | /// 6 | public interface IBusinessLogic 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IBusinessLogicFacade.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 2 | { 3 | /// 4 | /// Marker interface for Business Logic Façade classes 5 | /// 6 | /// 7 | /// A Business Logic Façade is an object that composes multiple Business Logic layer objects 8 | /// and functions as a single business logic layer unit to expose higher-level operations. 9 | /// 10 | public interface IBusinessLogicFacade : IBusinessLogic 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/ICustomerBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface ICustomerBusinessLogic : IBusinessLogic 6 | { 7 | Task>> GetByTenant( 8 | Guid tenantId, 9 | IEnumerable? customerNumbers = null, 10 | bool onlyActive = true); 11 | 12 | Task> GetDetailedById(Guid customerId); 13 | 14 | Task> GetDetailedByTenantIdAndCustomerNumber( 15 | Guid tenantId, 16 | string customerNumber); 17 | 18 | // TODO: Add CUD methods as needed 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IEmployeeBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface IEmployeeBusinessLogic : IBusinessLogic 6 | { 7 | Task>> GetByTenant( 8 | Guid tenantId, 9 | IEnumerable? employeeNumbers = null, 10 | bool onlyActive = true); 11 | 12 | // TODO: Add GetDetailedById() and CUD methods as needed 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IInvoiceBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface IInvoiceBusinessLogic : IBusinessLogic 6 | { 7 | Task> CreateInvoice(Invoice invoice); 8 | 9 | Task DeleteDraftInvoice(Guid tenantId, uint invoiceNumber, Guid contextUserId); 10 | 11 | Task> GetInvoiceByTenantAndInvoiceNumber(Guid tenantId, uint invoiceNumber); 12 | 13 | Task>> GetInvoiceTermsChoicesByTenant(Guid tenantId); 14 | 15 | Task>> GetPagedFilteredInvoices( 16 | Guid tenantId, 17 | DateTime? dateRangeStart, 18 | DateTime? dateRangeEnd, 19 | IEnumerable? includeCustomers, 20 | IEnumerable? includeInvoices, 21 | Pagination pagination); 22 | 23 | Task> UpdateInvoice(Invoice invoice, Guid contextUserId); 24 | 25 | Task> UpdateInvoiceStatus( 26 | Guid invoiceId, 27 | InvoiceStatus newStatus, 28 | Guid contextUserId); 29 | 30 | Task> UpdateInvoiceStatus( 31 | Guid tenantId, 32 | uint invoiceNumber, 33 | InvoiceStatus newStatus, 34 | Guid contextUserId); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IJournalEntryBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface IJournalEntryBusinessLogic : IBusinessLogic 6 | { 7 | Task> GetJournalEntryByTenantAndEntryId(Guid tenantId, uint entryId); 8 | 9 | Task> CreateJournalEntry(JournalEntry journalEntry); 10 | 11 | Task> PostJournalEntry( 12 | Guid tenantId, 13 | uint entryId, 14 | DateTime postDate, 15 | Guid postedByUserId, 16 | string? note = null); 17 | 18 | Task> UpdateJournalEntry(JournalEntry journalEntry, Guid contextUserId); 19 | 20 | Task DeletePendingJournalEntryByTenantAndEntryId(Guid tenantId, uint entryId); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/ILedgerBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface ILedgerBusinessLogic : IBusinessLogic 6 | { 7 | Task>> GetLedgerReport( 8 | Guid tenantId, 9 | DateTime dateRangeStart, 10 | DateTime dateRangeEnd); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IPaymentBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface IPaymentBusinessLogic : IBusinessLogic 6 | { 7 | Task> CreatePayment(Payment payment); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IPaymentFacade.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface IPaymentFacade : IBusinessLogicFacade 6 | { 7 | Task> CreatePayment(PaymentCreationRequestDto paymentCreationRequest); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/IProductBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface IProductBusinessLogic : IBusinessLogic 6 | { 7 | Task>> GetByTenant(Guid tenantId); 8 | 9 | // TODO: Add other methods as needed: 10 | // * Get by TenantAndCategory() 11 | // * GetDetailedById() 12 | // * CUD on both Categories and Products 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/ITenantBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface ITenantBusinessLogic : IBusinessLogic 6 | { 7 | Task>> GetTenants(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/ITimeZoneBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using TimeZone = DashAccountingSystemV2.BackEnd.Models.TimeZone; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 4 | { 5 | public interface ITimeZoneBusinessLogic 6 | { 7 | Task>> GetTimeZones(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/ProductBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | using DashAccountingSystemV2.BackEnd.Repositories; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 5 | { 6 | public class ProductBusinessLogic : IProductBusinessLogic 7 | { 8 | private readonly IProductRepository _productRepository; 9 | private readonly ITenantRepository _tenantRepository; 10 | 11 | public ProductBusinessLogic( 12 | IProductRepository productRepository, 13 | ITenantRepository tenantRepository) 14 | { 15 | _productRepository = productRepository; 16 | _tenantRepository = tenantRepository; 17 | } 18 | 19 | public async Task>> GetByTenant(Guid tenantId) 20 | { 21 | var tenant = await _tenantRepository.GetTenantAsync(tenantId); 22 | 23 | if (tenant == null) 24 | { 25 | return new BusinessLogicResponse>(ErrorType.RequestedEntityNotFound, "Tenant not found"); 26 | } 27 | 28 | // TODO: Verify access to tenant and permission for listing products 29 | 30 | var products = await _productRepository.GetProductsAsync(tenantId); 31 | 32 | return new BusinessLogicResponse>(products); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/BackEnd/BusinessLogic/TenantBusinessLogic.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | using DashAccountingSystemV2.BackEnd.Repositories; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.BusinessLogic 5 | { 6 | public class TenantBusinessLogic : ITenantBusinessLogic 7 | { 8 | private readonly ITenantRepository _tenantRepository; 9 | 10 | public TenantBusinessLogic(ITenantRepository tenantRepository) 11 | { 12 | _tenantRepository = tenantRepository; 13 | } 14 | 15 | public async Task>> GetTenants() 16 | { 17 | var tenants = await _tenantRepository.GetTenantsAsync(); 18 | return new BusinessLogicResponse>(tenants); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BackEnd/Configuration/OptionsHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using DashAccountingSystemV2.BackEnd.Extensions; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Configuration 5 | { 6 | public static class OptionsHelper 7 | { 8 | public static IOptions Make() where TOptions : class, new() 9 | { 10 | return new OptionsManager( 11 | new OptionsFactory( 12 | IEnumerableExtensions.CreateEnumerable(new ConfigureOptions(opt => { })), 13 | [])); 14 | } 15 | 16 | public static IOptions Make(Action action) where TOptions : class, new() 17 | { 18 | return new OptionsManager( 19 | new OptionsFactory( 20 | IEnumerableExtensions.CreateEnumerable(new ConfigureOptions(action)), 21 | [])); 22 | } 23 | 24 | public static IOptions Make(TOptions options) where TOptions : class, new() 25 | { 26 | return new OptionsWrapper(options); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/BackEnd/Controllers/CustomerController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using DashAccountingSystemV2.BackEnd.BusinessLogic; 3 | using DashAccountingSystemV2.BackEnd.Extensions; 4 | using DashAccountingSystemV2.BackEnd.Security.Authorization; 5 | using DashAccountingSystemV2.BackEnd.ViewModels; 6 | 7 | namespace DashAccountingSystemV2.BackEnd.Controllers 8 | { 9 | [ApiAuthorize] 10 | [ApiController] 11 | [Route("api/sales")] 12 | public class CustomerController : ControllerBase 13 | { 14 | private readonly ICustomerBusinessLogic _customerBusinessLogic; 15 | 16 | public CustomerController(ICustomerBusinessLogic customerBusinessLogic) 17 | { 18 | _customerBusinessLogic = customerBusinessLogic; 19 | } 20 | 21 | [HttpGet("{tenantId:guid}/customers")] 22 | public Task GetCustomers([FromRoute] Guid tenantId) 23 | { 24 | var bizLogicResponse = _customerBusinessLogic.GetByTenant(tenantId); 25 | return this.Result(bizLogicResponse, CustomerLiteResponseViewModel.FromModel); 26 | } 27 | 28 | [HttpGet("{tenantId:guid}/customer/{customerNumber}")] 29 | public Task GetCustomer([FromRoute] Guid tenantId, [FromRoute] string customerNumber) 30 | { 31 | var bizLogicResponse = _customerBusinessLogic.GetDetailedByTenantIdAndCustomerNumber(tenantId, customerNumber); 32 | return this.Result(bizLogicResponse, CustomerDetailedResponseViewModel.FromModel); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/BackEnd/Controllers/EmployeeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using DashAccountingSystemV2.BackEnd.BusinessLogic; 3 | using DashAccountingSystemV2.BackEnd.Extensions; 4 | using DashAccountingSystemV2.BackEnd.Security.Authorization; 5 | using DashAccountingSystemV2.BackEnd.ViewModels; 6 | 7 | namespace DashAccountingSystemV2.BackEnd.Controllers 8 | { 9 | [ApiAuthorize] 10 | [ApiController] 11 | [Route("api/workforce")] 12 | public class EmployeeController : ControllerBase 13 | { 14 | private readonly IEmployeeBusinessLogic _employeeBusinessLogic; 15 | 16 | public EmployeeController(IEmployeeBusinessLogic employeeBusinessLogic) 17 | { 18 | _employeeBusinessLogic = employeeBusinessLogic; 19 | } 20 | 21 | [HttpGet("{tenantId:guid}/employees")] 22 | public Task GetEmployees([FromRoute] Guid tenantId) 23 | { 24 | var bizLogicResponse = _employeeBusinessLogic.GetByTenant(tenantId); 25 | return this.Result(bizLogicResponse, EmployeeLiteResponseViewModel.FromModel); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/BackEnd/Controllers/ErrorController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Diagnostics; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.Controllers 6 | { 7 | /// 8 | /// Global error handler 9 | /// 10 | [AllowAnonymous] 11 | [ApiExplorerSettings(IgnoreApi = true)] 12 | public class ErrorController : ControllerBase 13 | { 14 | /// 15 | /// Global Error handler 16 | /// 17 | /// 18 | /// Error responses MUST NOT contain any sensitive information like exception types or stack traces! 19 | /// 20 | /// 21 | /// 500 Internal Server Error response with an RFC 7807 Problem Details 22 | /// JSON object in the response body. 23 | /// 24 | [Route("/api/error")] 25 | public IActionResult HandleError() 26 | { 27 | var exceptionHandlerPathFeature = HttpContext.Features.Get()!; 28 | 29 | return Problem( 30 | title: "Error", 31 | detail: "An error occurred while processing your request.", 32 | instance: exceptionHandlerPathFeature.Path); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/BackEnd/Controllers/PaymentController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using DashAccountingSystemV2.BackEnd.BusinessLogic; 3 | using DashAccountingSystemV2.BackEnd.Extensions; 4 | using DashAccountingSystemV2.BackEnd.Security.Authorization; 5 | using DashAccountingSystemV2.BackEnd.ViewModels; 6 | 7 | namespace DashAccountingSystemV2.BackEnd.Controllers 8 | { 9 | [ApiAuthorize] 10 | [ApiController] 11 | [Route("api/payment")] 12 | public class PaymentController : ControllerBase 13 | { 14 | private readonly IPaymentFacade _paymentBusinessLogicFacade; 15 | 16 | public PaymentController(IPaymentFacade paymentBusinessLogicFacade) 17 | { 18 | _paymentBusinessLogicFacade = paymentBusinessLogicFacade; 19 | } 20 | 21 | [HttpPost] 22 | public Task CreatePayment([FromBody] PaymentCreateRequestViewModel viewModel) 23 | { 24 | if (viewModel == null) 25 | return Task.FromResult(this.ErrorResponse("Invalid POST body")); 26 | 27 | var contextUserId = User.GetUserId(); 28 | var bizLogicResponse = _paymentBusinessLogicFacade.CreatePayment(PaymentCreateRequestViewModel.ToModel(viewModel, contextUserId)); 29 | return this.Result(bizLogicResponse, PaymentResponseViewModel.FromModel); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/BackEnd/Controllers/ProductController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using DashAccountingSystemV2.BackEnd.BusinessLogic; 3 | using DashAccountingSystemV2.BackEnd.Extensions; 4 | using DashAccountingSystemV2.BackEnd.Security.Authorization; 5 | using DashAccountingSystemV2.BackEnd.ViewModels; 6 | 7 | namespace DashAccountingSystemV2.BackEnd.Controllers 8 | { 9 | [ApiAuthorize] 10 | [ApiController] 11 | [Route("api/sales")] 12 | public class ProductController : ControllerBase 13 | { 14 | private readonly IProductBusinessLogic _productBusinessLogic; 15 | 16 | public ProductController(IProductBusinessLogic productBusinessLogic) 17 | { 18 | _productBusinessLogic = productBusinessLogic; 19 | } 20 | 21 | [HttpGet("{tenantId:guid}/products")] 22 | public Task GetCustomers([FromRoute] Guid tenantId) 23 | { 24 | var bizLogicResponse = _productBusinessLogic.GetByTenant(tenantId); 25 | return this.Result(bizLogicResponse, ProductLiteResponseViewModel.FromModel); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/BackEnd/Controllers/TenantController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using DashAccountingSystemV2.BackEnd.BusinessLogic; 3 | using DashAccountingSystemV2.BackEnd.Extensions; 4 | using DashAccountingSystemV2.BackEnd.Security.Authorization; 5 | using DashAccountingSystemV2.BackEnd.ViewModels; 6 | 7 | namespace DashAccountingSystemV2.BackEnd.Controllers 8 | { 9 | [ApiAuthorize] 10 | [ApiController] 11 | [Route("api/tenants")] 12 | public class TenantController : ControllerBase 13 | { 14 | private readonly ITenantBusinessLogic _tenantBusinessLogic; 15 | 16 | public TenantController(ITenantBusinessLogic tenantBusinessLogic) 17 | { 18 | _tenantBusinessLogic = tenantBusinessLogic; 19 | } 20 | 21 | [HttpGet] 22 | public Task GetTenants() 23 | { 24 | var tenantsBizLogicResponse = _tenantBusinessLogic.GetTenants(); 25 | return this.Result(tenantsBizLogicResponse, TenantViewModel.FromModel); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/BackEnd/Controllers/VersionController.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Controllers 5 | { 6 | [ApiController] 7 | [Route("api/application-version")] 8 | public class VersionController : ControllerBase 9 | { 10 | [HttpGet] 11 | public Task GetApplicationVersion() 12 | => Task.FromResult( 13 | Ok( 14 | new 15 | { 16 | Version = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.InformationalVersion 17 | })); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/BackEnd/Extensions/ClaimsPrincipalExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Extensions 4 | { 5 | public static class ClaimsPrincipalExtensions 6 | { 7 | public static string? GetUserFirstName(this ClaimsPrincipal user) 8 | { 9 | return user.FindFirstValue(ClaimTypes.GivenName); 10 | } 11 | 12 | public static string GetUserFullName(this ClaimsPrincipal user) 13 | { 14 | return $"{user.GetUserFirstName()} {user.GetUserLastName()}".Trim(); 15 | } 16 | 17 | public static Guid GetUserId(this ClaimsPrincipal user) 18 | { 19 | var nameIdentifierClaim = user.FindFirstValue(ClaimTypes.NameIdentifier); 20 | 21 | if (string.IsNullOrWhiteSpace(nameIdentifierClaim)) 22 | throw new ArgumentException("Could not find the 'name identifier' claim"); 23 | 24 | Guid userId; 25 | 26 | if (!Guid.TryParse(nameIdentifierClaim, out userId)) 27 | throw new ArgumentException( 28 | $"'name identifier' claim does not seem to be a GUID type value as expected; value is: '{nameIdentifierClaim}'"); 29 | 30 | return userId; 31 | } 32 | 33 | public static string? GetUserLastName(this ClaimsPrincipal user) 34 | { 35 | return user.FindFirstValue(ClaimTypes.Surname); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/BackEnd/Extensions/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Extensions 2 | { 3 | public static class Constants 4 | { 5 | public static readonly DateTime UnixEpochUtc = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/BackEnd/Extensions/DecimalExtensions.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Extensions 4 | { 5 | public static class DecimalExtensions 6 | { 7 | public static decimal WithNormalBalanceType(this decimal amount, AmountType normalBalanceType) 8 | { 9 | switch (normalBalanceType) 10 | { 11 | case AmountType.Credit: 12 | return amount * -1; 13 | 14 | case AmountType.Debit: 15 | default: 16 | return amount; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BackEnd/Extensions/IntegerExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Extensions 2 | { 3 | public static class IntegerExtensions 4 | { 5 | public static int CoalesceInRange(this int value, int lowerLimit, int upperLimit) 6 | { 7 | if (upperLimit < lowerLimit) 8 | throw new ArgumentOutOfRangeException(nameof(upperLimit), "upperLimit must not be less than lowerLimit"); 9 | 10 | var satisfiesLowerLimit = value >= lowerLimit; 11 | var satisfiesUpperLimit = value <= upperLimit; 12 | 13 | if (satisfiesLowerLimit && satisfiesUpperLimit) 14 | return value; 15 | else if (!satisfiesLowerLimit) 16 | return lowerLimit; 17 | else // doesn't satisfy upper limit 18 | return upperLimit; 19 | } 20 | 21 | public static int EnsureIsPositive(this int value, int defaultValue) 22 | { 23 | if (defaultValue <= 0) 24 | throw new ArgumentOutOfRangeException(nameof(defaultValue), "defaultValue must be positive"); 25 | 26 | if (value > 0) 27 | return value; 28 | 29 | return defaultValue; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/BackEnd/Models/AccountBalanceDto.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public class AccountBalanceDto 4 | { 5 | public Guid AccountId { get; set; } 6 | public decimal Balance { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/BackEnd/Models/AccountSubType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class AccountSubType 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int Id { get; private set; } 11 | 12 | [Required(AllowEmptyStrings = false)] 13 | [MaxLength(255)] 14 | public string Name { get; set; } 15 | 16 | public int AccountTypeId { get; private set; } 17 | public AccountType AccountType { get; set; } 18 | 19 | public AccountSubType(string name, int accountTypeId) 20 | { 21 | Name = name; 22 | AccountTypeId = accountTypeId; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/BackEnd/Models/AccountType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class AccountType 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int Id { get; private set; } 11 | 12 | [Required(AllowEmptyStrings = false)] 13 | [MaxLength(255)] 14 | public string Name { get; set; } 15 | 16 | public AccountType(string name) 17 | { 18 | Name = name; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BackEnd/Models/AccountWithBalanceDto.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public class AccountWithBalanceDto 4 | { 5 | public Account Account { get; set; } 6 | 7 | public decimal CurrentBalance { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/Models/AddressType.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public enum AddressType : ushort 4 | { 5 | Unknown = 0, 6 | Home = 1, 7 | Billing = 2, 8 | Shipping = 3, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/BackEnd/Models/AmountType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Models 4 | { 5 | public enum AmountType : sbyte 6 | { 7 | [Display(Name = "Debit")] 8 | Debit = 1, 9 | 10 | [Display(Name = "Credit")] 11 | Credit = -1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BackEnd/Models/ApplicationRole.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Models 4 | { 5 | public class ApplicationRole : IdentityRole 6 | { 7 | public string? Description { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/Models/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using Microsoft.AspNetCore.Identity; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.Models 6 | { 7 | public class ApplicationUser : IdentityUser 8 | { 9 | [PersonalData] 10 | [Required(AllowEmptyStrings = false)] 11 | [Display(Name = "First Name")] 12 | [MaxLength(70)] 13 | public string? FirstName { get; set; } 14 | 15 | [PersonalData] 16 | [Required(AllowEmptyStrings = false)] 17 | [Display(Name = "Last Name")] 18 | [MaxLength(70)] 19 | public string? LastName { get; set; } 20 | 21 | [Column(TypeName = "TIMESTAMP")] 22 | public DateTime? EmailConfirmedDate { get; set; } 23 | 24 | [Column(TypeName = "TIMESTAMP")] 25 | public DateTime? PhoneConfirmedDate { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/BackEnd/Models/AssetType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class AssetType : IEquatable 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int Id { get; private set; } 11 | 12 | [Required(AllowEmptyStrings = false)] 13 | [MaxLength(255)] 14 | public string Name { get; set; } 15 | 16 | [MaxLength(1024)] 17 | public string? Description { get; set; } 18 | 19 | [MaxLength(4)] 20 | public string? Symbol { get; set; } 21 | 22 | public AssetType() { } 23 | 24 | public AssetType(string name, string? symbol = null) 25 | { 26 | Name = name; 27 | Symbol = symbol; 28 | } 29 | 30 | public AssetType(int id, string name, string symbol) 31 | : this(name, symbol) 32 | { 33 | Id = id; 34 | } 35 | 36 | public bool Equals(AssetType? other) 37 | { 38 | if (other == null) 39 | return false; 40 | 41 | return Id == other.Id; 42 | } 43 | 44 | public override bool Equals(object? obj) 45 | { 46 | if (obj is AssetType other) 47 | { 48 | return Equals(other); 49 | } 50 | 51 | return false; 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | return Id.GetHashCode(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/BackEnd/Models/BalanceSheetReportDto.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public class BalanceSheetReportDto 4 | { 5 | public Tenant Tenant { get; set; } 6 | 7 | public DateRange DateRange { get; set; } 8 | 9 | public IEnumerable Assets { get; set; } 10 | 11 | public IEnumerable Liabilities { get; set; } 12 | 13 | public IEnumerable Equity { get; set; } 14 | 15 | public decimal NetIncome { get; set; } 16 | 17 | public decimal TotalAssets { get; set; } 18 | 19 | public decimal TotalLiabilities { get; set; } 20 | 21 | public decimal TotalEquity { get; set; } 22 | 23 | public decimal TotalLiabilitiesAndEquity { get; set; } 24 | 25 | public decimal? Discrepency { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/BackEnd/Models/Country.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class Country 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int Id { get; private set; } 11 | 12 | [Required(AllowEmptyStrings = false)] 13 | [MaxLength(64)] 14 | public string Name { get; set; } 15 | 16 | /// 17 | /// ISO-3611-Alpha-2 two letter abbreviation 18 | /// 19 | [Required(AllowEmptyStrings = false)] 20 | [MinLength(2)] 21 | [MaxLength(2)] 22 | public string TwoLetterCode { get; set; } 23 | 24 | /// 25 | /// ISO-3611-Alpha-3 three letter abbreviation 26 | /// 27 | [Required(AllowEmptyStrings = false)] 28 | [MinLength(3)] 29 | [MaxLength(3)] 30 | public string ThreeLetterCode { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/BackEnd/Models/DateRange.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public class DateRange 4 | { 5 | public DateTime DateRangeStart { get; set; } 6 | 7 | public DateTime DateRangeEnd { get; set; } 8 | 9 | public DateRange() { } 10 | 11 | public DateRange(DateTime dateRangeStart, DateTime dateRangeEnd) 12 | { 13 | DateRangeStart = dateRangeStart; 14 | DateRangeEnd = dateRangeEnd; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/BackEnd/Models/Entity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class Entity 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 10 | public Guid Id { get; private set; } 11 | 12 | [Required] 13 | public Guid TenantId { get; set; } 14 | 15 | [Required] 16 | public EntityType EntityType { get; set; } 17 | 18 | [Required] 19 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 20 | [Column(TypeName = "TIMESTAMP")] 21 | public DateTime Created { get; private set; } 22 | 23 | [Required] 24 | public Guid CreatedById { get; set; } 25 | public ApplicationUser CreatedBy { get; private set; } 26 | 27 | [Column(TypeName = "TIMESTAMP")] 28 | public DateTime? Updated { get; set; } 29 | 30 | public Guid? UpdatedById { get; set; } 31 | public ApplicationUser UpdatedBy { get; private set; } 32 | 33 | [Column(TypeName = "TIMESTAMP")] 34 | public DateTime? Inactivated { get; set; } 35 | 36 | public Guid? InactivatedById { get; set; } 37 | public ApplicationUser InactivatedBy { get; private set; } 38 | 39 | public bool IsActive => !Inactivated.HasValue; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/BackEnd/Models/InvoiceLineItemTimeActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class InvoiceLineItemTimeActivity 7 | { 8 | [Required] 9 | public Guid InvoiceLineItemId { get; set; } 10 | public InvoiceLineItem InvoiceLineItem { get; set; } 11 | 12 | [Required] 13 | public Guid TimeActivityId { get; set; } 14 | public TimeActivity TimeActivity { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/BackEnd/Models/InvoicePayment.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Models 4 | { 5 | public class InvoicePayment 6 | { 7 | [Required] 8 | public Guid InvoiceId { get; set; } 9 | public Invoice Invoice { get; set; } 10 | 11 | [Required] 12 | public Guid PaymentId { get; set; } 13 | public Payment Payment { get; set; } 14 | 15 | /// 16 | /// Amount of the specified Payment that is applied to the specified Invoice. 17 | /// 18 | public decimal Amount { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BackEnd/Models/InvoiceStatus.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public enum InvoiceStatus : short 4 | { 5 | Unknown = 0, 6 | Draft = 1, 7 | Sent = 2, 8 | Paid = 3, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/BackEnd/Models/InvoiceTerms.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class InvoiceTerms 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 10 | public Guid Id { get; private set; } 11 | 12 | public Guid? TenantId { get; set; } 13 | public Tenant Tenant { get; private set; } 14 | 15 | [Required(AllowEmptyStrings = false)] 16 | [MaxLength(256)] 17 | public string Name { get; set; } 18 | 19 | public ushort? DueInDays { get; set; } 20 | 21 | public ushort? DueOnDayOfMonth { get; set; } 22 | 23 | public ushort? DueNextMonthThreshold { get; set; } 24 | 25 | [Required] 26 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 27 | [Column(TypeName = "TIMESTAMP")] 28 | public DateTime Created { get; private set; } 29 | 30 | [Required] 31 | public Guid CreatedById { get; set; } 32 | public ApplicationUser CreatedBy { get; private set; } 33 | 34 | [Column(TypeName = "TIMESTAMP")] 35 | public DateTime? Updated { get; set; } 36 | 37 | public Guid? UpdatedById { get; set; } 38 | public ApplicationUser UpdatedBy { get; private set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/BackEnd/Models/KnownAccountType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DashAccountingSystemV2.BackEnd.Extensions; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public enum KnownAccountType 7 | { 8 | [Display(Name = "Unknown")] 9 | Unknown = 0, 10 | 11 | [Display(Name = "Assets")] 12 | Assets = 1, 13 | 14 | [Display(Name = "Liabilities")] 15 | Liabilities = 2, 16 | 17 | [Display(Name = "Owners Equity")] 18 | OwnersEquity = 3, 19 | 20 | [Display(Name = "Revenue")] 21 | Revenue = 4, 22 | 23 | [Display(Name = "Expenses")] 24 | Expenses = 5 25 | } 26 | 27 | public static class KnownAccountTypeExtensions 28 | { 29 | public static string GetDisplayName(this KnownAccountType accountType) 30 | { 31 | return EnumerationExtensions.GetDisplayName(accountType); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/BackEnd/Models/LedgerReportAccountDto.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public class LedgerReportAccountDto 4 | { 5 | public Account Account { get; set; } 6 | 7 | public decimal StartingBalance { get; set; } 8 | 9 | public IEnumerable Transactions { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/BackEnd/Models/PaginationBase.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public class PaginationBase 4 | { 5 | /// 6 | /// Current page of paged record sets 7 | /// 8 | public int? PageNumber { get; set; } 9 | 10 | /// 11 | /// Size of paged record sets 12 | /// 13 | public int? PageSize { get; set; } 14 | 15 | /// 16 | /// Default page size 17 | /// 18 | /// 19 | /// Some code requires DefaultPageSize == int.MaxValue (via Pagination.Default) 20 | /// So changing default size can lead to unpredictable results 21 | /// 22 | internal const int DefaultPageSize = int.MaxValue; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/BackEnd/Models/PaymentCreationRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class PaymentCreationRequestDto 7 | { 8 | public Guid TenantId { get; set; } 9 | 10 | public Guid CustomerId { get; set; } 11 | 12 | public Guid DepositAccountId { get; set; } 13 | 14 | public Guid RevenueAccountId { get; set; } 15 | 16 | public int PaymentMethodId { get; set; } 17 | 18 | public uint? CheckNumber { get; set; } 19 | 20 | public DateTime PaymentDate { get; set; } 21 | 22 | public decimal Amount { get; set; } 23 | 24 | public int AssetTypeId { get; set; } 25 | 26 | public string Description { get; set; } 27 | 28 | public bool IsPosted { get; set; } 29 | 30 | public Guid CreatedById { get; set; } 31 | 32 | public IEnumerable Invoices { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/BackEnd/Models/PaymentMethod.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class PaymentMethod 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int Id { get; private set; } 11 | 12 | [Required(AllowEmptyStrings = false)] 13 | [MaxLength(256)] 14 | public string Name { get; set; } 15 | 16 | public PaymentMethod(string name) 17 | { 18 | Name = name; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BackEnd/Models/ProductCategory.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class ProductCategory 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 10 | public Guid Id { get; private set; } 11 | 12 | [Required] 13 | public Guid TenantId { get; set; } 14 | 15 | [Required(AllowEmptyStrings = false)] 16 | [MaxLength(256)] 17 | public string Name { get; set; } 18 | 19 | /// 20 | /// For unique index on Name 21 | /// 22 | /// 23 | /// EF is LAME and cannot allow UPPER() or LOWER() in index expression 24 | /// 25 | public string? NormalizedName 26 | { 27 | get { return Name?.ToUpperInvariant(); } 28 | set { } 29 | } 30 | 31 | [Required] 32 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 33 | [Column(TypeName = "TIMESTAMP")] 34 | public DateTime Created { get; private set; } 35 | 36 | [Required] 37 | public Guid CreatedById { get; set; } 38 | public ApplicationUser CreatedBy { get; private set; } 39 | 40 | [Column(TypeName = "TIMESTAMP")] 41 | public DateTime? Updated { get; set; } 42 | 43 | public Guid? UpdatedById { get; set; } 44 | public ApplicationUser UpdatedBy { get; private set; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/BackEnd/Models/ProductType.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public enum ProductType 4 | { 5 | Unknown = 0, 6 | Product = 1, 7 | Service = 2, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/Models/ProfitAndLossReportDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Models 4 | { 5 | public class ProfitAndLossReportDto 6 | { 7 | public Tenant Tenant { get; set; } 8 | 9 | public DateRange DateRange { get; set; } 10 | 11 | public decimal GrossProfit { get; set; } 12 | 13 | public decimal TotalOperatingExpenses { get; set; } 14 | 15 | public decimal NetOperatingIncome { get; set; } 16 | 17 | public decimal TotalOtherIncome { get; set; } 18 | 19 | public decimal TotalOtherExpenses { get; set; } 20 | 21 | public decimal NetOtherIncome { get; set; } 22 | 23 | public decimal NetIncome { get; set; } 24 | 25 | public IEnumerable OperatingIncome { get; set; } 26 | 27 | public IEnumerable OtherIncome { get; set; } 28 | 29 | public IEnumerable OperatingExpenses { get; set; } 30 | 31 | public IEnumerable OtherExpenses { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/BackEnd/Models/ReconciliationReport.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using DashAccountingSystemV2.BackEnd.Extensions; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.Models 6 | { 7 | public class ReconciliationReport 8 | { 9 | [Key] 10 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 11 | public Guid Id { get; set; } 12 | 13 | [Required] 14 | public Guid AccountId { get; private set; } 15 | public Account Account { get; private set; } 16 | 17 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 18 | [Column(TypeName = "TIMESTAMP")] 19 | public DateTime Created { get; private set; } 20 | 21 | [Required] 22 | public Guid CreatedById { get; private set; } 23 | public ApplicationUser CreatedBy { get; private set; } 24 | 25 | [Required] 26 | [Column(TypeName = "TIMESTAMP")] 27 | public DateTime ClosingDate { get; set; } 28 | 29 | [Required] 30 | public decimal ClosingBalance { get; set; } 31 | 32 | public ICollection Transactions { get; set; } = new List(); 33 | 34 | public decimal ReconciledBalance => Transactions.HasAny() ? 35 | Transactions.Aggregate(0.0m, (sum, nextTx) => sum += nextTx.Amount) : 36 | 0.0m; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/BackEnd/Models/Region.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class Region 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 10 | public int Id { get; private set; } 11 | 12 | /// 13 | /// What "kind" of region this is, e.g. "State", "Province", etc. 14 | /// 15 | public string? Label { get; set; } 16 | 17 | [Required(AllowEmptyStrings = false)] 18 | [MaxLength(128)] 19 | public string Name { get; set; } 20 | 21 | /// 22 | /// ISO 3166-2 Code for the Region (only the regional/local segment; not including the parent Country code) 23 | /// 24 | /// 25 | /// 26 | /// 27 | [Required(AllowEmptyStrings = false)] 28 | [MaxLength(3)] 29 | public string Code { get; set; } 30 | 31 | public int CountryId { get; set; } 32 | public Country Country { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/BackEnd/Models/SortDirection.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public enum SortDirection 4 | { 5 | Ascending, 6 | Descending 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/BackEnd/Models/Sorting.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public class Sorting 4 | { 5 | public string SortColumn { get; set; } 6 | public SortDirection SortDirection { get; set; } 7 | 8 | public Sorting() 9 | { 10 | 11 | } 12 | 13 | public Sorting(string sortColumn, SortDirection sortDirection) 14 | { 15 | SortColumn = sortColumn; 16 | SortDirection = sortDirection; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/BackEnd/Models/Tenant.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Models 5 | { 6 | public class Tenant 7 | { 8 | [Key] 9 | [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 10 | public Guid Id { get; set; } 11 | 12 | [Required(AllowEmptyStrings = false)] 13 | [MaxLength(255)] 14 | public string Name { get; private set; } 15 | 16 | [Required] 17 | public int DefaultAssetTypeId { get; set; } 18 | public AssetType DefaultAssetType { get; set; } 19 | 20 | [EmailAddress] 21 | [MaxLength(256)] 22 | public string? ContactEmailAddress { get; set; } 23 | 24 | [NotMapped] 25 | public Address MailingAddress { get; set; } 26 | 27 | #region Navigation Properties 28 | public ICollection Accounts { get; } = new List(); 29 | 30 | public ICollection JournalEntries { get; } = new List(); 31 | #endregion Navigation Properties 32 | 33 | public Tenant() { } 34 | 35 | public Tenant(string name) 36 | { 37 | Name = name; 38 | } 39 | 40 | public Tenant(Guid id, string name) 41 | { 42 | Id = id; 43 | Name = name; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/BackEnd/Models/TimeActivityDetailsReportDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Models 4 | { 5 | public class TimeActivityDetailsReportDto 6 | { 7 | public Tenant Tenant { get; set; } 8 | 9 | public DateRange DateRange { get; set; } 10 | 11 | public IEnumerable TimeActivities { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BackEnd/Models/TimeZone.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public class TimeZone 4 | { 5 | /// 6 | /// IANA / Olson / TZDB ID for the Time Zone (e.g. America/Los_Angeles) 7 | /// 8 | /// 9 | /// For a more suitable, shorter and friendlier list, we're actually 10 | /// starting from the Windows / .NET Base Class Library (BCL) Time Zone 11 | /// master list, and not the full IANA database. We then use the 12 | /// Canonical IANA Zone that corresponds with the Windows Zone. 13 | /// 14 | public string Id { get; set; } 15 | 16 | /// 17 | /// Friendly Display Name for the Time Zone (e.g. "(UTC-08:00) Pacific Time (US & Canada)") 18 | /// 19 | public string DisplayName { get; set; } 20 | 21 | public TimeZone() { } 22 | 23 | public TimeZone(string id, string displayName) 24 | { 25 | Id = id; 26 | DisplayName = displayName; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/BackEnd/Models/TransactionStatus.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Models 2 | { 3 | public enum TransactionStatus : short 4 | { 5 | Unknown = 0, 6 | Pending = 1, 7 | Posted = 2, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:47043", 8 | "sslPort": 44393 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5208", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7252;http://localhost:5208", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/ConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Repositories 2 | { 3 | public static class ConfigurationExtensions 4 | { 5 | public static IServiceCollection AddRepositories(this IServiceCollection services) 6 | { 7 | services 8 | .AddScoped() 9 | .AddScoped() 10 | .AddScoped() 11 | .AddScoped() 12 | .AddScoped() 13 | .AddScoped() 14 | .AddScoped() 15 | .AddScoped() 16 | .AddScoped() 17 | .AddScoped() 18 | .AddScoped() 19 | .AddScoped(); 20 | 21 | return services; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/IAccountRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using DashAccountingSystemV2.BackEnd.Models; 5 | 6 | namespace DashAccountingSystemV2.BackEnd.Repositories 7 | { 8 | public interface IAccountRepository 9 | { 10 | Task CreateAccountAsync(Account account); 11 | 12 | Task> GetAccountsByTenantAsync( 13 | Guid tenantId, 14 | KnownAccountType[] accountTypes = null); 15 | 16 | Task GetAccountByIdAsync(Guid accountId); 17 | 18 | Task GetAccountBalanceAsync(Guid accountId, DateTime date); 19 | 20 | Task> GetAccountBalancesAsync( 21 | Guid tenantId, 22 | DateTime date, 23 | KnownAccountType[] accountTypes = null); 24 | 25 | Task> GetAccountBalancesAsync( 26 | Guid tenantId, 27 | DateTime dateRangeStart, 28 | DateTime dateRangeEnd, 29 | KnownAccountType[] accountTypes = null); 30 | 31 | Task> GetPendingTransactionsAsync(Guid accountId); 32 | 33 | Task> GetPostedTransactionsAsync( 34 | Guid accountId, 35 | DateTime dateRangeStart, 36 | DateTime dateRangeEnd, 37 | Pagination pagination); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/ICustomerRepository.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Repositories 4 | { 5 | public interface ICustomerRepository 6 | { 7 | Task> GetByTenantIdAsync( 8 | Guid tenantId, 9 | IEnumerable? customerNumbers = null, 10 | bool onlyActive = true); 11 | 12 | Task GetByEntityIdAsync(Guid entityId); 13 | 14 | Task GetByTenantIdAndCustomerNumberAsync(Guid tenantId, string customerNumber); 15 | 16 | Task InsertAsync(Customer customer); 17 | 18 | Task UpdateAsync(Customer customer); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/IEmployeeRepository.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Repositories 4 | { 5 | public interface IEmployeeRepository 6 | { 7 | Task> GetByTenantIdAsync( 8 | Guid tenantId, 9 | IEnumerable? employeeNumbers = null, 10 | bool onlyActive = true); 11 | 12 | Task GetByEntityIdAsync(Guid entityId); 13 | 14 | Task GetByUserIdAsync(Guid tenantId, Guid userId); 15 | 16 | Task InsertAsync(Employee employee); 17 | 18 | Task UpdateAsync(Employee employee); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/IInvoiceRepository.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Repositories 4 | { 5 | public interface IInvoiceRepository 6 | { 7 | Task GetByIdAsync(Guid invoiceId); 8 | 9 | Task GetByTenantIdAndInvoiceNumberAsync(Guid tenantId, uint invoiceNumber); 10 | 11 | Task GetDetailedByIdAsync(Guid invoiceId); 12 | 13 | Task GetDetailedByTenantIdAndInvoiceNumberAsync(Guid tenantId, uint invoiceNumber); 14 | 15 | Task> GetFilteredAsync( 16 | Guid tenantId, 17 | DateTime? dateRangeStart, 18 | DateTime? dateRangeEnd, 19 | IEnumerable? includeCustomers, 20 | IEnumerable? includeInvoices, 21 | Pagination pagination); 22 | 23 | Task GetNextInvoiceNumberAsync(Guid tenantId); 24 | 25 | Task CreateInvoiceAsync(Invoice invoice); 26 | 27 | Task UpdateCompleteInvoiceAsync(Invoice invoice, Guid contextUserId); 28 | 29 | Task UpdateInvoiceStatusAsync( 30 | Guid invoiceId, 31 | InvoiceStatus newStatus, 32 | Guid contextUserId); 33 | 34 | Task UpdateInvoiceStatusAsync( 35 | Guid tenantId, 36 | uint invoiceNumber, 37 | InvoiceStatus newStatus, 38 | Guid contextUserId); 39 | 40 | Task DeleteAsync(Guid invoiceId, Guid contextUserId); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/IInvoiceTermsRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using DashAccountingSystemV2.BackEnd.Models; 5 | 6 | namespace DashAccountingSystemV2.BackEnd.Repositories 7 | { 8 | public interface IInvoiceTermsRepository 9 | { 10 | Task GetByIdAsync(Guid invoiceTermsId); 11 | 12 | Task> GetInvoiceTermsChoicesAsync(Guid tenantId); 13 | 14 | Task InsertAsync(InvoiceTerms invoiceTerms); 15 | 16 | Task UpdateAsync(InvoiceTerms invoiceTerms, Guid contextUserId); 17 | 18 | Task DeleteAsync(Guid invoiceTermsId, Guid contextUserId); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/IPaymentRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using DashAccountingSystemV2.BackEnd.Models; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.Repositories 6 | { 7 | public interface IPaymentRepository 8 | { 9 | public Task DeleteAsync(Guid paymentId, Guid contextUserId); 10 | 11 | public Task GetByIdAsync(Guid paymentId); 12 | 13 | public Task InsertAsync(Payment payment); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/IProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using DashAccountingSystemV2.BackEnd.Models; 6 | 7 | namespace DashAccountingSystemV2.BackEnd.Repositories 8 | { 9 | public interface IProductRepository 10 | { 11 | Task> GetCategoriesAsync(Guid tenantId); 12 | 13 | Task> GetProductsAsync(Guid tenantId); 14 | 15 | Task InsertCategory(ProductCategory category); 16 | 17 | Task InsertProduct(Product product); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/ISharedLookupRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DashAccountingSystemV2.BackEnd.Models; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.Repositories 6 | { 7 | public interface ISharedLookupRepository 8 | { 9 | Task> GetAccountTypesAsync(); 10 | 11 | Task> GetAccountSubTypesAsync(); 12 | 13 | Task> GetAssetTypesAsync(); 14 | 15 | Task> GetCountriesAsync(); 16 | 17 | Task> GetPaymentMethodsAsync(); 18 | 19 | Task> GetRegionsByCountryAsync(int countryId); 20 | 21 | Task> GetRegionsByCountryAlpha2CodeAsync(string countryAlpha2Code); 22 | 23 | Task> GetRegionsByCountryAlpha3CodeAsync(string countryAlpha3Code); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/ITimeActivityRepository.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Repositories 4 | { 5 | public interface ITimeActivityRepository 6 | { 7 | Task GetByIdAsync(Guid timeActivityId); 8 | 9 | Task> GetFilteredAsync( 10 | Guid tenantId, 11 | DateTime dateRangeStart, 12 | DateTime dateRangeEnd, 13 | IEnumerable? includeCustomers = null, 14 | IEnumerable? includeEmployees = null); 15 | 16 | Task> GetUnbilledItemsForInvoicingAsync( 17 | Guid customerId, 18 | DateTime dateRangeStart, 19 | DateTime dateRangeEnd); 20 | 21 | Task InsertAsync(TimeActivity timeActivity); 22 | 23 | Task UpdateAsync(TimeActivity timeActivity, Guid contextUserId); 24 | 25 | Task DeleteAsync(Guid timeActivityId, Guid contextUserId); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/BackEnd/Repositories/ITimeZoneRepository.cs: -------------------------------------------------------------------------------- 1 | using TimeZone = DashAccountingSystemV2.BackEnd.Models.TimeZone; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Repositories 4 | { 5 | public interface ITimeZoneRepository 6 | { 7 | Task> GetTimeZones(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/Security/Authorization/ApplicationRoleManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Security.Authorization 5 | { 6 | public class ApplicationRoleManager : RoleManager 7 | { 8 | public ApplicationRoleManager( 9 | IRoleStore store, 10 | IEnumerable> roleValidators, 11 | ILookupNormalizer keyNormalizer, 12 | IdentityErrorDescriber errors, 13 | ILogger> logger) 14 | : base(store, roleValidators, keyNormalizer, errors, logger) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/BackEnd/Security/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Security 2 | { 3 | public static class Constants 4 | { 5 | public const string ApplicationAuthenticationScheme = "DashAccountingSystemTokenBasedAuthentication"; 6 | 7 | public const string ExportDownloadAuthenticationScheme = "One-time use export download token"; 8 | 9 | public const string ExportDownloadAuthorizationPolicy = "Authorization via Export Download Token"; 10 | 11 | public static class DashClaimTypes 12 | { 13 | public const string ExportType = "urn:dash-software:dash-accounting:export-type"; 14 | 15 | public const string TenantId = "urn:dash-software:dash-accounting:tenant-id"; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/BackEnd/Security/ExportDownloads/ExportDownloadAuthenticationTicket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DashAccountingSystemV2.BackEnd.Services.Export; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Security.ExportDownloads 5 | { 6 | public class ExportDownloadAuthenticationTicket 7 | { 8 | public Guid TenantId { get; set; } 9 | 10 | public Guid UserId { get; set; } 11 | 12 | public ExportType ExportType { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/BackEnd/Security/ExportDownloads/ExportDownloadSecurityTokenAuthenticationHandlerOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Security.ExportDownloads 5 | { 6 | public class ExportDownloadSecurityTokenAuthenticationHandlerOptions 7 | : AuthenticationSchemeOptions, IPostConfigureOptions 8 | { 9 | public ExportDownloadSecurityTokenAuthenticationHandlerOptions() : base() 10 | { 11 | } 12 | 13 | public void PostConfigure(string? name, ExportDownloadSecurityTokenAuthenticationHandlerOptions options) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/BackEnd/Security/ExportDownloads/IExportDownloadSecurityTokenService.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Services.Export; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Security.ExportDownloads 4 | { 5 | public interface IExportDownloadSecurityTokenService 6 | { 7 | Task RequestExportDownloadToken( 8 | Guid tenantId, 9 | Guid userId, 10 | ExportType exportType); 11 | 12 | Task RedeemExportDownloadToken(string token); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Caching/ConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Caching 2 | { 3 | public static class ConfigurationExtensions 4 | { 5 | public static IServiceCollection AddCaching(this IServiceCollection services) 6 | { 7 | services 8 | .AddSingleton(); 9 | 10 | return services; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Caching/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Caching 2 | { 3 | public static class Constants 4 | { 5 | public const string ApplicationCacheKeyPrefix = "DashAccounting"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Caching/IExtendedDistributedCache.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Distributed; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Services.Caching 4 | { 5 | public interface IExtendedDistributedCache : IDistributedCache 6 | { 7 | Task GetObjectAsync(string key); 8 | 9 | Task SetAsync( 10 | string key, 11 | byte[] value, 12 | TimeSpan expiration, 13 | bool isSlidingExpiration = false); 14 | 15 | Task SetObjectAsync( 16 | string key, 17 | TCachedData value); 18 | 19 | Task SetObjectAsync( 20 | string key, 21 | TCachedData value, 22 | TimeSpan expiration, 23 | bool isSlidingExpiration = false); 24 | 25 | Task SetObjectAsync( 26 | string key, 27 | TCachedData value, 28 | DistributedCacheEntryOptions options, 29 | CancellationToken cancellationToken = default); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/DataExporterFactory.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | public class DataExporterFactory : IDataExporterFactory 4 | { 5 | public Func DataExporterFactoryMethod { get; private set; } 6 | 7 | public DataExporterFactory(Func dataExporterFactoryMethod) 8 | { 9 | DataExporterFactoryMethod = dataExporterFactoryMethod; 10 | } 11 | 12 | public IDataExporter CreateDataExporter(Type typeOfDataModel, ExportRequestParameters exportRequestParameters) 13 | { 14 | return DataExporterFactoryMethod(typeOfDataModel, exportRequestParameters); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/ExcelTemplates/BalanceSheetReport.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DashSoftwareSolutions/DashAccountingSystemV2/822d8a15f5c9f5bf90317eac3a64f17301043e41/src/BackEnd/Services/Export/ExcelTemplates/BalanceSheetReport.xlsx -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/ExcelTemplates/ProfitAndLossReport.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DashSoftwareSolutions/DashAccountingSystemV2/822d8a15f5c9f5bf90317eac3a64f17301043e41/src/BackEnd/Services/Export/ExcelTemplates/ProfitAndLossReport.xlsx -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/ExportFormat.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | public enum ExportFormat 4 | { 5 | Unknown = 0, 6 | CSV = 1, 7 | PDF = 2, 8 | XLSX = 3, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/ExportRequestParameters.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | public class ExportRequestParameters 4 | { 5 | public Guid TenantId { get; set; } 6 | 7 | public Guid RequestingUserId { get; set; } 8 | 9 | public ExportFormat ExportFormat { get; set; } 10 | 11 | public ExportType ExportType { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/ExportResultDto.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | public class ExportResultDto 4 | { 5 | public ExportFormat ExportFormat { get; set; } 6 | 7 | public ExportType ExportType { get; set; } 8 | 9 | public Guid TenantId { get; set; } 10 | 11 | public string Token { get; set; } 12 | 13 | public string FileName { get; set; } 14 | 15 | public bool IsSuccessful { get; set; } 16 | 17 | public string Error { get; set; } 18 | 19 | public ExportResultDto() { } 20 | 21 | public ExportResultDto(ExportRequestParameters parameters) 22 | { 23 | ExportFormat = parameters.ExportFormat; 24 | ExportType = parameters.ExportType; 25 | TenantId = parameters.TenantId; 26 | } 27 | 28 | public ExportResultDto( 29 | ExportRequestParameters parameters, 30 | string errorMessage) 31 | : this(parameters) 32 | { 33 | IsSuccessful = false; 34 | Error = errorMessage; 35 | } 36 | 37 | public ExportResultDto( 38 | ExportRequestParameters parameters, 39 | string fileName, 40 | string downloadAccessToken) 41 | : this(parameters) 42 | { 43 | IsSuccessful = true; 44 | FileName = fileName; 45 | Token = downloadAccessToken; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/ExportType.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | public enum ExportType : ushort 4 | { 5 | Unknown = 0, 6 | LedgerReport = 1, 7 | BalanceSheetReport = 2, 8 | ProfitAndLossReport = 3, 9 | Invoice = 4, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/ExportedDataDto.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | public class ExportedDataDto 4 | { 5 | public string FileName { get; set; } = string.Empty; 6 | public byte[] Content { get; set; } = []; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/IDataExporter.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | /// 4 | /// Marker interface for Data Exporters, to be used with the factory pattern. 5 | /// 6 | public interface IDataExporter 7 | { 8 | } 9 | 10 | /// 11 | /// Generic interface for Data Exporters that actually specifies the contract. 12 | /// 13 | /// Type of model or DTO that will be sent to the Data Exporter 14 | public interface IDataExporter : IDataExporter 15 | { 16 | Task GetDataExport(ExportRequestParameters parameters, TUnderlyingData data); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/IDataExporterFactory.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | public interface IDataExporterFactory 4 | { 5 | IDataExporter CreateDataExporter(Type typeOfDataModel, ExportRequestParameters exportRequestParameters); 6 | 7 | Func DataExporterFactoryMethod { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Export/IExportService.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Export 2 | { 3 | public interface IExportService 4 | { 5 | Task GetDataExport( 6 | ExportRequestParameters parameters, 7 | TUnderlyingData data) where TUnderlyingData : class; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Template/ConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Template 2 | { 3 | public static class ConfigurationExtensions 4 | { 5 | public static IServiceCollection AddTemplateService(this IServiceCollection services) 6 | { 7 | // Template Provider 8 | services.AddSingleton(); 9 | 10 | // Template Service 11 | services.AddSingleton(); 12 | 13 | return services; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Template/DashRazorLightProject.cs: -------------------------------------------------------------------------------- 1 | using RazorLight.Razor; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.Services.Template 4 | { 5 | /// 6 | /// This type is necessary to help the RazorLight templating engine resolve partial views.
7 | /// See:
8 | ///
9 | /// 10 | ///
11 | public class DashRazorLightProject : RazorLightProject 12 | { 13 | private readonly ITemplateProvider _templateProvider; 14 | 15 | public DashRazorLightProject(ITemplateProvider templateProvider) 16 | { 17 | _templateProvider = templateProvider; 18 | } 19 | 20 | public override Task> GetImportsAsync(string templateKey) 21 | { 22 | return Task.FromResult(Enumerable.Empty()); 23 | } 24 | 25 | public override async Task GetItemAsync(string templateKey) 26 | { 27 | var templateContent = await _templateProvider.GetTemplate(templateKey); 28 | 29 | return new DashRazorLightProjectItem(templateKey, templateContent); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Template/DashRazorLightProjectItem.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using RazorLight.Razor; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.Services.Template 5 | { 6 | /// 7 | /// This type is necessary to help the RazorLight templating engine resolve partial views.
8 | /// See:
9 | ///
10 | /// 11 | ///
12 | public class DashRazorLightProjectItem : RazorLightProjectItem 13 | { 14 | private string _content; 15 | 16 | public DashRazorLightProjectItem(string key, string content) 17 | { 18 | Key = key; 19 | _content = content; 20 | } 21 | 22 | public override string Key { get; } 23 | 24 | public override bool Exists => _content != null; 25 | 26 | public override Stream Read() 27 | { 28 | return new MemoryStream(Encoding.UTF8.GetBytes(_content)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Template/FileSystemTemplateProvider.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Template 2 | { 3 | public class FileSystemTemplateProvider : ITemplateProvider 4 | { 5 | private const string _TemplatesDirectory = "RazorTemplates"; 6 | 7 | private readonly ILogger _logger; 8 | 9 | public FileSystemTemplateProvider(ILogger logger) 10 | { 11 | _logger = logger; 12 | } 13 | 14 | public Task GetTemplate(string templateName) 15 | { 16 | var filePath = $"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_TemplatesDirectory}{Path.DirectorySeparatorChar}{templateName}"; 17 | 18 | if (!File.Exists(filePath)) 19 | { 20 | _logger.LogError( 21 | "Request Template File {templateName} was not found in the expected folder {templatesFolder}", 22 | templateName, 23 | _TemplatesDirectory); 24 | 25 | return Task.FromResult(null); 26 | } 27 | 28 | return File.ReadAllTextAsync(filePath); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Template/ITemplateProvider.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Template 2 | { 3 | public interface ITemplateProvider 4 | { 5 | Task GetTemplate(string templateName); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/BackEnd/Services/Template/ITemplateService.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.Services.Template 2 | { 3 | public interface ITemplateService 4 | { 5 | /// 6 | /// Gets resulting HTML from the specified Razor Template and model object 7 | /// 8 | /// Razor Template Name 9 | /// Model to bind to the Razor Template to produce the HTML output 10 | /// Resulting HTML 11 | Task GetHtmlFromRazorTemplate(string razorTemplateName, object model); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/AccountLiteResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class AccountLiteResponseViewModel 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public uint AccountNumber { get; set; } 10 | 11 | public string AccountName { get; set; } 12 | 13 | public LookupValueViewModel AccountType { get; set; } 14 | 15 | public LookupValueViewModel AccountSubType { get; set; } 16 | 17 | public static AccountLiteResponseViewModel FromModel(Account model) 18 | { 19 | if (model == null) 20 | return null; 21 | 22 | return new AccountLiteResponseViewModel() 23 | { 24 | Id = model.Id, 25 | AccountName = model.Name, 26 | AccountNumber = model.AccountNumber, 27 | AccountType = model.AccountType != null ? 28 | new LookupValueViewModel(model.AccountType.Id, model.AccountType.Name) : 29 | null, 30 | AccountSubType = model.AccountSubType != null ? 31 | new LookupValueViewModel(model.AccountSubType.Id, model.AccountSubType.Name) : 32 | null, 33 | }; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/AccountSubTypeViewModels.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class AccountSubTypeViewModel : LookupValueViewModel 6 | { 7 | public int AccountTypeId { get; set; } 8 | 9 | public string AccountType { get; set; } 10 | 11 | public static AccountSubTypeViewModel FromModel(AccountSubType accountSubType) 12 | { 13 | if (accountSubType == null) 14 | return null; 15 | 16 | return new AccountSubTypeViewModel() 17 | { 18 | Id = accountSubType.Id, 19 | Name = accountSubType.Name, 20 | AccountTypeId = accountSubType.AccountType?.Id ?? 0, 21 | AccountType = accountSubType.AccountType?.Name ?? "Unknown", 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/AddressResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class AddressResponseViewModel 7 | { 8 | public Guid Id { get; set; } 9 | 10 | [JsonConverter(typeof(JsonStringEnumConverter))] 11 | public AddressType AddressType { get; set; } 12 | 13 | public string StreetAddress1 { get; set; } 14 | 15 | public string StreetAddress2 { get; set; } 16 | 17 | public string City { get; set; } 18 | 19 | public RegionResponseViewModel Region { get; set; } 20 | 21 | public string PostalCode { get; set; } 22 | 23 | public CountryResponseViewModel Country { get; set; } 24 | 25 | public static AddressResponseViewModel FromModel(Address model) 26 | { 27 | if (model == null) 28 | return null; 29 | 30 | return new AddressResponseViewModel() 31 | { 32 | Id = model.Id, 33 | AddressType = model.AddressType, 34 | StreetAddress1 = model.StreetAddress1, 35 | StreetAddress2 = model.StreetAddress2, 36 | City = model.City, 37 | PostalCode = model.PostalCode, 38 | Region = RegionResponseViewModel.FromModel(model.Region), 39 | Country = CountryResponseViewModel.FromModel(model.Country), 40 | }; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/ApplicationUserLiteViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class ApplicationUserLiteViewModel 6 | { 7 | public Guid Id { get; set; } 8 | public string FirstName { get; set; } 9 | public string LastName { get; set; } 10 | 11 | public ApplicationUserLiteViewModel() { } 12 | 13 | public ApplicationUserLiteViewModel(ApplicationUser applicationUser) 14 | { 15 | if (applicationUser == null) 16 | throw new ArgumentNullException(nameof(applicationUser)); 17 | 18 | Id = applicationUser.Id; 19 | FirstName = applicationUser.FirstName; 20 | LastName = applicationUser.LastName; 21 | } 22 | 23 | public static ApplicationUserLiteViewModel FromModel(ApplicationUser applicationUser) 24 | { 25 | if (applicationUser == null) 26 | return null; 27 | 28 | return new ApplicationUserLiteViewModel(applicationUser); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/AssetTypeViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class AssetTypeViewModel : LookupValueViewModel 6 | { 7 | public string? Symbol { get; set; } 8 | 9 | public AssetTypeViewModel() { } 10 | 11 | public AssetTypeViewModel(int id, string name, string? symbol = null) 12 | : base(id, name) 13 | { 14 | Symbol = symbol; 15 | } 16 | 17 | public static AssetTypeViewModel? FromModel(AssetType assetType) 18 | { 19 | if (assetType == null) 20 | return null; 21 | 22 | return new AssetTypeViewModel( 23 | assetType.Id, 24 | assetType.Name, 25 | assetType.Symbol); 26 | } 27 | 28 | public static AssetType? ToModel(AssetTypeViewModel viewModel) 29 | { 30 | if (viewModel == null) 31 | return null; 32 | 33 | return new AssetType( 34 | viewModel.Id, 35 | viewModel.Name, 36 | viewModel.Symbol); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/BaseExportRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using DashAccountingSystemV2.BackEnd.Services.Export; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class BaseExportRequestViewModel 7 | { 8 | public Guid TenantId { get; set; } 9 | 10 | [JsonConverter(typeof(JsonStringEnumConverter))] 11 | public ExportFormat ExportFormat { get; set; } 12 | 13 | [JsonConverter(typeof(JsonStringEnumConverter))] 14 | public ExportType ExportType { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/BootstrapResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.ViewModels 2 | { 3 | public class BootstrapResponseViewModel 4 | { 5 | public string ApplicationVersion { get; set; } 6 | 7 | public ApplicationUserLiteViewModel UserInfo { get; set; } 8 | 9 | public IEnumerable Tenants { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/ChangePasswordRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class ChangePasswordRequestViewModel 6 | { 7 | [Required] 8 | [DataType(DataType.Password)] 9 | [Display(Name = "Current password")] 10 | public string OldPassword { get; set; } 11 | 12 | [Required] 13 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 14 | [DataType(DataType.Password)] 15 | [Display(Name = "New password")] 16 | public string NewPassword { get; set; } 17 | 18 | [DataType(DataType.Password)] 19 | [Display(Name = "Confirm new password")] 20 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 21 | public string ConfirmPassword { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/CountryResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class CountryResponseViewModel 6 | { 7 | public int Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | /// 12 | /// ISO-3611-Alpha-2 two letter abbreviation 13 | /// 14 | public string TwoLetterCode { get; set; } 15 | 16 | /// 17 | /// ISO-3611-Alpha-2 two letter abbreviation 18 | /// 19 | public string ThreeLetterCode { get; set; } 20 | 21 | public static CountryResponseViewModel FromModel(Country model) 22 | { 23 | if (model == null) 24 | return null; 25 | 26 | return new CountryResponseViewModel() 27 | { 28 | Id = model.Id, 29 | Name = model.Name, 30 | ThreeLetterCode = model.ThreeLetterCode, 31 | TwoLetterCode = model.TwoLetterCode, 32 | }; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/CustomerLiteResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class CustomerLiteResponseViewModel 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string CustomerNumber { get; set; } 10 | 11 | public string CompanyName { get; set; } 12 | 13 | public string DisplayName { get; set; } 14 | 15 | public static CustomerLiteResponseViewModel FromModel(Customer customer) 16 | { 17 | if (customer == null) 18 | return null; 19 | 20 | return new CustomerLiteResponseViewModel() 21 | { 22 | Id = customer.EntityId, 23 | CustomerNumber = customer.CustomerNumber, 24 | CompanyName = customer.CompanyName, 25 | DisplayName = customer.DisplayName, 26 | }; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/EmployeeLiteResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class EmployeeLiteResponseViewModel 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public uint EmployeeNumber { get; set; } 11 | 12 | public string DisplayName { get; set; } 13 | 14 | public bool IsBillableByDefault { get; set; } 15 | 16 | public decimal? HourlyBillableRate { get; set; } 17 | 18 | public bool IsUser => UserId.HasValue; 19 | 20 | public Guid? UserId { get; set; } 21 | 22 | public static EmployeeLiteResponseViewModel FromModel(Employee employee) 23 | { 24 | if (employee == null) 25 | return null; 26 | 27 | return new EmployeeLiteResponseViewModel() 28 | { 29 | Id = employee.EntityId, 30 | EmployeeNumber = employee.EmployeeNumber, 31 | DisplayName = employee.DisplayName, 32 | UserId = employee.UserId, 33 | IsBillableByDefault = employee.IsBillableByDefault, 34 | HourlyBillableRate = employee.HourlyBillingRate, 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.ViewModels 2 | { 3 | public class ErrorViewModel 4 | { 5 | public string? RequestId { get; set; } 6 | 7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/ExportDescriptorRequestAndResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using DashAccountingSystemV2.BackEnd.Services.Export; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class ExportDescriptorRequestAndResponseViewModel 7 | { 8 | [JsonConverter(typeof(JsonStringEnumConverter))] 9 | public ExportFormat Format { get; set; } 10 | 11 | public string FileName { get; set; } 12 | 13 | public string Token { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/ExportRequestWithDateRangeViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.ViewModels 2 | { 3 | public class ExportRequestWithDateRangeViewModel : BaseExportRequestViewModel 4 | { 5 | public string DateRangeStart { get; set; } 6 | 7 | public string DateRangeEnd { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/ExtendedAssetTypeViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class ExtendedAssetTypeViewModel : AssetTypeViewModel 6 | { 7 | public string Description { get; set; } 8 | 9 | public static new ExtendedAssetTypeViewModel FromModel(AssetType assetType) 10 | { 11 | if (assetType == null) 12 | return null; 13 | 14 | return new ExtendedAssetTypeViewModel() 15 | { 16 | Id = assetType.Id, 17 | Name = assetType.Name, 18 | Description = assetType.Description, 19 | Symbol = assetType.Symbol, 20 | }; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/INumberedJournalEntry.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public interface INumberedJournalEntry 6 | { 7 | uint EntryId { get; } 8 | TransactionStatus Status { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/InvoiceFilterRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Extensions; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class InvoiceFilterRequestViewModel 6 | { 7 | public string? DateRangeStart { get; set; } 8 | 9 | public string? DateRangeEnd { get; set; } 10 | 11 | public string? Customers { get; set; } 12 | 13 | public DateTime? ParsedDateRangeStart => DateRangeStart.TryParseAsDateTime(); 14 | 15 | public DateTime? ParsedDateRangeEnd => DateRangeEnd.TryParseAsDateTime(); 16 | 17 | public IEnumerable? ParsedCustomerIds => Customers.ParseCommaSeparatedGuids(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/InvoicePaymentCreateRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class InvoicePaymentCreateRequestViewModel 7 | { 8 | [Required(ErrorMessage = "Invoice ID is required.")] 9 | public Guid InvoiceId { get; set; } 10 | 11 | [Required(ErrorMessage = "Amount is required.")] 12 | public AmountViewModel PaymentAmount { get; set; } 13 | 14 | public static InvoicePayment ToModel(InvoicePaymentCreateRequestViewModel viewModel) 15 | { 16 | if (viewModel == null) 17 | return null; 18 | 19 | return new InvoicePayment() 20 | { 21 | InvoiceId = viewModel.InvoiceId, 22 | Amount = viewModel.PaymentAmount?.Amount ?? 0.0m, 23 | }; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/InvoicePaymentResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class InvoicePaymentResponseViewModel 6 | { 7 | public Guid InvoiceId { get; set; } 8 | 9 | public InvoiceLiteResponseViewModel Invoice { get; set; } 10 | 11 | public AmountViewModel PaymentAmount { get; set; } 12 | 13 | public static InvoicePaymentResponseViewModel FromModel(InvoicePayment model, AssetType paymentAssetType) 14 | { 15 | if (model == null || paymentAssetType == null) 16 | return null; 17 | 18 | return new InvoicePaymentResponseViewModel() 19 | { 20 | InvoiceId = model.InvoiceId, 21 | Invoice = InvoiceLiteResponseViewModel.FromModel(model.Invoice), 22 | PaymentAmount = new AmountViewModel(model.Amount, paymentAssetType), 23 | }; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/InvoiceStatusUpdateRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.Json.Serialization; 3 | using DashAccountingSystemV2.BackEnd.Models; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.ViewModels 6 | { 7 | public class InvoiceStatusUpdateRequestViewModel 8 | { 9 | [Required(ErrorMessage = "Tenant ID is required.")] 10 | public Guid TenantId { get; set; } 11 | 12 | [Required(ErrorMessage = "Invoice Number is required.")] 13 | public uint InvoiceNumber { get; set; } 14 | 15 | [Required(ErrorMessage = "Status is required.")] 16 | [JsonConverter(typeof(JsonStringEnumConverter))] 17 | public InvoiceStatus Status { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/InvoiceTermsViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class InvoiceTermsViewModel 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public Guid? TenantId { get; set; } 10 | 11 | public bool IsCustom => TenantId.HasValue; 12 | 13 | public string Name { get; set; } 14 | 15 | public ushort? DueInDays { get; set; } 16 | 17 | public ushort? DueOnDayOfMonth { get; set; } 18 | 19 | public ushort? DueNextMonthThreshold { get; set; } 20 | 21 | public static InvoiceTermsViewModel FromModel(InvoiceTerms model) 22 | { 23 | if (model == null) 24 | return null; 25 | 26 | return new InvoiceTermsViewModel() 27 | { 28 | Id = model.Id, 29 | TenantId = model.TenantId, 30 | Name = model.Name, 31 | DueInDays = model.DueInDays, 32 | DueNextMonthThreshold = model.DueNextMonthThreshold, 33 | DueOnDayOfMonth = model.DueOnDayOfMonth, 34 | }; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/InvoiceUpdateRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class InvoiceUpdateRequestViewModel : InvoiceCreateRequestViewModel 7 | { 8 | [Required(ErrorMessage = "Invoice ID is required.")] 9 | public Guid Id { get; set; } 10 | 11 | [Required(ErrorMessage = "Invoice Number is required.")] 12 | public uint InvoiceNumber { get; set; } 13 | 14 | public static Invoice ToModel(InvoiceUpdateRequestViewModel viewModel, Guid contextUserId) 15 | { 16 | if (viewModel == null) 17 | return null; 18 | 19 | var result = InvoiceCreateRequestViewModel.ToModel(viewModel, contextUserId); 20 | result.Id = viewModel.Id; 21 | result.InvoiceNumber = viewModel.InvoiceNumber; 22 | 23 | return result; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/JournalEntryAccountCreateRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class JournalEntryAccountCreateRequestViewModel 7 | { 8 | [Required] 9 | public Guid AccountId { get; set; } 10 | 11 | [Required] 12 | public AmountViewModel Amount { get; set; } 13 | 14 | public static JournalEntryAccount ToModel(JournalEntryAccountCreateRequestViewModel viewModel) 15 | { 16 | if (viewModel == null || 17 | !viewModel.Amount.HasValue) 18 | return null; 19 | 20 | return new JournalEntryAccount( 21 | viewModel.AccountId, 22 | viewModel.Amount.Amount ?? 0.0m, 23 | viewModel.Amount.AssetType.Id); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/JournalEntryAccountResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class JournalEntryAccountResponseViewModel 6 | { 7 | public Guid AccountId { get; set; } 8 | 9 | public uint AccountNumber { get; set; } 10 | 11 | public string AccountName { get; set; } 12 | 13 | public AmountViewModel Amount { get; set; } 14 | 15 | public static JournalEntryAccountResponseViewModel? FromModel(JournalEntryAccount journalEntryAccount) 16 | { 17 | if (journalEntryAccount == null) 18 | return null; 19 | 20 | return new JournalEntryAccountResponseViewModel() 21 | { 22 | AccountId = journalEntryAccount.AccountId, 23 | AccountName = journalEntryAccount.Account.Name, 24 | AccountNumber = journalEntryAccount.Account.AccountNumber, 25 | Amount = new AmountViewModel(journalEntryAccount.Amount, journalEntryAccount.AssetType), 26 | }; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/JournalEntryLiteResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | using DashAccountingSystemV2.BackEnd.ViewModels.Serialization; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.ViewModels 6 | { 7 | public class JournalEntryLiteResponseViewModel : INumberedJournalEntry 8 | { 9 | public Guid Id { get; set; } 10 | 11 | public uint EntryId { get; set; } 12 | 13 | [JsonConverter(typeof(JsonStringEnumConverter))] 14 | public TransactionStatus Status { get; set; } 15 | 16 | [JsonConverter(typeof(JsonDateConverter))] 17 | public DateTime EntryDate { get; set; } 18 | 19 | [JsonConverter(typeof(JsonDateConverter))] 20 | public DateTime? PostDate { get; set; } 21 | 22 | public string Description { get; set; } 23 | 24 | public static JournalEntryLiteResponseViewModel FromModel(JournalEntry model) 25 | { 26 | if (model == null) 27 | return null; 28 | 29 | return new JournalEntryLiteResponseViewModel() 30 | { 31 | Id = model.Id, 32 | EntryId = model.EntryId, 33 | EntryDate = model.EntryDate, 34 | Description = model.Description, 35 | PostDate = model.PostDate, 36 | Status = model.Status, 37 | }; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/LookupValueViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DashAccountingSystemV2.BackEnd.ViewModels 2 | { 3 | public class LookupValueViewModel 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | 8 | public LookupValueViewModel() { } 9 | 10 | public LookupValueViewModel(int id, string name) 11 | { 12 | Id = id; 13 | Name = name; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/LookupsViewModel.cs: -------------------------------------------------------------------------------- 1 | using TimeZone = DashAccountingSystemV2.BackEnd.Models.TimeZone; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class LookupsViewModel 6 | { 7 | public IEnumerable AccountTypes { get; set; } 8 | public IEnumerable AccountSubTypes { get; set; } 9 | public IEnumerable AssetTypes { get; set; } 10 | public IEnumerable PaymentMethods { get; set; } 11 | public IEnumerable TimeZones { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/PostJournalEntryRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.Json.Serialization; 3 | using DashAccountingSystemV2.BackEnd.ViewModels.Serialization; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.ViewModels 6 | { 7 | public class PostJournalEntryRequestViewModel 8 | { 9 | [DataType(DataType.Date)] 10 | [Required] 11 | [JsonConverter(typeof(JsonDateConverter))] 12 | public DateTime PostDate { get; set; } 13 | 14 | public string? Note { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/ProductLiteResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class ProductLiteResponseViewModel 7 | { 8 | public Guid Id { get; set; } 9 | 10 | public Guid CategoryId { get; set; } 11 | 12 | public string Category { get; set; } 13 | 14 | [JsonConverter(typeof(JsonStringEnumConverter))] 15 | public ProductType Type { get; set; } 16 | 17 | public string SKU { get; set; } 18 | 19 | public string Name { get; set; } 20 | 21 | public decimal? SalesPriceOrRate { get; set; } 22 | 23 | public static ProductLiteResponseViewModel FromModel(Product product) 24 | { 25 | if (product == null) 26 | return null; 27 | 28 | return new ProductLiteResponseViewModel() 29 | { 30 | Id = product.Id, 31 | CategoryId = product.CategoryId, 32 | Category = product.Category?.Name, 33 | Type = product.Type, 34 | SKU = product.SKU, 35 | Name = product.Name, 36 | SalesPriceOrRate = product.SalesPriceOrRate, 37 | }; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/RegionResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class RegionResponseViewModel 6 | { 7 | public int Id { get; set; } 8 | 9 | /// 10 | /// What "kind" of region this is, e.g. "State", "Province", etc. 11 | /// 12 | public string Label { get; set; } 13 | 14 | public string Name { get; set; } 15 | 16 | /// 17 | /// ISO 3166-2 Code for the Region (only the regional/local segment; not including the parent Country code) 18 | /// 19 | /// 20 | /// 21 | /// 22 | public string Code { get; set; } 23 | 24 | public static RegionResponseViewModel FromModel(Region model) 25 | { 26 | if (model == null) 27 | return null; 28 | 29 | return new RegionResponseViewModel() 30 | { 31 | Id = model.Id, 32 | Label = model.Label, 33 | Name = model.Name, 34 | Code = model.Code, 35 | }; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/ReportDatesResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using DashAccountingSystemV2.BackEnd.ViewModels.Serialization; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class ReportDatesResponseViewModel 7 | { 8 | [JsonConverter(typeof(JsonDateConverter))] 9 | public DateTime DateRangeStart { get; set; } 10 | 11 | [JsonConverter(typeof(JsonDateConverter))] 12 | public DateTime DateRangeEnd { get; set; } 13 | 14 | public ReportDatesResponseViewModel() { } 15 | 16 | public ReportDatesResponseViewModel(DateTime dateRangeStart, DateTime dateRangeEnd) 17 | { 18 | DateRangeStart = dateRangeStart; 19 | DateRangeEnd = dateRangeEnd; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/Serialization/JsonDateConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace DashAccountingSystemV2.BackEnd.ViewModels.Serialization 6 | { 7 | /// 8 | /// Custom JSON Converter for s that serializes the values as YYYY-MM-DD strings. 9 | /// 10 | public class JsonDateConverter : JsonConverter 11 | { 12 | private static readonly CultureInfo _enUS = new CultureInfo("en-US"); 13 | 14 | public override DateTime Read( 15 | ref Utf8JsonReader reader, 16 | Type typeToConvert, 17 | JsonSerializerOptions options) 18 | { 19 | var value = reader.GetString(); 20 | 21 | if (!string.IsNullOrWhiteSpace(value) && 22 | DateTime.TryParseExact(value, "yyyy-MM-dd", _enUS, DateTimeStyles.None, out var date)) 23 | { 24 | return date; 25 | } 26 | 27 | throw new JsonException("Invalid date format"); 28 | } 29 | 30 | public override void Write( 31 | Utf8JsonWriter writer, 32 | DateTime dateTimeValue, 33 | JsonSerializerOptions options) 34 | => writer.WriteStringValue(dateTimeValue.ToString("yyyy-MM-dd", _enUS)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/Serialization/JsonNullableTimeSpanConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels.Serialization 5 | { 6 | public class JsonNullableTimeSpanConverter : JsonConverter 7 | { 8 | public override TimeSpan? Read( 9 | ref Utf8JsonReader reader, 10 | Type typeToConvert, 11 | JsonSerializerOptions options) 12 | { 13 | var value = reader.GetString(); 14 | 15 | if (value == null) 16 | return null; 17 | 18 | if (!string.IsNullOrWhiteSpace(value) && 19 | TimeSpan.TryParse(value, out var timeSpan)) 20 | return timeSpan; 21 | 22 | throw new JsonException("Invalid time span format"); 23 | } 24 | 25 | public override void Write( 26 | Utf8JsonWriter writer, 27 | TimeSpan? value, 28 | JsonSerializerOptions options) 29 | { 30 | if (!value.HasValue) 31 | { 32 | writer.WriteNullValue(); 33 | return; 34 | } 35 | 36 | writer.WriteStringValue(value.Value.ToString("hh:mm:ss")); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/TenantViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class TenantViewModel 6 | { 7 | public Guid Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public AssetTypeViewModel DefaultAssetType { get; set; } 12 | 13 | public static TenantViewModel? FromModel(Tenant? tenant) 14 | { 15 | if (tenant == null) 16 | return null; 17 | 18 | return new TenantViewModel() 19 | { 20 | Id = tenant.Id, 21 | Name = tenant.Name, 22 | DefaultAssetType = AssetTypeViewModel.FromModel(tenant.DefaultAssetType), 23 | }; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/TimeActivitiesReportResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Models; 2 | 3 | namespace DashAccountingSystemV2.BackEnd.ViewModels 4 | { 5 | public class TimeActivitiesReportResponseViewModel 6 | { 7 | public AssetTypeViewModel DefaultAssetType { get; set; } 8 | 9 | public ReportDatesResponseViewModel ReportDates { get; set; } 10 | 11 | public IEnumerable TimeActivities { get; set; } 12 | 13 | public static TimeActivitiesReportResponseViewModel FromModel(TimeActivityDetailsReportDto reportData) 14 | { 15 | if (reportData == null) 16 | return null; 17 | 18 | return new TimeActivitiesReportResponseViewModel() 19 | { 20 | DefaultAssetType = AssetTypeViewModel.FromModel(reportData.Tenant.DefaultAssetType), 21 | ReportDates = new ReportDatesResponseViewModel(reportData.DateRange.DateRangeStart, reportData.DateRange.DateRangeEnd), 22 | TimeActivities = reportData.TimeActivities 23 | ?.Select(TimeActivityResponseViewModel.FromModel) 24 | ?? Enumerable.Empty(), 25 | }; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/BackEnd/ViewModels/TimeActivityUpdateRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.BackEnd.ViewModels 5 | { 6 | public class TimeActivityUpdateRequestViewModel : TimeActivityCreateRequestViewModel 7 | { 8 | [Required] 9 | public Guid Id { get; set; } 10 | 11 | public static TimeActivity ToModel(TimeActivityUpdateRequestViewModel viewModel) 12 | { 13 | if (viewModel == null) 14 | return null; 15 | 16 | var model = TimeActivityCreateRequestViewModel.ToModel(viewModel); 17 | model.Id = viewModel.Id; 18 | 19 | return model; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/BackEnd/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=localhost;Port=5432;User Id=dash_accounting;Database=dash_accounting;Application Name=Dash Accounting System v2.1" 4 | }, 5 | 6 | "Logging": { 7 | "LogLevel": { 8 | "Default": "Information", 9 | "Microsoft.AspNetCore": "Warning" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/BackEnd/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedHosts": "*", 3 | 4 | "ConnectionStrings": { 5 | "DefaultConnection": "##TODO: Add Connection String##" 6 | }, 7 | 8 | "Logging": { 9 | "LogLevel": { 10 | "Default": "Information", 11 | "Microsoft.AspNetCore": "Warning" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/FrontEnd/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "localhost (Chrome)", 8 | "url": "https://localhost:3000", 9 | "webRoot": "${workspaceFolder}" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/FrontEnd/DashAccountingSystemV2.FrontEnd.esproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | npm start 4 | src\ 5 | Jest 6 | 7 | false 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/FrontEnd/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/FrontEnd/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DashSoftwareSolutions/DashAccountingSystemV2/822d8a15f5c9f5bf90317eac3a64f17301043e41/src/FrontEnd/public/favicon.ico -------------------------------------------------------------------------------- /src/FrontEnd/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DashSoftwareSolutions/DashAccountingSystemV2/822d8a15f5c9f5bf90317eac3a64f17301043e41/src/FrontEnd/public/logo192.png -------------------------------------------------------------------------------- /src/FrontEnd/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DashSoftwareSolutions/DashAccountingSystemV2/822d8a15f5c9f5bf90317eac3a64f17301043e41/src/FrontEnd/public/logo512.png -------------------------------------------------------------------------------- /src/FrontEnd/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/FrontEnd/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/applicationRedux/application.actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BootstrapInfo, 3 | NavigationSection, 4 | Tenant, 5 | } from '../../common/models'; 6 | import IAction from '../globalReduxStore/action.interface'; 7 | import ActionType from '../globalReduxStore/actionType'; 8 | 9 | export interface RequestApplicationVersionAction extends IAction { 10 | type: ActionType.REQUEST_APPLICATION_VERSION; 11 | } 12 | 13 | export interface ReceiveApplicationVersionAction extends IAction { 14 | type: ActionType.RECEIVE_APPLICATION_VERSION; 15 | applicationVersion: string; 16 | } 17 | 18 | export interface RequestBootstrapInfoAction extends IAction { 19 | type: ActionType.REQUEST_BOOTSTRAP_INFO; 20 | } 21 | 22 | export interface ReceiveBootstrapInfoAction extends IAction { 23 | type: ActionType.RECEIVE_BOOTSTRAP_INFO; 24 | bootstrapInfo: BootstrapInfo; 25 | } 26 | 27 | export interface SelectTenantAction extends IAction { 28 | type: ActionType.SELECT_TENANT; 29 | tenant: Tenant; 30 | } 31 | 32 | export interface SetMainContentContainerHeightAction extends IAction { 33 | type: ActionType.SET_MAIN_CONTENT_CONTAINER_HEIGHT; 34 | height: number; 35 | } 36 | 37 | export interface SetNavigationSectionAction extends IAction { 38 | type: ActionType.SET_NAVIGATION_SECTION; 39 | navigationSection: NavigationSection | null; 40 | } 41 | 42 | export type KnownAction = 43 | RequestApplicationVersionAction | 44 | ReceiveApplicationVersionAction | 45 | RequestBootstrapInfoAction | 46 | ReceiveBootstrapInfoAction | 47 | SelectTenantAction | 48 | SetMainContentContainerHeightAction | 49 | SetNavigationSectionAction; 50 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/applicationRedux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './application.actionCreators'; 2 | export type { ApplicationState as state } from './application.reducer'; 3 | export { default as reducer } from './application.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/authentication/models/accessTokenResponse.model.ts: -------------------------------------------------------------------------------- 1 | import { DateTimeString } from '../../../common/models'; 2 | 3 | export default interface AccessTokenResponse { 4 | tokenType: string; 5 | accessToken: string; 6 | expires?: DateTimeString; 7 | expiresIn: number; 8 | refreshToken: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/authentication/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as AccessTokenResponse } from './accessTokenResponse.model'; 2 | export type { default as LoginRequest } from './loginRequest.model'; 3 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/authentication/models/loginRequest.model.ts: -------------------------------------------------------------------------------- 1 | export default interface LoginRequest { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/authentication/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './authentication.actionCreators'; 2 | export type { AuthenticationState as state } from './authentication.reducer'; 3 | export { default as reducer } from './authentication.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/export/export.actionCreators.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux'; 2 | import { KnownAction } from './export.actions'; 3 | import { apiErrorHandler } from '../../common/utilities/errorHandling'; 4 | import { AppThunkAction } from '../globalReduxStore'; 5 | import IAction from '../globalReduxStore/action.interface'; 6 | import ActionType from '../globalReduxStore/actionType'; 7 | 8 | const actionCreators = { 9 | reportError: (apiResponse: Response): AppThunkAction => (dispatch) => { 10 | apiErrorHandler.handleError(apiResponse, dispatch as Dispatch); 11 | 12 | dispatch({ 13 | type: ActionType.RECEIVE_EXPORT_DOWNLOAD_ERROR, 14 | error: 'Error downloading requested data export', 15 | }); 16 | }, 17 | 18 | reset: (): AppThunkAction => (dispatch) => { 19 | dispatch({ type: ActionType.RESET_EXPORT_STORE }); 20 | }, 21 | }; 22 | 23 | export default actionCreators; 24 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/export/export.actions.ts: -------------------------------------------------------------------------------- 1 | import { ExportDownloadInfo } from '../../common/models'; 2 | import IAction from '../globalReduxStore/action.interface'; 3 | import ActionType from "../globalReduxStore/actionType"; 4 | 5 | // this is a common action type that other stores may dispatch to expose export/download functionality for their feature area 6 | export interface RequestDownloadAction extends IAction { 7 | type: ActionType.REQUEST_EXPORT_DOWNLOAD; 8 | } 9 | 10 | // this is a common action type that other stores may dispatch to expose export/download functionality for their feature area 11 | export interface ReceiveDownloadErrorAction extends IAction { 12 | type: ActionType.RECEIVE_EXPORT_DOWNLOAD_ERROR; 13 | error: string; 14 | } 15 | 16 | // this is a common action type that other stores may dispatch to expose export/download functionality for their feature area 17 | export interface ReceiveDownloadInfoAction extends IAction { 18 | type: ActionType.RECEIVE_EXPORT_DOWNLOAD_INFO; 19 | downloadInfo: ExportDownloadInfo; 20 | } 21 | 22 | export interface ResetExportStoreAction extends IAction { 23 | type: ActionType.RESET_EXPORT_STORE; 24 | } 25 | 26 | export type KnownAction = RequestDownloadAction | 27 | ReceiveDownloadErrorAction | 28 | ReceiveDownloadInfoAction | 29 | ResetExportStoreAction; 30 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/export/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './export.actionCreators'; 2 | export type { ExportState as state } from './export.reducer'; 3 | export { default as reducer } from './export.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/globalReduxStore/action.interface.ts: -------------------------------------------------------------------------------- 1 | import ActionType from './actionType'; 2 | 3 | /** 4 | * Interface for Actions dispatched by Redux 5 | */ 6 | export default interface IAction { 7 | type: ActionType; 8 | } 9 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/globalReduxStore/sessionStorageMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Dispatch, 3 | Middleware, 4 | } from 'redux'; 5 | import { RootState } from '.'; 6 | import IAction from './action.interface'; 7 | import ActionType from './actionType'; 8 | import { AUTH_SESSION_STORAGE_KEY } from '../../common/constants'; 9 | import { encodeJsonObjectAsBase64 } from '../../common/utilities/encoding'; 10 | 11 | const authenticationSessionStorageMiddleware: Middleware< 12 | Dispatch, 13 | RootState, 14 | Dispatch 15 | > = storeApi => next => action => { 16 | switch (action.type) { 17 | case ActionType.RECEIVE_SUCCESSFUL_LOGIN_RESPONSE: 18 | case ActionType.RECEIVE_UPDATED_TOKENS: 19 | window.sessionStorage.setItem(AUTH_SESSION_STORAGE_KEY, encodeJsonObjectAsBase64(action.accessTokenResponse)); 20 | break; 21 | 22 | case ActionType.RECEIVE_LOGOUT_RESPONSE: 23 | window.sessionStorage.removeItem(AUTH_SESSION_STORAGE_KEY); 24 | break; 25 | } 26 | 27 | return next(action); 28 | } 29 | 30 | export default authenticationSessionStorageMiddleware; 31 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './app'; 2 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/lookupValues/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './lookupValues.actionCreators'; 2 | export type { LookupValuesState as state } from './lookupValues.reducer'; 3 | export { default as reducer } from './lookupValues.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/lookupValues/lookupValues.actions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AssetType, 3 | TimeZone 4 | } from '../../common/models'; 5 | import { 6 | AccountSubType, 7 | AccountType, 8 | } from '../../features/accounting/chart-of-accounts/models'; 9 | import { PaymentMethod } from '../../features/invoicing/payments/models'; 10 | import IAction from '../globalReduxStore/action.interface'; 11 | import ActionType from '../globalReduxStore/actionType'; 12 | 13 | export interface RequestLookupValuesAction extends IAction { 14 | type: ActionType.REQUEST_LOOKUPS; 15 | } 16 | 17 | export interface ReceiveLookupValuesAction extends IAction { 18 | type: ActionType.RECEIVE_LOOKUPS; 19 | accountTypes: AccountType[]; 20 | accountSubTypes: AccountSubType[]; 21 | assetTypes: AssetType[]; 22 | paymentMethods: PaymentMethod[]; 23 | timeZones: TimeZone[]; 24 | } 25 | 26 | export type KnownAction = RequestLookupValuesAction | ReceiveLookupValuesAction; 27 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/navMenu.css: -------------------------------------------------------------------------------- 1 | a.navbar-brand { 2 | white-space: normal; 3 | text-align: center; 4 | word-break: break-all; 5 | } 6 | 7 | .box-shadow { 8 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 9 | } 10 | 11 | .dotnet-8-badge { 12 | background-color: rgb(81 43 212); 13 | border: solid 1px rgb(81 43 212); 14 | color: #fff; 15 | font-weight: 600; 16 | margin-top: 2px; 17 | vertical-align: text-top; 18 | } 19 | 20 | .app-nav-link { 21 | border-radius: 0px; 22 | padding-left: 0px !important; 23 | padding-right: 0px !important; 24 | } 25 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/navMenuItem.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { NavItem } from 'reactstrap'; 3 | import LinkButton from '../common/components/linkButton'; 4 | import { NavigationSection } from '../common/models'; 5 | 6 | type PropTypes = { 7 | children: React.ReactNode; 8 | currentlyActiveSection: NavigationSection | null; 9 | onClick: (navigationSection: NavigationSection, path: string) => void; 10 | section: NavigationSection; 11 | to: string; 12 | } 13 | 14 | function NavMenuItem({ 15 | children, 16 | currentlyActiveSection, 17 | onClick, 18 | section, 19 | to, 20 | }: PropTypes) { 21 | const handleClick = () => onClick(section, to); 22 | 23 | return ( 24 | 25 | 29 | {children} 30 | 31 | 32 | ); 33 | } 34 | 35 | export default NavMenuItem; 36 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/notifications/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './notifications.actionCreators'; 2 | export { default as NotificationLevel } from './notificationLevel'; 3 | export type { SystemNotificationsState as state } from './notifications.reducer'; 4 | export { default as reducer } from './notifications.reducer'; 5 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/notifications/notificationLevel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * "Level" (importance/severity) of the Notification/Alert, corrsponding to a Bootstrap theme color. 3 | */ 4 | enum NotificationLevel { 5 | Danger = 'danger', 6 | Info = 'info', 7 | Success = 'success', 8 | Warning = 'warning', 9 | } 10 | 11 | export default NotificationLevel; 12 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/notifications/notifications.actionCreators.ts: -------------------------------------------------------------------------------- 1 | import NotificationLevel from './notificationLevel'; 2 | import { 3 | DismissAlertAction, 4 | ShowAlertAction 5 | } from './notifications.actions'; 6 | import { AppThunkAction } from '../globalReduxStore'; 7 | import ActionType from '../globalReduxStore/actionType'; 8 | 9 | const actionCreators = { 10 | showAlert: ( 11 | color: NotificationLevel, 12 | content: string | React.ReactNode, 13 | autoDismiss: number | boolean, 14 | ): AppThunkAction => (dispatch) => { 15 | dispatch({ 16 | type: ActionType.SHOW_ALERT, 17 | autoDismiss, 18 | color, 19 | content, 20 | }); 21 | }, 22 | 23 | dismissAlert: (alertId: string): AppThunkAction => (dispatch) => { 24 | dispatch({ 25 | type: ActionType.DISMISS_ALERT, 26 | alertId, 27 | }); 28 | }, 29 | }; 30 | 31 | export default actionCreators; 32 | -------------------------------------------------------------------------------- /src/FrontEnd/src/app/notifications/notifications.actions.ts: -------------------------------------------------------------------------------- 1 | import NotificationLevel from './notificationLevel'; 2 | import IAction from '../globalReduxStore/action.interface'; 3 | import ActionType from '../globalReduxStore/actionType'; 4 | 5 | /** 6 | * Action to show an alert in the System Notifications area 7 | */ 8 | export interface ShowAlertAction extends IAction { 9 | type: ActionType.SHOW_ALERT; 10 | 11 | /** 12 | * Controls the auto-dismiss behavior of the alert.
13 | * * Specifying a number for this property intructs the alert to auto-dismiss after specified timeout.
14 | * * Boolean `true` instructs the alert auto-dismiss after default timeout.
15 | * * Boolean `false` means don't auto-dismiss the alert. 16 | */ 17 | autoDismiss: number | boolean; 18 | 19 | /** 20 | * The Bootstrap theme color for the alert. 21 | */ 22 | color: NotificationLevel; 23 | 24 | /** 25 | * Content of the alert. Use a string for a simple message, or pass in JSX markup. 26 | */ 27 | content: string | React.ReactNode; 28 | } 29 | 30 | /** 31 | * Action to manually dismiss an alert 32 | */ 33 | export interface DismissAlertAction extends IAction { 34 | type: ActionType.DISMISS_ALERT; 35 | 36 | /** 37 | * ID of the target alert. 38 | */ 39 | alertId: string; 40 | } 41 | 42 | export type KnownAction = ShowAlertAction | DismissAlertAction; 43 | -------------------------------------------------------------------------------- /src/FrontEnd/src/assets/dash-hero-image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DashSoftwareSolutions/DashAccountingSystemV2/822d8a15f5c9f5bf90317eac3a64f17301043e41/src/FrontEnd/src/assets/dash-hero-image.jpeg -------------------------------------------------------------------------------- /src/FrontEnd/src/common/components/dateRangeMacroSelector.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent } from 'react'; 2 | import { 3 | DateRange, 4 | DateRangeMacroType, 5 | } from '../models'; 6 | import { 7 | computeDateRangeFromMacro, 8 | dateRangeMacroOptions, 9 | } from '../utilities/dateRangeMacros'; 10 | 11 | function DateRangeMacroSelector({ 12 | id, 13 | onChange, 14 | value = DateRangeMacroType.Custom, 15 | }: { 16 | id: string; 17 | onChange: (selectedMacro: DateRangeMacroType, dateRange: DateRange) => void; 18 | value?: DateRangeMacroType; 19 | }) { 20 | const onSelectionChanged = (event: ChangeEvent) => { 21 | const selectElement = event.target; 22 | const selectedOption = selectElement.selectedOptions[0]; 23 | const selectedMacro = selectedOption.value as DateRangeMacroType; 24 | const dateRange = computeDateRangeFromMacro(selectedMacro); 25 | onChange(selectedMacro, dateRange); 26 | }; 27 | 28 | return ( 29 | 46 | ); 47 | } 48 | 49 | export default DateRangeMacroSelector 50 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/components/linkButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'reactstrap'; 3 | 4 | type PropTypes = { 5 | className?: string; 6 | onClick: React.MouseEventHandler; 7 | children?: React.ReactNode; 8 | style?: object; 9 | }; 10 | 11 | function LinkButton({ 12 | className, 13 | children, 14 | onClick, 15 | style, 16 | }: PropTypes) { 17 | return ( 18 | 26 | ); 27 | } 28 | 29 | export default LinkButton; 30 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/components/loader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type PropTypes = { 4 | id?: string; 5 | topOffset?: number; 6 | } 7 | 8 | function Loader({ 9 | id = 'app_loading_spinner', 10 | topOffset = 0, 11 | }: PropTypes) { 12 | return ( 13 |
21 |
29 | Loading... 30 |
31 |
32 | ); 33 | } 34 | 35 | export default Loader; 36 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/components/mainPageContent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Scrollbars } from 'react-custom-scrollbars'; 3 | import { 4 | ConnectedProps, 5 | connect, 6 | } from 'react-redux'; 7 | import { RootState } from '../../app/globalReduxStore'; 8 | 9 | type OwnPropTypes = { 10 | id: string; 11 | children: React.ReactNode; 12 | }; 13 | 14 | const mapStateToProps = (state: RootState) => ({ 15 | mainContentContainerHeight: state.application.mainContentContainerHeight, 16 | }); 17 | 18 | const connector = connect(mapStateToProps); 19 | 20 | type ReduxPropTypes = ConnectedProps; 21 | 22 | type PropTypes = ReduxPropTypes & OwnPropTypes; 23 | 24 | function MainPageContent({ 25 | children, 26 | id, 27 | mainContentContainerHeight, 28 | }: PropTypes) { 29 | return ( 30 |
31 | 35 |
36 | {children} 37 |
38 |
39 |
40 | ); 41 | } 42 | 43 | export default connector(MainPageContent); 44 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/components/transactionStatusLabel.tsx: -------------------------------------------------------------------------------- 1 | import { Alert } from 'reactstrap'; 2 | import { TransactionStatus } from '../models'; 3 | 4 | function TransactionStatusLabel({ 5 | status, 6 | }: { 7 | status: TransactionStatus; 8 | }) { 9 | let color: string; 10 | 11 | switch (status) { 12 | case TransactionStatus.Pending: 13 | color = 'warning'; 14 | break; 15 | case TransactionStatus.Posted: 16 | color = 'success'; 17 | break; 18 | } 19 | 20 | return ( 21 | 29 | {status} 30 | 31 | ); 32 | } 33 | 34 | export default TransactionStatusLabel; 35 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Amount, 3 | AmountType, 4 | AssetType 5 | } from './models'; 6 | 7 | export const AUTH_SESSION_STORAGE_KEY = 'DashAccountingSystem.Auth'; 8 | 9 | export const DEFAULT_ASSET_TYPE: AssetType = { 10 | id: 1, 11 | name: 'USD', 12 | symbol: '$', 13 | }; 14 | 15 | export const DEFAULT_AMOUNT: Amount = { 16 | amount: 0, 17 | amountAsString: '0.00', 18 | amountType: AmountType.Debit, 19 | assetType: DEFAULT_ASSET_TYPE, 20 | }; 21 | 22 | export const DEFAULT_INVOICE_TERMS: string = 'Net 30'; 23 | 24 | export const DEFAULT_SYSTEM_NOTIFICATION_ALERT_TIMEOUT: number = 4000; 25 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/logging/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as ILogger } from './logger.interface'; 2 | export { default as Logger } from './logger'; -------------------------------------------------------------------------------- /src/FrontEnd/src/common/logging/logger.interface.ts: -------------------------------------------------------------------------------- 1 | export default interface ILogger { 2 | trace(format: string, ...args: unknown[]): void; 3 | debug(format: string, ...args: unknown[]): void; 4 | info(format: string, ...args: unknown[]): void; 5 | warn(format: string, ...args: unknown[]): void; 6 | error(format: string, ...args: unknown[]): void; 7 | } -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/address.model.ts: -------------------------------------------------------------------------------- 1 | import AddressType from './addressType.model'; 2 | import Country from './country.model'; 3 | import Region from './region.model'; 4 | 5 | export default interface Address { 6 | id: string; // GUID 7 | addressType: AddressType; 8 | streetAddress1: string; 9 | streetAddress2: string; 10 | city: string; 11 | region: Region; 12 | postalCode: string; 13 | country: Country; 14 | } 15 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/addressType.model.ts: -------------------------------------------------------------------------------- 1 | enum AddressType { 2 | Home = 'Home', 3 | Billing = 'Billing', 4 | Shipping = 'Shipping', 5 | } 6 | 7 | export default AddressType; -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/amount.model.ts: -------------------------------------------------------------------------------- 1 | import AmountType from './amountType.model'; 2 | import AssetType from './assetType.model'; 3 | 4 | export default interface Amount { 5 | amount: number | null; 6 | amountAsString?: string | null; 7 | assetType: AssetType | null; 8 | amountType: AmountType | null; // "Debit" or "Credit" 9 | } 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/amountType.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The type of an accounting amount (Debit or Credit) 3 | */ 4 | enum AmountType { 5 | Debit = 'Debit', 6 | Credit = 'Credit', 7 | } 8 | 9 | export default AmountType; 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/apiErrorResponse.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * API Error Response object 3 | * (confirming to the {@link https://datatracker.ietf.org/doc/html/rfc7807|RFC 7807} / {@link https://datatracker.ietf.org/doc/html/rfc9457|RFC 9457} 4 | * Problem Details specification) 5 | */ 6 | export default interface ApiErrorResponse { 7 | detail: string; 8 | errors?: Record; 9 | instance: string; 10 | status: number; 11 | title: string; 12 | traceId?: string; 13 | type: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/assetType.model.ts: -------------------------------------------------------------------------------- 1 | import LookupValue from './lookupValue.model'; 2 | 3 | export default interface AssetType extends LookupValue { 4 | symbol: string | null; 5 | description?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/bootstrapInfo.model.ts: -------------------------------------------------------------------------------- 1 | import Tenant from './tenant.model'; 2 | import UserLite from './userLite.model'; 3 | 4 | export default interface BootstrapInfo { 5 | applicationVersion: string; 6 | userInfo: UserLite; 7 | tenants: Tenant[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/country.model.ts: -------------------------------------------------------------------------------- 1 | export default interface Country { 2 | id: number; // integer 3 | name: string; 4 | twoLetterCode: string; 5 | threeLetterCode: string; 6 | } -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/dateRange.model.ts: -------------------------------------------------------------------------------- 1 | import DateTimeString from './dateTimeString.model'; 2 | 3 | export default interface DateRange { 4 | /** 5 | * Range Start Date in YYYY-MM-DD format. 6 | */ 7 | dateRangeStart: DateTimeString; 8 | /** 9 | * Range End Date in in YYYY-MM-DD format. 10 | */ 11 | dateRangeEnd: DateTimeString; 12 | } 13 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/dateRangeMacroType.model.ts: -------------------------------------------------------------------------------- 1 | enum DateRangeMacroType { 2 | All = 'All', 3 | Custom = 'Custom', 4 | Today = 'Today', 5 | ThisWeek = 'ThisWeek', 6 | ThisWeekToDate = 'ThisWeekToDate', 7 | ThisMonth = 'ThisMonth', 8 | ThisMonthToDate = 'ThisMonthToDate', 9 | ThisQuarter = 'ThisQuarter', 10 | ThisQuarterToDate = 'ThisQuarterToDate', 11 | ThisYear = 'ThisYear', 12 | ThisYearToDate = 'ThisYearToDate', 13 | ThisYearToLastMonth = 'ThisYearToLastMonth', 14 | Yesterday = 'Yesterday', 15 | LastWeek = 'LastWeek', 16 | LastWeekToDate = 'LastWeekToDate', 17 | LastMonth = 'LastMonth', 18 | LastMonthToDate = 'LastMonthToDate', 19 | LastQuarter = 'LastQuarter', 20 | LastQuarterToDate = 'LastQuarterToDate', 21 | LastYear = 'LastYear', 22 | LastYearToDate = 'LastYearToDate', 23 | Since30DaysAgo = 'Since30DaysAgo', 24 | Since60DaysAgo = 'Since60DaysAgo', 25 | Since90DaysAgo = 'Since90DaysAgo', 26 | Since365DaysAgo = 'Since365DaysAgo', 27 | NextWeek = 'NextWeek', 28 | Next4Weeks = 'Next4Weeks', 29 | NextMonth = 'NextMonth', 30 | NextQuarter = 'NextQuarter', 31 | NextYear = 'NextYear', 32 | } 33 | 34 | export default DateRangeMacroType; 35 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/dateTimeString.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type alias for a string that represents a date/time value {@link https://en.wikipedia.org/wiki/ISO_8601|ISO 8601} format). 3 | */ 4 | type DateTimeString = string; 5 | 6 | export default DateTimeString; 7 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/exportDownloadInfo.model.ts: -------------------------------------------------------------------------------- 1 | import ExportFormat from './exportDownloadInfo.model'; 2 | 3 | export default interface ExportDownloadInfo { 4 | format: ExportFormat; 5 | fileName: string; 6 | token: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/exportFormat.model.ts: -------------------------------------------------------------------------------- 1 | enum ExportFormat { 2 | CSV = 'CSV', 3 | PDF = 'PDF', 4 | XLSX = 'XLSX', 5 | } 6 | 7 | export default ExportFormat; 8 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as Address } from './address.model'; 2 | export { default as AddressType } from './addressType.model'; 3 | export type { default as Amount } from './amount.model'; 4 | export { default as AmountType } from './amountType.model'; 5 | export type { default as ApiErrorResponse } from './apiErrorResponse.model'; 6 | export type { default as AssetType } from './assetType.model'; 7 | export type { default as BootstrapInfo } from './bootstrapInfo.model'; 8 | export type { default as Country } from './country.model'; 9 | export type { default as DateRange } from './dateRange.model'; 10 | export { default as DateRangeMacroType } from './dateRangeMacroType.model'; 11 | export type { default as DateTimeString } from './dateTimeString.model'; 12 | export { default as ExportFormat } from './exportFormat.model'; 13 | export type { default as ExportDownloadInfo } from './exportDownloadInfo.model'; 14 | export type { default as LookupValue } from './lookupValue.model'; 15 | export { default as Mode } from './mode.model'; 16 | export { default as NavigationSection } from './navigationSection.model'; 17 | export type { default as PagedResult } from './pagedResult.model'; 18 | export type { default as Pagination } from './pagination.model'; 19 | export type { default as Region } from './region.model'; 20 | export type { default as Tenant } from './tenant.model'; 21 | export type { default as TimeZone } from './timeZone.model'; 22 | export { default as TransactionStatus } from './transactionStatus.model'; 23 | export type { default as UserLite } from './userLite.model'; 24 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/lookupValue.model.ts: -------------------------------------------------------------------------------- 1 | export default interface LookupValue { 2 | id: number; 3 | name: string; 4 | }; -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/mode.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enumeration to control UI/UX mode for components/forms that support both adds and edits 3 | */ 4 | enum Mode { 5 | Add = 'Add', 6 | Edit = 'Edit', 7 | } 8 | 9 | export default Mode; 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/navigationSection.model.ts: -------------------------------------------------------------------------------- 1 | enum NavigationSection { 2 | ChartOfAccounts, 3 | Dashboard, 4 | Ledger, 5 | BalanceSheet, 6 | ProfitAndLoss, 7 | TimeTracking, 8 | Invoicing, 9 | } 10 | 11 | export default NavigationSection; 12 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/pagedResult.model.ts: -------------------------------------------------------------------------------- 1 | import Pagination from './pagination.model'; 2 | 3 | export default interface PagedResult { 4 | containsMorePages: boolean; 5 | pageCount: number | null; 6 | pagination: Pagination; 7 | results: TResult[]; 8 | total: number | null; 9 | } 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/pagination.model.ts: -------------------------------------------------------------------------------- 1 | export default interface Pagination { 2 | pageNumber: number | null; 3 | pageSize: number | null; 4 | } -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/region.model.ts: -------------------------------------------------------------------------------- 1 | export default interface Region { 2 | id: number; // integer 3 | label: string; 4 | name: string; 5 | code: string; 6 | } -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/tenant.model.ts: -------------------------------------------------------------------------------- 1 | import AssetType from './assetType.model'; 2 | 3 | export default interface Tenant { 4 | id: string; // GUID 5 | name: string; 6 | defaultAssetType: AssetType; 7 | } 8 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/timeZone.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Time Zone 3 | */ 4 | export default interface TimeZone { 5 | /** 6 | * TZDB / IANA / Olson Time Zone ID, e.g. "America/Los_Angeles" 7 | */ 8 | id: string; 9 | 10 | /** 11 | * Friendly name, e.g. "(UTC-08:00) Pacific Time (US & Canada)" 12 | */ 13 | displayName: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/transactionStatus.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enumeration representing the status of a General Ledger transaction (also known as a Journal Entry) 3 | */ 4 | enum TransactionStatus { 5 | Pending = 'Pending', 6 | Posted = 'Posted', 7 | } 8 | 9 | export default TransactionStatus; 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/models/userLite.model.ts: -------------------------------------------------------------------------------- 1 | export default interface UserLite { 2 | id: string; // GUID 3 | firstName: string; 4 | lastName: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/utilities/encoding.ts: -------------------------------------------------------------------------------- 1 | // Adapted from: https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem 2 | 3 | export function base64ToBytes(base64: string): Uint8Array { 4 | const binString = window.atob(base64); 5 | return Uint8Array.from(binString, (m) => m.codePointAt(0) ?? 0); 6 | } 7 | 8 | export function bytesToBase64(bytes: Uint8Array): string { 9 | const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join(''); 10 | return window.btoa(binString); 11 | } 12 | 13 | export function encodeStringAsBase64(data: string): string { 14 | return bytesToBase64(new TextEncoder().encode(data)); 15 | } 16 | 17 | export function encodeJsonObjectAsBase64(data: TObject): string { 18 | return encodeStringAsBase64(JSON.stringify(data)); 19 | } 20 | 21 | export function decodeStringFromBase64(base64String: string): string { 22 | return new TextDecoder().decode(base64ToBytes(base64String)); 23 | } 24 | 25 | export function decodeJsonObjectFromBase64(base64String: string): TObject { 26 | return JSON.parse(decodeStringFromBase64(base64String)); 27 | } 28 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/utilities/errorHandling/apiErrorHandler.interface.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from "redux"; 2 | import IAction from "../../../app/globalReduxStore/action.interface"; 3 | 4 | /** 5 | * Interface for a Redux based API Error Handler 6 | */ 7 | export default interface IApiErrorHandler { 8 | handleError( 9 | errorResponse: Response, 10 | dispatch: Dispatch, 11 | redirectToLoginOn401?: boolean, 12 | ): void; 13 | } 14 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/utilities/errorHandling/index.ts: -------------------------------------------------------------------------------- 1 | export { default as apiErrorHandler } from './apiErrorHandler'; 2 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/utilities/numericUtils.ts: -------------------------------------------------------------------------------- 1 | const defaultPrecision = 0.001; 2 | 3 | /** 4 | * Helper function for checking two numbers for equality. 5 | * 'cause ... you know ... floating point number issues! ;-) 6 | * Adapted from: https://stackoverflow.com/a/49261488 7 | * @param {Number} n1 8 | * @param {Number} n2 9 | * @param {Number} precision 10 | * @returns {Number} 11 | */ 12 | export const numbersAreEqualWithPrecision = (n1: number, n2: number, precision: number = defaultPrecision): boolean => { 13 | return Math.abs(n1 - n2) <= precision; 14 | } 15 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/utilities/useNamedState.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Dispatch, 3 | SetStateAction, 4 | useDebugValue, 5 | useState, 6 | } from 'react'; 7 | 8 | export default function useNamedState(name: string, initialValue: T): [T, Dispatch>] { 9 | useDebugValue(name); 10 | const [state, setState] = useState(initialValue); 11 | return [state, setState]; 12 | } 13 | -------------------------------------------------------------------------------- /src/FrontEnd/src/common/utilities/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useEffect, 3 | useRef, 4 | } from 'react'; 5 | 6 | export default function usePrevious(value: T) { 7 | const ref = useRef(); 8 | 9 | useEffect(() => { 10 | ref.current = value; 11 | }); 12 | 13 | return ref.current; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/balance-sheet/models/balanceSheetReport.model.ts: -------------------------------------------------------------------------------- 1 | import { Amount } from '../../../../common/models'; 2 | import { ReportAccount } from '../../chart-of-accounts/models'; 3 | 4 | export default interface BalanceSheetReport { 5 | totalAssets: Amount; 6 | totalLiabilities: Amount; 7 | totalEquity: Amount; 8 | netIncome: Amount; 9 | totalLiabilitiesAndEquity: Amount; 10 | discrepancy?: Amount; 11 | assets: ReportAccount[]; 12 | liabilities: ReportAccount[]; 13 | equity: ReportAccount[]; 14 | } 15 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/balance-sheet/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as BalanceSheetReport } from './balanceSheetReport.model'; 2 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/balance-sheet/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './balanceSheet.actionCreators'; 2 | export type { BalanceSheetState as state } from './balanceSheet.reducer'; 3 | export { default as reducer } from './balanceSheet.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/account.model.ts: -------------------------------------------------------------------------------- 1 | import AccountSubType from './accountSubType.model'; 2 | import AccountType from './accountType.model'; 3 | import { 4 | Amount, 5 | AmountType, 6 | AssetType, 7 | DateTimeString, 8 | UserLite, 9 | } from '../../../../common/models'; 10 | 11 | /** 12 | * Account object (in the accounting sense of the word -- an Account within the Chart of Accounts/Ledger). 13 | */ 14 | export default interface Account { 15 | /** 16 | * Account ID (GUID) 17 | */ 18 | id: string; 19 | /** 20 | * Account Number (unsigned [positive] short integer) 21 | */ 22 | accountNumber: number; 23 | name: string; 24 | description: string; 25 | accountType: AccountType; 26 | accountSubType: AccountSubType; 27 | assetType: AssetType; 28 | /** 29 | * Type of the Account's "Normal Balance" ("Debit" or "Credit") 30 | */ 31 | normalBalanceType: AmountType; 32 | created: DateTimeString; 33 | createdBy: UserLite; 34 | updated: DateTimeString | null; 35 | updatedBy: UserLite | null; 36 | balance: Amount; 37 | isBalanceNormal: boolean; 38 | } 39 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/accountCategoryList.model.ts: -------------------------------------------------------------------------------- 1 | import AccountSelectOption from './accountSelectOption.model'; 2 | 3 | /** 4 | * Used to show a collection of Account select options, grouped by type/category. 5 | */ 6 | export default interface AccountCategoryList { 7 | /** 8 | * Type/category of Accounts 9 | */ 10 | category: string; 11 | /** 12 | * Accounts of the specified type/category 13 | */ 14 | accounts: AccountSelectOption[]; 15 | } 16 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/accountLite.model.ts: -------------------------------------------------------------------------------- 1 | import AccountSubType from './accountSubType.model'; 2 | import AccountType from './accountType.model'; 3 | 4 | /** 5 | * Lite version of an Account object. 6 | */ 7 | export default interface AccountLite { 8 | /** 9 | * Account ID (GUID) 10 | */ 11 | id: string; 12 | /** 13 | * Account Number (unsigned [positive] short integer) 14 | */ 15 | accountNumber: number; 16 | /** 17 | * Account Name 18 | */ 19 | accountName: string; 20 | /** 21 | * Account Type 22 | */ 23 | accountType: AccountType; 24 | /** 25 | * Account Sub Type 26 | */ 27 | accountSubType: AccountSubType; 28 | } 29 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/accountSelectOption.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Account select option (for drop-down lists) 3 | */ 4 | export default interface AccountSelectOption { 5 | /** 6 | * Account ID (GUID) 7 | */ 8 | id: string; 9 | /** 10 | * Account Display Name 11 | */ 12 | name: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/accountSubType.model.ts: -------------------------------------------------------------------------------- 1 | import { LookupValue } from '../../../../common/models'; 2 | 3 | /** 4 | * Account Sub-Type Lookup Value 5 | */ 6 | export default interface AccountType extends LookupValue { 7 | accountTypeId: number; 8 | accountType: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/accountType.model.ts: -------------------------------------------------------------------------------- 1 | import { LookupValue } from '../../../../common/models'; 2 | 3 | /** 4 | * Account Type Lookup Value 5 | */ 6 | export default interface AccountType extends LookupValue { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as Account } from './account.model'; 2 | export type { default as AccountCategoryList } from './accountCategoryList.model'; 3 | export type { default as AccountLite } from './accountLite.model'; 4 | export type { default as AccountSelectOption } from './accountSelectOption.model'; 5 | export type { default as AccountSubType } from './accountSubType.model'; 6 | export type { default as AccountType } from './accountType.model'; 7 | export { default as KnownAccountSubType } from './knownAccountSubType.model'; 8 | export { default as KnownAccountType } from './knownAccountType.model'; 9 | export type { default as ReportAccount } from './reportAccount.model'; 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/knownAccountSubType.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enumeration for known sub-types of Accounts 3 | */ 4 | enum KnownAccountSubType { 5 | BankAccount = 1, 6 | AccountsReceivable = 2, 7 | OtherCurrentAssets = 3, 8 | FixedAssets = 4, 9 | OtherAssets = 5, 10 | AccountsPayable = 6, 11 | CreditCard = 7, 12 | OtherCurrentLiabilities = 8, 13 | LongTermLiabilities = 9, 14 | Equity = 10, 15 | RetainedEarnings = 11, 16 | OperatingRevenue = 12, 17 | OtherIncome = 13, 18 | CostOfGoodsSold = 14, 19 | OperatingExpense = 15, 20 | OtherExpense = 16, 21 | } 22 | 23 | export default KnownAccountSubType; 24 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/knownAccountType.model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enumeration for known types of Accounts 3 | */ 4 | enum KnownAccountType { 5 | Asset = 1, 6 | Liability = 2, 7 | Equity = 3, 8 | Revenue = 4, 9 | Expense = 5, 10 | } 11 | 12 | export default KnownAccountType; 13 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/models/reportAccount.model.ts: -------------------------------------------------------------------------------- 1 | import AccountSubType from './accountSubType.model'; 2 | import AccountType from './accountType.model'; 3 | import { 4 | Amount, 5 | AmountType, 6 | AssetType, 7 | } from '../../../../common/models'; 8 | 9 | export default interface ReportAccount { 10 | id: string; // GUID 11 | accountNumber: number; // unsigned short 12 | name: string; 13 | description: string; 14 | accountType: AccountType; 15 | accountSubType: AccountSubType; 16 | assetType: AssetType; 17 | normalBalanceType: AmountType; // "Debit" or "Credit" 18 | balance: Amount; 19 | } 20 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/redux/accounts.actions.ts: -------------------------------------------------------------------------------- 1 | import IAction from '../../../../app/globalReduxStore/action.interface'; 2 | import ActionType from '../../../../app/globalReduxStore/actionType'; 3 | import { Account } from '../models'; 4 | 5 | interface RequestAccountsAction extends IAction { 6 | type: ActionType.REQUEST_ACCOUNTS; 7 | } 8 | 9 | interface ReceiveAccountsAction extends IAction { 10 | type: ActionType.RECEIVE_ACCOUNTS; 11 | accounts: Account[]; 12 | } 13 | 14 | interface SelectAccountAction extends IAction { 15 | type: ActionType.SELECT_ACCOUNT; 16 | account: Account; 17 | } 18 | 19 | export type KnownAction = RequestAccountsAction | ReceiveAccountsAction | SelectAccountAction; 20 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/chart-of-accounts/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './accounts.actionCreators'; 2 | export type { AccountsState as state } from './accounts.reducer'; 3 | export { default as reducer } from './accounts.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/general-ledger/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as LedgerAccount } from './ledgerAccount.model'; 2 | export type { default as LedgerAccountTransaction } from './ledgerAccountTransaction.model'; 3 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/general-ledger/models/ledgerAccount.model.ts: -------------------------------------------------------------------------------- 1 | import LedgerAccountTransaction from './ledgerAccountTransaction.model'; 2 | import { 3 | Amount, 4 | AmountType, 5 | AssetType, 6 | } from '../../../../common/models'; 7 | import { AccountType } from '../../chart-of-accounts/models'; 8 | 9 | /** 10 | * Represents an Account on the General Ledger 11 | */ 12 | export default interface LedgerAccount { 13 | /** 14 | * Account ID (GUID) 15 | */ 16 | id: string; 17 | /** 18 | * Account Number (unsigned [positive] short integer) 19 | */ 20 | accountNumber: number; 21 | name: string; 22 | description: string; 23 | accountType: AccountType; 24 | assetType: AssetType; 25 | /** 26 | * Type of the Account's "Normal Balance" ("Debit" or "Credit") 27 | */ 28 | normalBalanceType: AmountType; 29 | startingBalance: Amount; 30 | transactions: LedgerAccountTransaction[]; 31 | } 32 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/general-ledger/models/ledgerAccountTransaction.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Amount, 3 | TransactionStatus, 4 | } from '../../../../common/models'; 5 | 6 | export default interface LedgerAccountTransaction { 7 | id: string; // GUID 8 | entryId: number; // unsigned integer 9 | status: TransactionStatus; 10 | entryDate: string; // Date - YYYY-MM-DD (required) 11 | postDate: string | null; // Nullable Date - YYYY-MM-DD (date value for transactions in 'Posted' status; null for transactions in 'Pending' status) 12 | description: string | null; // required 13 | note: string | null; // optional 14 | checkNumber: number | null; // optional 15 | created: string; // UTC timestamp in ISO 8601 format 16 | updated: string | null; // nullable UTC timestamp in ISO 8601 format 17 | canceled: string | null; // nullable UTC timestamp in ISO 8601 format 18 | amount: Amount; 19 | updatedBalance: Amount; 20 | } 21 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/general-ledger/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './ledger.actionCreators'; 2 | export type { LedgerState as state } from './ledger.reducer'; 3 | export { default as reducer } from './ledger.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/general-ledger/redux/ledger.actions.ts: -------------------------------------------------------------------------------- 1 | import IAction from '../../../../app/globalReduxStore/action.interface'; 2 | import ActionType from '../../../../app/globalReduxStore/actionType'; 3 | import { DateRange } from '../../../../common/models'; 4 | import { LedgerAccount } from '../models'; 5 | 6 | export interface RequestLedgerReportDataAction extends IAction { 7 | type: ActionType.REQUEST_LEDGER_REPORT_DATA; 8 | } 9 | 10 | export interface ReceiveLedgerReportDataAction extends IAction { 11 | type: ActionType.RECEIVE_LEDGER_REPORT_DATA; 12 | accounts: LedgerAccount[]; 13 | } 14 | 15 | export interface UpdateLedgerReportDateRangeAction extends IAction { 16 | type: ActionType.UPDATE_LEDGER_REPORT_DATE_RANGE; 17 | dateRange: DateRange; 18 | } 19 | 20 | export interface UpdateLedgerReportDateRangeStartAction extends IAction { 21 | type: ActionType.UPDATE_LEDGER_REPORT_DATE_RANGE_START; 22 | dateRangeStart: string; 23 | } 24 | 25 | export interface UpdateLedgerReportDateRangeEndAction extends IAction { 26 | type: ActionType.UPDATE_LEDGER_REPORT_DATE_RANGE_END; 27 | dateRangeEnd: string; 28 | } 29 | 30 | export interface ResetLedgerReportDataAction extends IAction { 31 | type: ActionType.RESET_LEDGER_REPORT_DATA; 32 | } 33 | 34 | export type KnownAction = RequestLedgerReportDataAction | 35 | ReceiveLedgerReportDataAction | 36 | UpdateLedgerReportDateRangeAction | 37 | UpdateLedgerReportDateRangeStartAction | 38 | UpdateLedgerReportDateRangeEndAction | 39 | ResetLedgerReportDataAction; 40 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/journal/models/draftJournalEntryAccount.model.ts: -------------------------------------------------------------------------------- 1 | import { AssetType } from '../../../../common/models'; 2 | 3 | /** 4 | * Represents a work-in-progress Journal Entry Account used by the Journal Entry Editor. 5 | */ 6 | export default interface DraftJournalEntryAccount { 7 | accountId: string | null; 8 | accountNumber: number | null; 9 | accountName: string | null; 10 | assetType: AssetType | null; 11 | credit: number | null; 12 | creditAsString: string | null; 13 | debit: number | null; 14 | debitAsString: string | null; 15 | }; 16 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/journal/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as DraftJournalEntryAccount } from './draftJournalEntryAccount.model'; 2 | export type { default as JournalEntry } from './journalEntry.model'; 3 | export type { default as JournalEntryAccount } from './journalEntryAccount.model'; 4 | export type { default as JournalEntryLite } from './journalEntryLite.model'; 5 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/journal/models/journalEntry.model.ts: -------------------------------------------------------------------------------- 1 | import JournalEntryAccount from './journalEntryAccount.model'; 2 | import { TransactionStatus } from '../../../../common/models'; 3 | 4 | export default interface JournalEntry { 5 | tenantId: string | null; // GUID (required to create) 6 | id: string | null; // GUID (not required to created - assigned by system) 7 | entryId: number | null; // unsigned integer (not required to create - assigned by system) 8 | status: TransactionStatus | null; // (not required to create - assigned by system) 9 | entryDate: string | null; // Date - YYYY-MM-DD (required) 10 | postDate: string | null; // Nullable Date - YYYY-MM-DD (optional to create Journal Entry in 'Posted' status) 11 | description: string | null; // required 12 | note: string | null; // optional 13 | checkNumber: number | null; // optional 14 | accounts: JournalEntryAccount[]; 15 | } 16 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/journal/models/journalEntryAccount.model.ts: -------------------------------------------------------------------------------- 1 | import { Amount } from '../../../../common/models'; 2 | 3 | export default interface JournalEntryAccount { 4 | accountId: string | null; 5 | accountNumber: number | null; 6 | accountName: string | null; 7 | amount: Amount | null; 8 | } 9 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/journal/models/journalEntryLite.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DateTimeString, 3 | TransactionStatus 4 | } from '../../../../common/models'; 5 | 6 | export default interface JournalEntryLite { 7 | id: string; // GUID 8 | entryId: number; // unsigned integer 9 | status: TransactionStatus; 10 | entryDate: DateTimeString; // Date - YYYY-MM-DD 11 | postDate: DateTimeString | null; // Nullable Date - YYYY-MM-DD 12 | description: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/journal/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './journalEntry.actionCreators'; 2 | export type { JournalEntryAttributeValidationState } from './journalEntry.reducer'; 3 | export type { JournalEntryState as state } from './journalEntry.reducer'; 4 | export { default as reducer } from './journalEntry.reducer'; 5 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/profit-and-loss/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as ProfitAndLossReport } from './profitAndLossReport.model'; 2 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/profit-and-loss/models/profitAndLossReport.model.ts: -------------------------------------------------------------------------------- 1 | import { Amount } from '../../../../common/models'; 2 | import { ReportAccount } from '../../chart-of-accounts/models'; 3 | 4 | export default interface ProfitAndLossReport { 5 | grossProfit: Amount; 6 | totalOperatingExpenses: Amount; 7 | netOperatingIncome: Amount; 8 | totalOtherIncome: Amount; 9 | totalOtherExpenses: Amount; 10 | netOtherIncome: Amount; 11 | netIncome: Amount; 12 | operatingIncome: ReportAccount[]; 13 | operatingExpenses: ReportAccount[]; 14 | otherIncome: ReportAccount[]; 15 | otherExpenses: ReportAccount[]; 16 | } 17 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/accounting/profit-and-loss/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './profitAndLoss.actionCreators'; 2 | export type { ProfitAndLossState as state } from './profitAndLoss.reducer'; 3 | export { default as reducer } from './profitAndLoss.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/invoices/invoiceStatusLabel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Alert } from 'reactstrap'; 3 | import { InvoiceStatus } from './models'; 4 | 5 | type PropTypes = { 6 | isPastDue: boolean; 7 | status: InvoiceStatus; 8 | } 9 | 10 | function InvoiceStatusLabel({ 11 | isPastDue, 12 | status, 13 | }: PropTypes) { 14 | let color: string; 15 | let statusText: string = status; 16 | 17 | switch (status) { 18 | case InvoiceStatus.Draft: 19 | color = 'secondary'; 20 | break; 21 | 22 | case InvoiceStatus.Paid: 23 | color = 'success'; 24 | break; 25 | 26 | case InvoiceStatus.Sent: { 27 | if (isPastDue) { 28 | color = 'warning'; 29 | statusText = 'Past Due'; 30 | } else { 31 | color = 'info'; 32 | } 33 | } 34 | } 35 | 36 | return ( 37 | 45 | {statusText} 46 | 47 | ); 48 | } 49 | 50 | export default InvoiceStatusLabel; 51 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/invoices/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as Invoice } from './invoice.model'; 2 | export type { default as InvoiceLineItem } from './invoiceLineItem.model'; 3 | export type { default as InvoiceLite } from './invoiceLite.model'; 4 | export { default as InvoiceStatus } from './invoiceStatus.model'; 5 | export type { default as InvoiceTerms } from './invoiceTerms.model'; 6 | 7 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/invoices/models/invoice.model.ts: -------------------------------------------------------------------------------- 1 | import InvoiceLineItem from './invoiceLineItem.model'; 2 | import InvoiceStatus from './invoiceStatus.model'; 3 | import InvoiceTerms from './invoiceTerms.model'; 4 | import { Amount } from '../../../../common/models'; 5 | import { CustomerLite } from '../../../sales/customers/models'; 6 | 7 | export default interface Invoice { 8 | id?: string; // GUID (not required to create; required to update) 9 | tenantId: string; // GUID (required to create) 10 | invoiceNumber?: number; // uint (not required to create; required to update) 11 | status: InvoiceStatus; // required to create 12 | customerId: string | null; // GUID (required to create) 13 | customer?: CustomerLite; // not required to create; part of response 14 | customerAddress: string | null; 15 | customerEmail: string | null; 16 | invoiceTermsId: string | null; // GUID (required to create) 17 | invoiceTerms?: InvoiceTerms; // not required to create; part of response 18 | issueDate: string | null; // Date in YYYY-MM-DD format; required to create 19 | dueDate: string | null; // Date in YYYY-MM-DD format; required to create 20 | amount?: Amount; // not required to create; part of response 21 | message: string | null; 22 | lineItems: InvoiceLineItem[]; 23 | } 24 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/invoices/models/invoiceLineItem.model.ts: -------------------------------------------------------------------------------- 1 | import { Amount } from '../../../../common/models'; 2 | 3 | export default interface InvoiceLineItem { 4 | id: string | null; // GUID (not required to create) 5 | orderNumber: number; // uint (required to create) 6 | date: string | null; // Date as YYYY-MM-DD (required to create) 7 | productId: string | null; // GUID (required to create) 8 | productOrService?: string | null; 9 | productCategory?: string | null; 10 | description: string | null; // required to create 11 | quantity: number | null; // required to create 12 | unitPrice: Amount | null; // required to create 13 | total?: Amount | null; 14 | timeActivityId: string | null; // nullable GUID - ID of linked Time Activity 15 | } 16 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/invoices/models/invoiceLite.model.ts: -------------------------------------------------------------------------------- 1 | import InvoiceStatus from './invoiceStatus.model'; 2 | import { 3 | Amount, 4 | DateTimeString, 5 | } from '../../../../common/models'; 6 | 7 | export default interface InvoiceLite { 8 | id: string; // GUID 9 | invoiceNumber: number; // uint 10 | customerName: string; 11 | amount: Amount; 12 | issueDate: DateTimeString; // Date as YYYY-MM-DD 13 | dueDate: DateTimeString; // Date as YYYY-MM-DD 14 | terms: string; 15 | status: InvoiceStatus; 16 | } 17 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/invoices/models/invoiceStatus.model.ts: -------------------------------------------------------------------------------- 1 | enum InvoiceStatus { 2 | Draft = 'Draft', 3 | Sent = 'Sent', 4 | Paid = 'Paid', 5 | } 6 | 7 | export default InvoiceStatus; -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/invoices/models/invoiceTerms.model.ts: -------------------------------------------------------------------------------- 1 | export default interface InvoiceTerms { 2 | id: string | null; // GUID 3 | tenantId: string | null; // GUID 4 | isCustom: boolean; 5 | name: string | null; 6 | dueInDays: number | null; 7 | dueOnDayOfMonth: number | null; 8 | dueNextMonthThreshold: number | null; 9 | } -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/invoices/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './invoice.actionCreators'; 2 | export type { InvoiceStoreState as state } from './invoice.reducer'; 3 | export { default as reducer } from './invoice.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/payments/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as InvoicePayment } from './invoicePayment.model'; 2 | export type { default as Payment } from './payment.model'; 3 | export type { default as PaymentMethod } from './paymentMethod.model'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/payments/models/invoicePayment.model.ts: -------------------------------------------------------------------------------- 1 | import { Amount } from '../../../../common/models'; 2 | import InvoiceLite from '../../invoices/models/invoiceLite.model'; 3 | 4 | export default interface InvoicePayment { 5 | invoiceId: string; // GUID (required to create) 6 | invoice?: InvoiceLite; // not required to create; part of response 7 | isSelected?: boolean; // Not part of API request or response at all; used for UI state management 8 | paymentAmount: Amount; // required to create 9 | } 10 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/payments/models/paymentMethod.model.ts: -------------------------------------------------------------------------------- 1 | import { LookupValue } from '../../../../common/models'; 2 | 3 | export default interface PaymentMethod extends LookupValue { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/invoicing/payments/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './payment.actionCreators'; 2 | export type { PaymentStoreState as state } from './payment.reducer'; 3 | export { default as reducer } from './payment.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/customers/models/customer.model.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../../../common/models'; 2 | 3 | export default interface Customer { 4 | id: string; // GUID 5 | customerNumber: string; // short, unique alpha-numeric ID number for the customer 6 | companyName: string; 7 | displayName: string; 8 | contactPersonTitle: string | null; 9 | contactPersonFirstName: string; 10 | contactPersonMiddleName: string | null; 11 | contactPersonLastName: string; 12 | contactPersonNickName: string | null; 13 | contactPersonSuffix: string | null; 14 | email: string | null; 15 | workPhoneNumber: string | null; 16 | mobilePhoneNumber: string | null; 17 | faxNumber: string | null; 18 | otherPhoneNumber: string | null; 19 | website: string | null; 20 | isShippingAddressSameAsBillingAddress: boolean; 21 | billingAddress: Address; 22 | shippingAddress: Address | null; 23 | notes: string | null; 24 | } 25 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/customers/models/customerLite.model.ts: -------------------------------------------------------------------------------- 1 | export default interface CustomerLite { 2 | id: string; // GUID 3 | customerNumber: string; // alphanumeric 4 | companyName: string; 5 | displayName: string; 6 | } -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/customers/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as Customer } from './customer.model'; 2 | export type { default as CustomerLite } from './customerLite.model'; 3 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/customers/redux/customers.actions.ts: -------------------------------------------------------------------------------- 1 | import IAction from '../../../../app/globalReduxStore/action.interface'; 2 | import ActionType from '../../../../app/globalReduxStore/actionType'; 3 | import { 4 | Customer, 5 | CustomerLite, 6 | } from '../models'; 7 | 8 | export interface RequestCustomersAction extends IAction { 9 | type: ActionType.REQUEST_CUSTOMERS; 10 | } 11 | 12 | export interface ReceiveCustomersAction extends IAction { 13 | type: ActionType.RECEIVE_CUSTOMERS; 14 | customers: CustomerLite[]; 15 | } 16 | 17 | export interface RequestCustomerDetailsAction extends IAction { 18 | type: ActionType.REQUEST_CUSTOMER_DETAILS; 19 | } 20 | 21 | export interface ReceiveCustomerDetailsAction extends IAction { 22 | type: ActionType.RECEIVE_CUSTOMER_DETAILS; 23 | customer: Customer; 24 | } 25 | 26 | export interface ResetCustomerDetailsAction { 27 | type: ActionType.RESET_CUSTOMER_DETAILS; 28 | } 29 | 30 | export interface ResetCustomersListAction { 31 | type: ActionType.RESET_CUSTOMERS_LIST; 32 | } 33 | 34 | export interface ResetCustomerStoreAction { 35 | type: ActionType.RESET_CUSTOMER_STORE_STATE; 36 | } 37 | 38 | export type KnownAction = RequestCustomersAction | 39 | ReceiveCustomersAction | 40 | RequestCustomerDetailsAction | 41 | ReceiveCustomerDetailsAction | 42 | ResetCustomerDetailsAction | 43 | ResetCustomersListAction | 44 | ResetCustomerStoreAction; 45 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/customers/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './customers.actionCreators'; 2 | export type { CustomerStoreState as state } from './customers.reducer'; 3 | export { default as reducer } from './customers.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/products/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as ProductLite } from './productLite.model'; 2 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/products/models/productLite.model.ts: -------------------------------------------------------------------------------- 1 | export default interface ProductLite { 2 | id: string; // GUID 3 | categoryId: string; // GUID 4 | category: string; 5 | type: string; // 'Product' or 'Service' 6 | sku: string | null; 7 | name: string; 8 | salesPriceOrRate: number; 9 | } -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/products/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './products.actionCreators'; 2 | export type { ProductStoreState as state } from './products.reducer'; 3 | export { default as reducer } from './products.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/products/redux/products.actions.ts: -------------------------------------------------------------------------------- 1 | import IAction from '../../../../app/globalReduxStore/action.interface'; 2 | import ActionType from '../../../../app/globalReduxStore/actionType'; 3 | import { ProductLite } from '../models'; 4 | 5 | export interface RequestProductsAction extends IAction { 6 | type: ActionType.REQUEST_PRODUCTS; 7 | } 8 | 9 | export interface ReceiveProductsAction extends IAction { 10 | type: ActionType.RECEIVE_PRODUCTS; 11 | products: ProductLite[]; 12 | } 13 | 14 | export type KnownAction = RequestProductsAction | ReceiveProductsAction; 15 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/sales/products/redux/products.reducer.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from 'lodash'; 2 | import { 3 | Action, 4 | Reducer, 5 | } from 'redux'; 6 | import { KnownAction } from './products.actions'; 7 | import ActionType from '../../../../app/globalReduxStore/actionType'; 8 | import { ProductLite } from '../models'; 9 | 10 | export interface ProductStoreState { 11 | isFetching: boolean; 12 | products: ProductLite[]; 13 | } 14 | 15 | const unloadedState: ProductStoreState = { 16 | isFetching: false, 17 | products: [], 18 | }; 19 | 20 | const reducer: Reducer = (state: ProductStoreState | undefined, incomingAction: Action): ProductStoreState => { 21 | if (state === undefined) { 22 | return unloadedState; 23 | } 24 | 25 | const action = incomingAction as KnownAction; 26 | 27 | if (!isNil(action)) { 28 | switch (action.type) { 29 | case ActionType.REQUEST_PRODUCTS: 30 | return { 31 | ...state, 32 | isFetching: true 33 | }; 34 | 35 | case ActionType.RECEIVE_PRODUCTS: 36 | return { 37 | ...state, 38 | isFetching: false, 39 | products: action.products, 40 | }; 41 | } 42 | } 43 | 44 | // All stores should get reset to default state on logout 45 | if (incomingAction.type === ActionType.RECEIVE_LOGOUT_RESPONSE) { 46 | return unloadedState; 47 | } 48 | 49 | return state; 50 | }; 51 | 52 | export default reducer; 53 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/employees/models/employeeLite.model.ts: -------------------------------------------------------------------------------- 1 | export default interface EmployeeLite { 2 | id: string; // GUID 3 | employeeNumber: number; // uint 4 | displayName: string; 5 | isBillableByDefault: boolean; 6 | hourlyBillableRate: number | null; 7 | isUser: boolean; 8 | userId: string | null; // GUID 9 | } -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/employees/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as EmployeeLite } from './employeeLite.model'; 2 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/employees/redux/employees.actions.ts: -------------------------------------------------------------------------------- 1 | import IAction from '../../../../app/globalReduxStore/action.interface'; 2 | import ActionType from '../../../../app/globalReduxStore/actionType'; 3 | import { EmployeeLite } from '../models'; 4 | 5 | export interface RequestEmployeesAction extends IAction { 6 | type: ActionType.REQUEST_EMPLOYEES; 7 | } 8 | 9 | export interface ReceiveEmployeesAction extends IAction { 10 | type: ActionType.RECEIVE_EMPLOYEES; 11 | employees: EmployeeLite[]; 12 | } 13 | 14 | export type KnownAction = RequestEmployeesAction | ReceiveEmployeesAction; 15 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/employees/redux/employees.reducer.ts: -------------------------------------------------------------------------------- 1 | import { isNil } from 'lodash'; 2 | import { 3 | Action, 4 | Reducer, 5 | } from 'redux'; 6 | import { KnownAction } from './employees.actions'; 7 | import ActionType from '../../../../app/globalReduxStore/actionType'; 8 | import { EmployeeLite } from '../models'; 9 | 10 | export interface EmployeeStoreState { 11 | isFetching: boolean; 12 | employees: EmployeeLite[]; 13 | } 14 | 15 | const unloadedState: EmployeeStoreState = { 16 | isFetching: false, 17 | employees: [], 18 | }; 19 | 20 | const reducer: Reducer = (state: EmployeeStoreState | undefined, incomingAction: Action): EmployeeStoreState => { 21 | if (state === undefined) { 22 | return unloadedState; 23 | } 24 | 25 | const action = incomingAction as KnownAction; 26 | 27 | if (!isNil(action)) { 28 | switch (action.type) { 29 | case ActionType.REQUEST_EMPLOYEES: 30 | return { 31 | ...state, 32 | isFetching: true 33 | }; 34 | 35 | case ActionType.RECEIVE_EMPLOYEES: 36 | return { 37 | ...state, 38 | isFetching: false, 39 | employees: action.employees, 40 | }; 41 | } 42 | } 43 | 44 | // All stores should get reset to default state on logout 45 | if (incomingAction.type === ActionType.RECEIVE_LOGOUT_RESPONSE) { 46 | return unloadedState; 47 | } 48 | 49 | return state; 50 | }; 51 | 52 | export default reducer; 53 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/employees/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './employees.actionCreators'; 2 | export type { EmployeeStoreState as state } from './employees.reducer'; 3 | export { default as reducer } from './employees.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/time-activities/models/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as TimeActivity } from './timeActivity.model'; 2 | export type { default as TimeActivityDetailsReport } from './timeActivityDetailsReport.model'; 3 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/time-activities/models/timeActivity.model.ts: -------------------------------------------------------------------------------- 1 | import { DateTimeString } from '../../../../common/models'; 2 | import { CustomerLite } from '../../../sales/customers/models'; 3 | import { ProductLite } from '../../../sales/products/models'; 4 | import { EmployeeLite } from '../../employees/models'; 5 | 6 | export default interface TimeActivity { 7 | id?: string | null; // GUID 8 | tenantId: string | null; // GUID (required to create) 9 | customerId: string | null; // GUID (required to create) 10 | customer?: CustomerLite | null; 11 | employeeId: string | null; // GUID (required to create) 12 | employee?: EmployeeLite | null; 13 | productId: string | null; // GUID (required to create) 14 | product?: ProductLite | null; 15 | isBillable: boolean; 16 | hourlyBillingRate: number | null; 17 | hourlyBillingRateAsString?: string | null; // keep the rate as a raw string as well as a parsed float; helps with XXX.0X issue 18 | date: DateTimeString | null; // Date in YYYY-MM-DD format (required to create) 19 | timeZone: string | null; // IANA/Olson/TZDB Time Zone ID (required to create) 20 | startTime: string | null; // Time of day string in hh:mm:ss format (required to create) 21 | endTime: string | null; // Time of day string in hh:mm:ss format (required to create) 22 | break: string | null; // Duration in hh:mm:ss format (optional) 23 | description: string | null; // required to create 24 | totalTime?: string | null; // Duration in hh:mm:ss format (not required to create; part of response) 25 | totalBillableAmount?: number | null; // not required to create; part of response 26 | } 27 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/time-activities/models/timeActivityDetailsReport.model.ts: -------------------------------------------------------------------------------- 1 | import TimeActivity from './timeActivity.model'; 2 | import { 3 | AssetType, 4 | DateRange, 5 | } from '../../../../common/models'; 6 | 7 | export default interface TimeActivityDetailsReport { 8 | reportDates: DateRange; 9 | defaultAssetType: AssetType; 10 | timeActivities: TimeActivity[]; 11 | } 12 | -------------------------------------------------------------------------------- /src/FrontEnd/src/features/time-tracking/time-activities/redux/index.ts: -------------------------------------------------------------------------------- 1 | export { default as actionCreators } from './timeTracking.actionCreators'; 2 | export type { TimeActivityStoreState as state } from './timeTracking.reducer'; 3 | export { default as reducer } from './timeTracking.reducer'; 4 | -------------------------------------------------------------------------------- /src/FrontEnd/src/index.tsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.css'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom/client'; 5 | import { Provider } from 'react-redux'; 6 | import { BrowserRouter } from 'react-router-dom'; 7 | import App from './app'; 8 | import configureStore from './app/globalReduxStore/configureStore'; 9 | import reportWebVitals from './reportWebVitals'; 10 | 11 | // Get the application-wide store instance, pre-populating with state from the server where available. 12 | const store = configureStore(); 13 | 14 | const root = ReactDOM.createRoot( 15 | document.getElementById('root') as HTMLElement 16 | ); 17 | 18 | root.render( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | 28 | // If you want to start measuring performance in your app, pass a function 29 | // to log results (for example: reportWebVitals(console.log)) 30 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 31 | reportWebVitals(); 32 | -------------------------------------------------------------------------------- /src/FrontEnd/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/FrontEnd/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/FrontEnd/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/FrontEnd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/Infrastructure/DashAccountingSystemV2.Aspire.AppHost/DashAccountingSystemV2.Aspire.AppHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DashAccountingSystemV2.Aspire.AppHost 5 | 2.1.0 6 | Geoffrey Roberts 7 | Dash Software Solutions, Inc. 8 | Copyright (C) 2022 - 2024 Dash Software Solutions, Inc. All rights reserved. 9 | 10 | 11 | 12 | Exe 13 | net8.0 14 | enable 15 | enable 16 | true 17 | 0220bdbb-eba6-4d11-9776-ac25dc7a0f60 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Infrastructure/DashAccountingSystemV2.Aspire.AppHost/Program.cs: -------------------------------------------------------------------------------- 1 | using Projects; 2 | 3 | var builder = DistributedApplication.CreateBuilder(args); 4 | 5 | var backEnd = builder 6 | .AddProject("BackEnd") 7 | .WithExternalHttpEndpoints(); 8 | 9 | builder 10 | .AddNpmApp("FrontEnd", "../../FrontEnd") 11 | .WithReference(backEnd) 12 | .WithEnvironment("BROWSER", "none") // Disable opening browser on npm start 13 | .WithHttpsEndpoint(env: "PORT") 14 | .WithExternalHttpEndpoints() 15 | .PublishAsDockerFile(); 16 | 17 | builder.Build().Run(); 18 | -------------------------------------------------------------------------------- /src/Infrastructure/DashAccountingSystemV2.Aspire.AppHost/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "https://localhost:17041;http://localhost:15185", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development", 11 | "DOTNET_ENVIRONMENT": "Development", 12 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21119", 13 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22242" 14 | } 15 | }, 16 | "http": { 17 | "commandName": "Project", 18 | "dotnetRunMessages": true, 19 | "launchBrowser": true, 20 | "applicationUrl": "http://localhost:15185", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development", 23 | "DOTNET_ENVIRONMENT": "Development", 24 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19286", 25 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20226" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Infrastructure/DashAccountingSystemV2.Aspire.AppHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Infrastructure/DashAccountingSystemV2.Aspire.AppHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Aspire.Hosting.Dcp": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Infrastructure/DashAccountingSystemV2.Aspire.ServiceDefaults/DashAccountingSystemV2.Aspire.ServiceDefaults.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DashAccountingSystemV2.Aspire.ServiceDefaults 5 | 2.1.0 6 | Geoffrey Roberts 7 | Dash Software Solutions, Inc. 8 | Copyright (C) 2022 - 2024 Dash Software Solutions, Inc. All rights reserved. 9 | 10 | 11 | 12 | net8.0 13 | enable 14 | enable 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/DashAccountingSystemV2.Tests/Extensions/DecimalExtensionsFixture.cs: -------------------------------------------------------------------------------- 1 | using DashAccountingSystemV2.BackEnd.Extensions; 2 | using DashAccountingSystemV2.BackEnd.Models; 3 | 4 | namespace DashAccountingSystemV2.Tests.Extensions 5 | { 6 | public class DecimalExtensionsFixture 7 | { 8 | [Theory] 9 | [InlineData(1000, AmountType.Debit, 1000)] 10 | [InlineData(-1000, AmountType.Debit, -1000)] 11 | [InlineData(-2500.25, AmountType.Credit, 2500.25)] 12 | [InlineData(2500.25, AmountType.Credit, -2500.25)] 13 | public void WithNormalBalanceType_Ok(decimal originalAmount, AmountType normalBalanceType, decimal expectedAmount) 14 | { 15 | Assert.Equal(expectedAmount, originalAmount.WithNormalBalanceType(normalBalanceType)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/DashAccountingSystemV2.Tests/Services/Template/FileSystemTemplateProviderFixture.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using DashAccountingSystemV2.BackEnd.Services.Template; 3 | 4 | namespace DashAccountingSystemV2.Tests.Services.Template 5 | { 6 | public class FileSystemTemplateProviderFixture 7 | { 8 | [Fact] 9 | public async Task GetTemplate_Ok() 10 | { 11 | var loggerFactory = TestUtilities.GetLoggerFactory(); 12 | var subjectUnderTest = new FileSystemTemplateProvider(loggerFactory.CreateLogger()); 13 | var templateName = "DefaultInvoiceTemplate.cshtml"; 14 | 15 | var templateContent = await subjectUnderTest.GetTemplate(templateName); 16 | 17 | Assert.False(string.IsNullOrWhiteSpace(templateContent), "Expect GetTemplate() to have returned a non-empty string containing the template content"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/DashAccountingSystemV2.Tests/Services/Template/RazorTemplates/DefaultInvoiceTemplate.cshtml: -------------------------------------------------------------------------------- 1 | @* 2 | UNIT TEST Default Invoice Template for Dash Accounting System v2.0 3 | *@ 4 | @model DashAccountingSystemV2.BackEnd.Models.Invoice 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Invoice

12 |

@Model.Tenant.Name

13 |

@Model.InvoiceNumber

14 | 15 | 16 | -------------------------------------------------------------------------------- /test/DashAccountingSystemV2.Tests/appsettings.UnitTests.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=localhost;Port=5432;User Id=postgres;Database=dash_accounting_aspnet8" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Debug", 8 | "System": "Information", 9 | "Microsoft": "Information" 10 | }, 11 | "LogToConsole": true 12 | } 13 | } 14 | --------------------------------------------------------------------------------