From 9f6e9e85a5c847621afdb6a22c32adbd9e3c1e6e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 30 Jun 2025 16:30:55 +0200 Subject: [PATCH] feat(server): endpoints swagger docs --- packages/server/src/i18n/en/branches.json | 3 + .../modules/Accounts/Account.transformer.ts | 4 +- .../modules/Accounts/Accounts.controller.ts | 15 +- .../Accounts/AccountsApplication.service.ts | 9 +- .../modules/Accounts/DeleteAccount.service.ts | 10 +- .../GetAccountTransactions.service.ts | 3 +- .../dtos/GetAccountTransactionResponse.dto.ts | 114 ++++++++ .../dtos/GetAccountTransactionsQuery.dto.ts | 23 ++ ...ankingRecognizedTransactions.controller.ts | 13 + .../GetRecognizedTransactionResponse.dto.ts | 117 ++++++++ .../BankingPendingTransactions.controller.ts | 14 +- .../dtos/GetPendingTransactionResponse.dto.ts | 76 +++++ .../BankingTransactionsExclude.controller.ts | 21 +- .../GetExcludedBankTransactionResponse.dto.ts | 205 ++++++++++++++ .../ExcludedBankTransaction.transformer.ts | 3 + .../queries/GetExcludedBankTransactions.ts | 4 +- .../APAgingSummary.controller.ts | 13 +- .../APAgingSummary/APAgingSummary.swagger.ts | 178 ++++++++++++ .../ARAgingSummary.controller.ts | 13 +- .../ARAgingSummary/ARAgingSummary.swagger.ts | 264 ++++++++++++++++++ .../Organization/Organization.controller.ts | 16 +- .../GetCurrentOrganizationResponse.dto.ts | 151 ++++++++++ .../webapp/src/hooks/query/organization.tsx | 2 +- 23 files changed, 1248 insertions(+), 23 deletions(-) create mode 100644 packages/server/src/i18n/en/branches.json create mode 100644 packages/server/src/modules/Accounts/dtos/GetAccountTransactionResponse.dto.ts create mode 100644 packages/server/src/modules/Accounts/dtos/GetAccountTransactionsQuery.dto.ts create mode 100644 packages/server/src/modules/BankingTranasctionsRegonize/dtos/GetRecognizedTransactionResponse.dto.ts create mode 100644 packages/server/src/modules/BankingTransactions/dtos/GetPendingTransactionResponse.dto.ts create mode 100644 packages/server/src/modules/BankingTransactionsExclude/dtos/GetExcludedBankTransactionResponse.dto.ts create mode 100644 packages/server/src/modules/BankingTransactionsExclude/queries/ExcludedBankTransaction.transformer.ts create mode 100644 packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.swagger.ts create mode 100644 packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.swagger.ts create mode 100644 packages/server/src/modules/Organization/dtos/GetCurrentOrganizationResponse.dto.ts diff --git a/packages/server/src/i18n/en/branches.json b/packages/server/src/i18n/en/branches.json new file mode 100644 index 000000000..03480d5a0 --- /dev/null +++ b/packages/server/src/i18n/en/branches.json @@ -0,0 +1,3 @@ +{ + "head_branch": "Head Branch" +} diff --git a/packages/server/src/modules/Accounts/Account.transformer.ts b/packages/server/src/modules/Accounts/Account.transformer.ts index b65dd82a5..df20e3257 100644 --- a/packages/server/src/modules/Accounts/Account.transformer.ts +++ b/packages/server/src/modules/Accounts/Account.transformer.ts @@ -85,9 +85,7 @@ export class AccountTransformer extends Transformer { * @returns {boolean} */ protected isFeedsPaused = (account: Account): boolean => { - // return account.plaidItem?.isPaused || false; - - return false; + return account.plaidItem?.isPaused || false; }; /** diff --git a/packages/server/src/modules/Accounts/Accounts.controller.ts b/packages/server/src/modules/Accounts/Accounts.controller.ts index 0e000a5ca..bbdec663a 100644 --- a/packages/server/src/modules/Accounts/Accounts.controller.ts +++ b/packages/server/src/modules/Accounts/Accounts.controller.ts @@ -11,7 +11,7 @@ import { import { AccountsApplication } from './AccountsApplication.service'; import { CreateAccountDTO } from './CreateAccount.dto'; import { EditAccountDTO } from './EditAccount.dto'; -import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types'; +import { IAccountsFilter } from './Accounts.types'; import { ApiExtraModels, ApiOperation, @@ -22,11 +22,14 @@ import { } from '@nestjs/swagger'; import { AccountResponseDto } from './dtos/AccountResponse.dto'; import { AccountTypeResponseDto } from './dtos/AccountTypeResponse.dto'; +import { GetAccountTransactionResponseDto } from './dtos/GetAccountTransactionResponse.dto'; +import { GetAccountTransactionsQueryDto } from './dtos/GetAccountTransactionsQuery.dto'; @Controller('accounts') @ApiTags('Accounts') @ApiExtraModels(AccountResponseDto) @ApiExtraModels(AccountTypeResponseDto) +@ApiExtraModels(GetAccountTransactionResponseDto) export class AccountsController { constructor(private readonly accountsApplication: AccountsApplication) {} @@ -132,8 +135,16 @@ export class AccountsController { @ApiResponse({ status: 200, description: 'The account transactions have been successfully retrieved.', + schema: { + type: 'array', + items: { + $ref: getSchemaPath(GetAccountTransactionResponseDto), + }, + }, }) - async getAccountTransactions(@Query() filter: IAccountsTransactionsFilter) { + async getAccountTransactions( + @Query() filter: GetAccountTransactionsQueryDto, + ) { return this.accountsApplication.getAccountsTransactions(filter); } diff --git a/packages/server/src/modules/Accounts/AccountsApplication.service.ts b/packages/server/src/modules/Accounts/AccountsApplication.service.ts index 8828f2727..e1f02013b 100644 --- a/packages/server/src/modules/Accounts/AccountsApplication.service.ts +++ b/packages/server/src/modules/Accounts/AccountsApplication.service.ts @@ -10,13 +10,10 @@ import { GetAccount } from './GetAccount.service'; import { ActivateAccount } from './ActivateAccount.service'; import { GetAccountTypesService } from './GetAccountTypes.service'; import { GetAccountTransactionsService } from './GetAccountTransactions.service'; -import { - IAccountsFilter, - IAccountsTransactionsFilter, - IGetAccountTransactionPOJO, -} from './Accounts.types'; +import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types'; import { GetAccountsService } from './GetAccounts.service'; import { IFilterMeta } from '@/interfaces/Model'; +import { GetAccountTransactionResponseDto } from './dtos/GetAccountTransactionResponse.dto'; @Injectable() export class AccountsApplication { @@ -127,7 +124,7 @@ export class AccountsApplication { */ public getAccountsTransactions = ( filter: IAccountsTransactionsFilter, - ): Promise => { + ): Promise> => { return this.getAccountTransactionsService.getAccountsTransactions(filter); }; } diff --git a/packages/server/src/modules/Accounts/DeleteAccount.service.ts b/packages/server/src/modules/Accounts/DeleteAccount.service.ts index 40f3978bd..985989998 100644 --- a/packages/server/src/modules/Accounts/DeleteAccount.service.ts +++ b/packages/server/src/modules/Accounts/DeleteAccount.service.ts @@ -1,6 +1,5 @@ import { Knex } from 'knex'; import { Inject, Injectable } from '@nestjs/common'; -// import { IAccountEventDeletedPayload } from '@/interfaces'; import { CommandAccountValidators } from './CommandAccountValidators.service'; import { Account } from './models/Account.model'; import { EventEmitter2 } from '@nestjs/event-emitter'; @@ -8,6 +7,7 @@ import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; import { events } from '@/common/events/events'; import { IAccountEventDeletedPayload } from './Accounts.types'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { ERRORS } from './constants'; @Injectable() export class DeleteAccount { @@ -70,8 +70,12 @@ export class DeleteAccount { await this.unassociateChildrenAccountsFromParent(accountId, trx); // Deletes account by the given id. - await this.accountModel().query(trx).deleteById(accountId); - + await this.accountModel() + .query(trx) + .findById(accountId) + .deleteIfNoRelations({ + type: ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS, + }); // Triggers `onAccountDeleted` event. await this.eventEmitter.emitAsync(events.accounts.onDeleted, { accountId, diff --git a/packages/server/src/modules/Accounts/GetAccountTransactions.service.ts b/packages/server/src/modules/Accounts/GetAccountTransactions.service.ts index 24e4bab22..4e5c50e32 100644 --- a/packages/server/src/modules/Accounts/GetAccountTransactions.service.ts +++ b/packages/server/src/modules/Accounts/GetAccountTransactions.service.ts @@ -8,6 +8,7 @@ import { Account } from './models/Account.model'; import { Inject, Injectable } from '@nestjs/common'; import { TransformerInjectable } from '../Transformer/TransformerInjectable.service'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { GetAccountTransactionResponseDto } from './dtos/GetAccountTransactionResponse.dto'; @Injectable() export class GetAccountTransactionsService { @@ -29,7 +30,7 @@ export class GetAccountTransactionsService { */ public getAccountsTransactions = async ( filter: IAccountsTransactionsFilter, - ): Promise => { + ): Promise> => { // Retrieve the given account or throw not found error. if (filter.accountId) { await this.account().query().findById(filter.accountId).throwIfNotFound(); diff --git a/packages/server/src/modules/Accounts/dtos/GetAccountTransactionResponse.dto.ts b/packages/server/src/modules/Accounts/dtos/GetAccountTransactionResponse.dto.ts new file mode 100644 index 000000000..7e270613a --- /dev/null +++ b/packages/server/src/modules/Accounts/dtos/GetAccountTransactionResponse.dto.ts @@ -0,0 +1,114 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class GetAccountTransactionResponseDto { + /** + * The transaction date (ISO string or Date). + */ + @ApiProperty({ + description: 'The transaction date (ISO string or Date)', + example: '2024-01-01', + }) + date: string | Date; + + /** + * The formatted transaction date (string). + */ + @ApiProperty({ + description: 'The formatted transaction date', + example: '01 Jan 2024', + }) + formattedDate: string; + + /** + * The transaction type (referenceType from model). + */ + @ApiProperty({ + description: 'The transaction type (referenceType from model)', + example: 'INVOICE', + }) + transactionType: string; + + /** + * The transaction id (referenceId from model). + */ + @ApiProperty({ + description: 'The transaction id (referenceId from model)', + example: 123, + }) + transactionId: number; + + /** + * The formatted transaction type (translated string). + */ + @ApiProperty({ + description: 'The formatted transaction type (translated string)', + example: 'Invoice', + }) + transactionTypeFormatted: string; + + /** + * The credit amount (number). + */ + @ApiProperty({ description: 'The credit amount', example: 100 }) + credit: number; + + /** + * The debit amount (number). + */ + @ApiProperty({ description: 'The debit amount', example: 50 }) + debit: number; + + /** + * The formatted credit amount (string, e.g. currency formatted). + */ + @ApiProperty({ + description: 'The formatted credit amount (e.g. currency formatted)', + example: '100.00 USD', + }) + formattedCredit: string; + + /** + * The formatted debit amount (string, e.g. currency formatted). + */ + @ApiProperty({ + description: 'The formatted debit amount (e.g. currency formatted)', + example: '50.00 USD', + }) + formattedDebit: string; + + /** + * The foreign currency credit (number, credit * exchangeRate). + */ + @ApiProperty({ + description: 'The foreign currency credit (credit * exchangeRate)', + example: 120, + }) + fcCredit: number; + + /** + * The foreign currency debit (number, debit * exchangeRate). + */ + @ApiProperty({ + description: 'The foreign currency debit (debit * exchangeRate)', + example: 60, + }) + fcDebit: number; + + /** + * The formatted foreign currency credit (string). + */ + @ApiProperty({ + description: 'The formatted foreign currency credit', + example: '120.00 EUR', + }) + formattedFcCredit: string; + + /** + * The formatted foreign currency debit (string). + */ + @ApiProperty({ + description: 'The formatted foreign currency debit', + example: '60.00 EUR', + }) + formattedFcDebit: string; +} diff --git a/packages/server/src/modules/Accounts/dtos/GetAccountTransactionsQuery.dto.ts b/packages/server/src/modules/Accounts/dtos/GetAccountTransactionsQuery.dto.ts new file mode 100644 index 000000000..799dc2d1a --- /dev/null +++ b/packages/server/src/modules/Accounts/dtos/GetAccountTransactionsQuery.dto.ts @@ -0,0 +1,23 @@ +import { IsInt, IsOptional } from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { ToNumber } from '@/common/decorators/Validators'; + +export class GetAccountTransactionsQueryDto { + @ApiPropertyOptional({ + type: Number, + description: 'ID of the account to fetch transactions for', + }) + @IsOptional() + @IsInt() + @ToNumber() + accountId?: number; + + @ApiPropertyOptional({ + type: Number, + description: 'Maximum number of transactions to return', + }) + @IsOptional() + @IsInt() + @ToNumber() + limit?: number; +} diff --git a/packages/server/src/modules/BankingTranasctionsRegonize/BankingRecognizedTransactions.controller.ts b/packages/server/src/modules/BankingTranasctionsRegonize/BankingRecognizedTransactions.controller.ts index c55d4827b..7735494a7 100644 --- a/packages/server/src/modules/BankingTranasctionsRegonize/BankingRecognizedTransactions.controller.ts +++ b/packages/server/src/modules/BankingTranasctionsRegonize/BankingRecognizedTransactions.controller.ts @@ -5,11 +5,15 @@ import { ApiParam, ApiQuery, ApiResponse, + ApiExtraModels, + getSchemaPath, } from '@nestjs/swagger'; import { RecognizedTransactionsApplication } from './RecognizedTransactions.application'; +import { GetRecognizedTransactionResponseDto } from './dtos/GetRecognizedTransactionResponse.dto'; @Controller('banking/recognized') @ApiTags('Banking Recognized Transactions') +@ApiExtraModels(GetRecognizedTransactionResponseDto) export class BankingRecognizedTransactionsController { constructor( private readonly recognizedTransactionsApplication: RecognizedTransactionsApplication, @@ -25,6 +29,9 @@ export class BankingRecognizedTransactionsController { @ApiResponse({ status: 200, description: 'Returns the recognized transaction details', + schema: { + $ref: getSchemaPath(GetRecognizedTransactionResponseDto), + }, }) @ApiResponse({ status: 404, @@ -48,6 +55,12 @@ export class BankingRecognizedTransactionsController { @ApiResponse({ status: 200, description: 'Returns a list of recognized transactions', + schema: { + type: 'array', + items: { + $ref: getSchemaPath(GetRecognizedTransactionResponseDto), + }, + }, }) async getRecognizedTransactions(@Query() query: any) { return this.recognizedTransactionsApplication.getRecognizedTransactions( diff --git a/packages/server/src/modules/BankingTranasctionsRegonize/dtos/GetRecognizedTransactionResponse.dto.ts b/packages/server/src/modules/BankingTranasctionsRegonize/dtos/GetRecognizedTransactionResponse.dto.ts new file mode 100644 index 000000000..cba1cad62 --- /dev/null +++ b/packages/server/src/modules/BankingTranasctionsRegonize/dtos/GetRecognizedTransactionResponse.dto.ts @@ -0,0 +1,117 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class GetRecognizedTransactionResponseDto { + @ApiProperty({ + description: 'The unique identifier of the uncategorized transaction', + example: 123, + }) + uncategorizedTransactionId: number; + + @ApiProperty({ + description: 'The reference number of the transaction', + example: 'TRX-2024-001', + }) + referenceNo: string; + + @ApiProperty({ + description: 'The description of the transaction', + example: 'Payment for invoice #123', + }) + description: string; + + @ApiProperty({ + description: 'The payee of the transaction', + example: 'John Doe', + }) + payee: string; + + @ApiProperty({ + description: 'The amount of the transaction', + example: 1500.75, + }) + amount: number; + + @ApiProperty({ + description: 'The formatted amount of the transaction', + example: '$1,500.75', + }) + formattedAmount: string; + + @ApiProperty({ + description: 'The date of the transaction', + example: '2024-04-01', + }) + date: string; + + @ApiProperty({ + description: 'The formatted date of the transaction', + example: 'Apr 1, 2024', + }) + formattedDate: string; + + @ApiProperty({ description: 'The assigned account ID', example: 10 }) + assignedAccountId: number; + + @ApiProperty({ + description: 'The assigned account name', + example: 'Bank Account', + }) + assignedAccountName: string; + + @ApiProperty({ description: 'The assigned account code', example: '1001' }) + assignedAccountCode: string; + + @ApiProperty({ description: 'The assigned payee', example: 'Jane Smith' }) + assignedPayee: string; + + @ApiProperty({ description: 'The assigned memo', example: 'Office supplies' }) + assignedMemo: string; + + @ApiProperty({ + description: 'The assigned category', + example: 'Office Expenses', + }) + assignedCategory: string; + + @ApiProperty({ + description: 'The formatted assigned category', + example: 'Other Income', + }) + assignedCategoryFormatted: string; + + @ApiProperty({ description: 'The withdrawal amount', example: 500 }) + withdrawal: number; + + @ApiProperty({ description: 'The deposit amount', example: 1000 }) + deposit: number; + + @ApiProperty({ + description: 'Whether this is a deposit transaction', + example: true, + }) + isDepositTransaction: boolean; + + @ApiProperty({ + description: 'Whether this is a withdrawal transaction', + example: false, + }) + isWithdrawalTransaction: boolean; + + @ApiProperty({ + description: 'The formatted deposit amount', + example: '$1,000.00', + }) + formattedDepositAmount: string; + + @ApiProperty({ + description: 'The formatted withdrawal amount', + example: '$500.00', + }) + formattedWithdrawalAmount: string; + + @ApiProperty({ description: 'The bank rule ID', example: 'BR-001' }) + bankRuleId: string; + + @ApiProperty({ description: 'The bank rule name', example: 'Salary Rule' }) + bankRuleName: string; +} diff --git a/packages/server/src/modules/BankingTransactions/controllers/BankingPendingTransactions.controller.ts b/packages/server/src/modules/BankingTransactions/controllers/BankingPendingTransactions.controller.ts index 95405aa67..45d0121ef 100644 --- a/packages/server/src/modules/BankingTransactions/controllers/BankingPendingTransactions.controller.ts +++ b/packages/server/src/modules/BankingTransactions/controllers/BankingPendingTransactions.controller.ts @@ -1,10 +1,19 @@ import { Controller, Get, Query } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiQuery, + getSchemaPath, + ApiExtraModels, +} from '@nestjs/swagger'; import { BankingTransactionsApplication } from '../BankingTransactionsApplication.service'; import { GetPendingTransactionsQueryDto } from '../dtos/GetPendingTransactionsQuery.dto'; +import { GetPendingTransactionResponseDto } from '../dtos/GetPendingTransactionResponse.dto'; @Controller('banking/pending') @ApiTags('Banking Pending Transactions') +@ApiExtraModels(GetPendingTransactionResponseDto) export class BankingPendingTransactionsController { constructor( private readonly bankingTransactionsApplication: BankingTransactionsApplication, @@ -15,6 +24,9 @@ export class BankingPendingTransactionsController { @ApiResponse({ status: 200, description: 'Returns a list of pending bank account transactions', + schema: { + $ref: getSchemaPath(GetPendingTransactionResponseDto), + }, }) @ApiQuery({ name: 'page', diff --git a/packages/server/src/modules/BankingTransactions/dtos/GetPendingTransactionResponse.dto.ts b/packages/server/src/modules/BankingTransactions/dtos/GetPendingTransactionResponse.dto.ts new file mode 100644 index 000000000..2cb0d1f15 --- /dev/null +++ b/packages/server/src/modules/BankingTransactions/dtos/GetPendingTransactionResponse.dto.ts @@ -0,0 +1,76 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNumber, IsBoolean, IsDateString } from 'class-validator'; + +export class GetPendingTransactionResponseDto { + @ApiProperty({ description: 'Transaction amount' }) + @IsNumber() + amount: number; + + @ApiProperty({ description: 'Transaction date' }) + @IsDateString() + date: Date | string; + + @ApiProperty({ description: 'Bank account ID' }) + @IsNumber() + accountId: number; + + @ApiProperty({ description: 'Transaction reference number', required: false }) + @IsString() + referenceNo: string; + + @ApiProperty({ description: 'Payee', required: false }) + @IsString() + payee: string; + + @ApiProperty({ description: 'Transaction description', required: false }) + @IsString() + description: string; + + @ApiProperty({ description: 'Plaid transaction ID', required: false }) + @IsString() + plaidTransactionId: string; + + @ApiProperty({ description: 'Recognized transaction ID', required: false }) + @IsNumber() + recognizedTransactionId: number; + + @ApiProperty({ description: 'Is transaction pending?' }) + @IsBoolean() + pending: boolean; + + @ApiProperty({ description: 'Transaction currency code' }) + @IsString() + currencyCode: string; + + @ApiProperty({ description: 'Withdrawal amount' }) + @IsNumber() + withdrawal: number; + + @ApiProperty({ description: 'Deposit amount' }) + @IsNumber() + deposit: number; + + @ApiProperty({ description: 'Is deposit transaction?' }) + @IsBoolean() + isDepositTransaction: boolean; + + @ApiProperty({ description: 'Is withdrawal transaction?' }) + @IsBoolean() + isWithdrawalTransaction: boolean; + + @ApiProperty({ description: 'Formatted amount' }) + @IsString() + formattedAmount: string; + + @ApiProperty({ description: 'Formatted date' }) + @IsString() + formattedDate: string; + + @ApiProperty({ description: 'Formatted deposit amount' }) + @IsString() + formattedDepositAmount: string; + + @ApiProperty({ description: 'Formatted withdrawal amount' }) + @IsString() + formattedWithdrawalAmount: string; +} diff --git a/packages/server/src/modules/BankingTransactionsExclude/BankingTransactionsExclude.controller.ts b/packages/server/src/modules/BankingTransactionsExclude/BankingTransactionsExclude.controller.ts index 457637911..344c00bff 100644 --- a/packages/server/src/modules/BankingTransactionsExclude/BankingTransactionsExclude.controller.ts +++ b/packages/server/src/modules/BankingTransactionsExclude/BankingTransactionsExclude.controller.ts @@ -10,10 +10,18 @@ import { } from '@nestjs/common'; import { ExcludeBankTransactionsApplication } from './ExcludeBankTransactionsApplication'; import { ExcludedBankTransactionsQuery } from './types/BankTransactionsExclude.types'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { + ApiExtraModels, + ApiOperation, + ApiResponse, + ApiTags, + getSchemaPath, +} from '@nestjs/swagger'; +import { GetExcludedBankTransactionResponseDto } from './dtos/GetExcludedBankTransactionResponse.dto'; @Controller('banking/exclude') @ApiTags('Banking Transactions') +@ApiExtraModels(GetExcludedBankTransactionResponseDto) export class BankingTransactionsExcludeController { constructor( private readonly excludeBankTransactionsApplication: ExcludeBankTransactionsApplication, @@ -35,6 +43,17 @@ export class BankingTransactionsExcludeController { @Get() @ApiOperation({ summary: 'Retrieves the excluded bank transactions.' }) + @ApiResponse({ + status: 200, + description: + 'The excluded bank transactions has been retrieved successfully.', + schema: { + type: 'array', + items: { + $ref: getSchemaPath(GetExcludedBankTransactionResponseDto), + }, + }, + }) public getExcludedBankTransactions( @Query() query: ExcludedBankTransactionsQuery, ) { diff --git a/packages/server/src/modules/BankingTransactionsExclude/dtos/GetExcludedBankTransactionResponse.dto.ts b/packages/server/src/modules/BankingTransactionsExclude/dtos/GetExcludedBankTransactionResponse.dto.ts new file mode 100644 index 000000000..08fc280e7 --- /dev/null +++ b/packages/server/src/modules/BankingTransactionsExclude/dtos/GetExcludedBankTransactionResponse.dto.ts @@ -0,0 +1,205 @@ +import { ApiProperty, ApiExtraModels } from '@nestjs/swagger'; +import { + IsNumber, + IsString, + IsBoolean, + IsDateString, + IsOptional, +} from 'class-validator'; + +@ApiExtraModels() +export class GetExcludedBankTransactionResponseDto { + @ApiProperty({ + description: + 'Transaction amount (positive for deposit, negative for withdrawal)', + }) + @IsNumber() + amount: number; + + @ApiProperty({ description: 'Transaction date (ISO string or Date)' }) + @IsDateString() + date: string | Date; + + @ApiProperty({ description: 'ID of the associated bank account' }) + @IsNumber() + accountId: number; + + @ApiProperty({ + description: 'Reference number for the transaction', + required: false, + }) + @IsString() + @IsOptional() + referenceNo?: string; + + @ApiProperty({ description: 'Payee name', required: false }) + @IsString() + @IsOptional() + payee?: string; + + @ApiProperty({ description: 'Transaction description', required: false }) + @IsString() + @IsOptional() + description?: string; + + @ApiProperty({ description: 'Plaid transaction ID', required: false }) + @IsString() + @IsOptional() + plaidTransactionId?: string; + + @ApiProperty({ + description: 'Whether the transaction is pending', + required: false, + }) + @IsBoolean() + @IsOptional() + pending?: boolean; + + @ApiProperty({ + description: 'ID of the recognized transaction, if any', + required: false, + }) + @IsNumber() + @IsOptional() + recognizedTransactionId?: number; + + @ApiProperty({ + description: 'Categorization reference type', + required: false, + }) + @IsString() + @IsOptional() + categorizeRefType?: string; + + @ApiProperty({ description: 'Categorization reference ID', required: false }) + @IsNumber() + @IsOptional() + categorizeRefId?: number; + + @ApiProperty({ + description: 'Formatted amount (localized string)', + required: false, + }) + @IsString() + @IsOptional() + formattedAmount?: string; + + @ApiProperty({ description: 'Formatted transaction date', required: false }) + @IsString() + @IsOptional() + formattedDate?: string; + + @ApiProperty({ description: 'Formatted deposit amount', required: false }) + @IsString() + @IsOptional() + formattedDepositAmount?: string; + + @ApiProperty({ description: 'Formatted withdrawal amount', required: false }) + @IsString() + @IsOptional() + formattedWithdrawalAmount?: string; + + @ApiProperty({ description: 'Withdrawal amount', required: false }) + @IsNumber() + @IsOptional() + withdrawal?: number; + + @ApiProperty({ description: 'Deposit amount', required: false }) + @IsNumber() + @IsOptional() + deposit?: number; + + @ApiProperty({ description: 'True if deposit transaction', required: false }) + @IsBoolean() + @IsOptional() + isDepositTransaction?: boolean; + + @ApiProperty({ + description: 'True if withdrawal transaction', + required: false, + }) + @IsBoolean() + @IsOptional() + isWithdrawalTransaction?: boolean; + + @ApiProperty({ + description: 'True if transaction is recognized', + required: false, + }) + @IsBoolean() + @IsOptional() + isRecognized?: boolean; + + @ApiProperty({ + description: 'True if transaction is excluded', + required: false, + }) + @IsBoolean() + @IsOptional() + isExcluded?: boolean; + + @ApiProperty({ + description: 'True if transaction is pending', + required: false, + }) + @IsBoolean() + @IsOptional() + isPending?: boolean; + + // Recognized transaction fields (from transformer) + @ApiProperty({ + description: 'Assigned account ID from recognized transaction', + required: false, + }) + @IsNumber() + @IsOptional() + assignedAccountId?: number; + + @ApiProperty({ + description: 'Assigned account name from recognized transaction', + required: false, + }) + @IsString() + @IsOptional() + assignedAccountName?: string; + + @ApiProperty({ + description: 'Assigned account code from recognized transaction', + required: false, + }) + @IsString() + @IsOptional() + assignedAccountCode?: string; + + @ApiProperty({ + description: 'Assigned payee from recognized transaction', + required: false, + }) + @IsString() + @IsOptional() + assignedPayee?: string; + + @ApiProperty({ + description: 'Assigned memo from recognized transaction', + required: false, + }) + @IsString() + @IsOptional() + assignedMemo?: string; + + @ApiProperty({ + description: 'Assigned category from recognized transaction', + required: false, + }) + @IsString() + @IsOptional() + assignedCategory?: string; + + @ApiProperty({ + description: 'Assigned formatted category from recognized transaction', + required: false, + }) + @IsString() + @IsOptional() + assignedCategoryFormatted?: string; +} diff --git a/packages/server/src/modules/BankingTransactionsExclude/queries/ExcludedBankTransaction.transformer.ts b/packages/server/src/modules/BankingTransactionsExclude/queries/ExcludedBankTransaction.transformer.ts new file mode 100644 index 000000000..754ea182c --- /dev/null +++ b/packages/server/src/modules/BankingTransactionsExclude/queries/ExcludedBankTransaction.transformer.ts @@ -0,0 +1,3 @@ +import { UncategorizedTransactionTransformer } from '@/modules/BankingCategorize/commands/UncategorizedTransaction.transformer'; + +export class ExcludedBankTransactionTransformer extends UncategorizedTransactionTransformer {} diff --git a/packages/server/src/modules/BankingTransactionsExclude/queries/GetExcludedBankTransactions.ts b/packages/server/src/modules/BankingTransactionsExclude/queries/GetExcludedBankTransactions.ts index 60e980100..e7c97678a 100644 --- a/packages/server/src/modules/BankingTransactionsExclude/queries/GetExcludedBankTransactions.ts +++ b/packages/server/src/modules/BankingTransactionsExclude/queries/GetExcludedBankTransactions.ts @@ -1,9 +1,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { ExcludedBankTransactionsQuery } from '../types/BankTransactionsExclude.types'; -import { UncategorizedTransactionTransformer } from '@/modules/BankingCategorize/commands/UncategorizedTransaction.transformer'; import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { ExcludedBankTransactionTransformer } from './ExcludedBankTransaction.transformer'; @Injectable() export class GetExcludedBankTransactionsService { @@ -60,7 +60,7 @@ export class GetExcludedBankTransactionsService { const data = await this.transformer.transform( results, - new UncategorizedTransactionTransformer(), + new ExcludedBankTransactionTransformer(), ); return { data, pagination }; } diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts index a02f77b34..4e9bbec54 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts @@ -2,8 +2,14 @@ import { Response } from 'express'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; import { APAgingSummaryApplication } from './APAgingSummaryApplication'; import { AcceptType } from '@/constants/accept-type'; -import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger'; +import { + ApiOperation, + ApiProduces, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; +import { APAgingSummaryResponseExample } from './APAgingSummary.swagger'; @Controller('reports/payable-aging-summary') @ApiTags('Reports') @@ -12,6 +18,11 @@ export class APAgingSummaryController { @Get() @ApiOperation({ summary: 'Get payable aging summary' }) + @ApiResponse({ + status: 200, + description: 'A/P aging summary response', + example: APAgingSummaryResponseExample, + }) @ApiProduces( AcceptType.ApplicationJson, AcceptType.ApplicationJsonTable, diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.swagger.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.swagger.ts new file mode 100644 index 000000000..2d936285c --- /dev/null +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.swagger.ts @@ -0,0 +1,178 @@ +export const APAgingSummaryResponseExample = { + data: { + vendors: [ + { + vendor_name: 'asdasd, asdasd', + current: { + amount: 0, + formatted_amount: '', + }, + aging: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + total: { + amount: 0, + formatted_amount: '', + }, + }, + ], + total: { + amount: 0, + formatted_amount: '$0.00', + }, + }, + { + vendor_name: 'Ahmed Bouhuolia', + current: { + amount: 32000, + formatted_amount: '32,000.00', + }, + aging: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + total: { + amount: 0, + formatted_amount: '', + }, + }, + ], + total: { + amount: 32000, + formatted_amount: '$32,000.00', + }, + }, + ], + total: { + current: { + amount: 32000, + formatted_amount: '$32,000.00', + }, + aging: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + total: { + amount: 0, + formatted_amount: '$0.00', + }, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + total: { + amount: 0, + formatted_amount: '$0.00', + }, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + total: { + amount: 0, + formatted_amount: '$0.00', + }, + }, + ], + total: { + amount: 32000, + formatted_amount: '$32,000.00', + }, + }, + }, + columns: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + }, + ], + query: { + as_date: '2025-06-30', + aging_days_before: 30, + aging_periods: 3, + number_format: { + precision: 2, + divide_on1000: false, + show_zero: false, + format_money: 'total', + negative_format: 'mines', + }, + vendors_ids: [], + branches_ids: [], + none_zero: false, + }, + meta: { + organization_name: 'BIGCAPITAL, INC', + base_currency: 'USD', + date_format: 'DD MMM yyyy', + is_cost_compute_running: false, + sheet_name: 'A/P Aging Summary', + formatted_as_date: '2025/06/30', + formatted_date_range: 'As 2025/06/30', + }, +}; diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts index 23c5f7edc..1abc2f2d0 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts @@ -3,8 +3,14 @@ import { Query, Res } from '@nestjs/common'; import { ARAgingSummaryApplication } from './ARAgingSummaryApplication'; import { AcceptType } from '@/constants/accept-type'; import { Response } from 'express'; -import { ApiOperation, ApiProduces, ApiTags } from '@nestjs/swagger'; +import { + ApiOperation, + ApiProduces, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; +import { ARAgingSummaryResponseExample } from './ARAgingSummary.swagger'; @Controller('reports/receivable-aging-summary') @ApiTags('Reports') @@ -13,6 +19,11 @@ export class ARAgingSummaryController { @Get() @ApiOperation({ summary: 'Get receivable aging summary' }) + @ApiResponse({ + status: 200, + description: 'Receivable aging summary response', + example: ARAgingSummaryResponseExample, + }) @ApiProduces( AcceptType.ApplicationJson, AcceptType.ApplicationJsonTable, diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.swagger.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.swagger.ts new file mode 100644 index 000000000..a6dd79d82 --- /dev/null +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.swagger.ts @@ -0,0 +1,264 @@ +export const ARAgingSummaryResponseExample = { + query: { + as_date: '2025-06-30', + aging_days_before: 30, + aging_periods: 3, + number_format: { + divide_on1000: false, + negative_format: 'mines', + show_zero: false, + format_money: 'total', + precision: 2, + }, + customers_ids: [], + branches_ids: [], + none_zero: false, + }, + columns: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + }, + ], + data: { + customers: [ + { + customer_name: 'business', + current: { + amount: 0, + formatted_amount: '', + }, + aging: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + total: { + amount: 5000, + formatted_amount: '5,000.00', + }, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + total: { + amount: 0, + formatted_amount: '', + }, + }, + ], + total: { + amount: 5000, + formatted_amount: '$5,000.00', + }, + }, + { + customer_name: 'business', + current: { + amount: 0, + formatted_amount: '', + }, + aging: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + total: { + amount: 0, + formatted_amount: '', + }, + }, + ], + total: { + amount: 0, + formatted_amount: '$0.00', + }, + }, + { + customer_name: 'asdsadasd, asd', + current: { + amount: 0, + formatted_amount: '', + }, + aging: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + total: { + amount: 0, + formatted_amount: '', + }, + }, + ], + total: { + amount: 0, + formatted_amount: '$0.00', + }, + }, + { + customer_name: 'Ahmed Bouhuolia', + current: { + amount: 300000, + formatted_amount: '300,000.00', + }, + aging: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + total: { + amount: 0, + formatted_amount: '', + }, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + total: { + amount: 0, + formatted_amount: '', + }, + }, + ], + total: { + amount: 300000, + formatted_amount: '$300,000.00', + }, + }, + ], + total: { + current: { + amount: 300000, + formatted_amount: '$300,000.00', + }, + aging: [ + { + from_period: '2025-06-30', + to_period: '2025-05-31', + before_days: 0, + to_days: 30, + total: { + amount: 5000, + formatted_amount: '$5,000.00', + }, + }, + { + from_period: '2025-05-31', + to_period: '2025-05-01', + before_days: 31, + to_days: 60, + total: { + amount: 0, + formatted_amount: '$0.00', + }, + }, + { + from_period: '2025-05-01', + to_period: null, + before_days: 61, + to_days: null, + total: { + amount: 0, + formatted_amount: '$0.00', + }, + }, + ], + total: { + amount: 305000, + formatted_amount: '$305,000.00', + }, + }, + }, + meta: { + organization_name: 'BIGCAPITAL, INC', + base_currency: 'USD', + date_format: 'DD MMM yyyy', + is_cost_compute_running: false, + sheet_name: 'A/R Aging Summary', + formatted_as_date: '2025/06/30', + formatted_date_range: 'As 2025/06/30', + }, +}; diff --git a/packages/server/src/modules/Organization/Organization.controller.ts b/packages/server/src/modules/Organization/Organization.controller.ts index e112ceafc..0586d45aa 100644 --- a/packages/server/src/modules/Organization/Organization.controller.ts +++ b/packages/server/src/modules/Organization/Organization.controller.ts @@ -4,6 +4,9 @@ import { ApiResponse, ApiBody, ApiOkResponse, + ApiExtraModels, + getSchemaPath, + ApiParam, } from '@nestjs/swagger'; import { Controller, @@ -29,11 +32,13 @@ import { OrganizationBuildResponseExample, OrganizationBuiltResponseExample, } from './Organization.swagger'; +import { GetCurrentOrganizationResponseDto } from './dtos/GetCurrentOrganizationResponse.dto'; @ApiTags('Organization') @Controller('organization') @IgnoreTenantInitializedRoute() @IgnoreTenantSeededRoute() +@ApiExtraModels(GetCurrentOrganizationResponseDto) export class OrganizationController { constructor( private readonly buildOrganizationService: BuildOrganizationService, @@ -68,6 +73,12 @@ export class OrganizationController { } @Get('build/:buildJobId') + @ApiParam({ + name: 'buildJobId', + required: true, + type: Number, + description: 'The build job id', + }) @HttpCode(200) @ApiOperation({ summary: 'Gets the organization build job details' }) async buildJob(@Param('buildJobId') buildJobId: string) { @@ -80,12 +91,15 @@ export class OrganizationController { @ApiResponse({ status: 200, description: 'Returns the current organization', + schema: { + $ref: getSchemaPath(GetCurrentOrganizationResponseDto), + }, }) async currentOrganization() { const organization = await this.getCurrentOrgService.getCurrentOrganization(); - return { organization }; + return organization; } @Get('base-currency-mutate') diff --git a/packages/server/src/modules/Organization/dtos/GetCurrentOrganizationResponse.dto.ts b/packages/server/src/modules/Organization/dtos/GetCurrentOrganizationResponse.dto.ts new file mode 100644 index 000000000..e30b42eba --- /dev/null +++ b/packages/server/src/modules/Organization/dtos/GetCurrentOrganizationResponse.dto.ts @@ -0,0 +1,151 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class OrganizationMetadataResponseDto { + @ApiProperty({ + description: 'Internal numeric ID of the metadata', + example: 1, + }) + id: number; + + @ApiProperty({ + description: 'Tenant ID associated with the organization', + example: 101, + }) + tenantId: number; + + @ApiProperty({ + description: 'Name of the organization', + example: 'Acme Inc.', + }) + name: string; + + @ApiPropertyOptional({ + description: 'Industry of the organization', + example: 'Technology', + }) + industry: string; + + @ApiProperty({ description: 'Location of the organization', example: 'US' }) + location: string; + + @ApiProperty({ + description: 'Base currency in ISO 4217 format', + example: 'USD', + }) + baseCurrency: string; + + @ApiProperty({ + description: 'Language/locale of the organization', + example: 'en-US', + }) + language: string; + + @ApiProperty({ + description: 'Timezone of the organization', + example: 'America/New_York', + }) + timezone: string; + + @ApiProperty({ + description: 'Date format used by the organization', + example: 'MM/DD/YYYY', + }) + dateFormat: string; + + @ApiProperty({ + description: 'Fiscal year of the organization', + example: 'January', + }) + fiscalYear: string; + + @ApiPropertyOptional({ + description: 'Tax identification number', + example: '12-3456789', + nullable: true, + }) + taxNumber: string; + + @ApiPropertyOptional({ + description: 'Primary brand color in hex format', + example: '#4285F4', + nullable: true, + }) + primaryColor: string; + + @ApiPropertyOptional({ + description: 'Logo file key reference', + example: 'organizations/acme-logo-123456.png', + nullable: true, + }) + logoKey: string; + + @ApiPropertyOptional({ + description: 'Organization address details', + example: '123 Main St, New York, NY', + nullable: true, + }) + address: string; +} + +export class GetCurrentOrganizationResponseDto { + @ApiProperty({ + description: 'Internal numeric ID of the organization', + example: 1, + }) + id: number; + + @ApiProperty({ + description: 'Unique string identifier for the organization', + example: 'org_123456', + }) + organizationId: string; + + @ApiProperty({ + description: 'Date when the organization was initialized', + example: '2024-01-01T00:00:00.000Z', + }) + initializedAt: Date; + + @ApiProperty({ + description: 'Date when the organization was seeded', + example: '2024-01-01T01:00:00.000Z', + }) + seededAt: Date; + + @ApiProperty({ + description: 'Date when the organization was built', + example: '2024-01-01T02:00:00.000Z', + }) + builtAt: Date; + + @ApiPropertyOptional({ + description: 'Database batch identifier, if any', + example: 'batch_001', + nullable: true, + }) + databaseBatch: null | string; + + @ApiProperty({ + description: 'Organization metadata', + type: () => [OrganizationMetadataResponseDto], + }) + metadata: OrganizationMetadataResponseDto; + + @ApiProperty({ + description: 'Whether the organization is ready', + example: true, + }) + isReady: boolean; + + @ApiProperty({ + description: 'Whether a build process is currently running', + example: false, + }) + isBuildRunning: boolean; + + @ApiProperty({ + description: 'Whether an upgrade process is currently running', + example: false, + }) + isUpgradeRunning: boolean; +} diff --git a/packages/webapp/src/hooks/query/organization.tsx b/packages/webapp/src/hooks/query/organization.tsx index 6f0baac72..8ded53703 100644 --- a/packages/webapp/src/hooks/query/organization.tsx +++ b/packages/webapp/src/hooks/query/organization.tsx @@ -43,7 +43,7 @@ export function useCurrentOrganization(props) { [t.ORGANIZATION_CURRENT], { method: 'get', url: OrganizationRoute.Current }, { - select: (res) => res.data.organization, + select: (res) => res.data, defaultData: {}, onSuccess: (data) => { const organization = omit(data, ['subscriptions']);