From f87bd341e9e26487ac11a7e41785b40bca2a686c Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 3 Jun 2025 21:42:09 +0200 Subject: [PATCH] refactor(nestjs): banking modules --- packages/server/src/modules/App/App.module.ts | 4 + .../BankingCategorize.application.ts | 92 +++++++++++++++++++ .../BankingCategorize.controller.ts | 39 ++++++++ .../BankingCategorize.module.ts | 24 ++++- ...action.ts => CategorizeBankTransaction.ts} | 7 +- .../CreateUncategorizedTransaction.service.ts | 4 +- ...=> UncategorizeBankTransaction.service.ts} | 2 +- ...categorizeBankTransactionsBulk.service.ts} | 9 +- .../dtos/CategorizeBankTransaction.dto.ts | 55 +++++++++++ .../CreateUncategorizedBankTransaction.dto.ts | 36 ++++++++ .../BankingMatching.controller.ts | 19 ++-- .../GetMatchedTransactionsByCashflow.ts | 1 + .../GetMatchedTransactionsByExpenses.ts | 19 +++- ...etMatchedTransactionsByInvoices.service.ts | 3 +- ...hedTransactionsByManualJournals.service.ts | 3 + .../queries/GetMatchedTransactionsByType.ts | 2 +- .../BankingTransactions.module.ts | 7 +- .../BankingTransactionsApplication.service.ts | 3 +- .../CommandCasflowValidator.service.ts | 2 +- .../commands/CreateBankTransaction.service.ts | 11 +-- .../dtos/CreateBankTransaction.dto.ts | 37 ++++++-- .../GetBankAccountTransactions.service.ts | 4 +- .../queries/GetUncategorizedTransactions.ts | 41 +++------ .../CashflowTransactionSubscriber.ts | 7 +- .../server/src/modules/Bills/models/Bill.ts | 24 ++--- .../DynamicListFilterRoles.service.ts | 4 +- .../modules/Expenses/models/Expense.model.ts | 30 +++--- .../ManualJournals/models/ManualJournal.ts | 24 ++--- .../SaleInvoices/models/SaleInvoice.ts | 24 ++--- .../src/modules/Tenancy/Tenancy.module.ts | 5 + .../Tenancy/TenancyInitializeModels.guard.ts | 54 +++++++++++ .../Tenancy/TenantModelsInitialize.module.ts | 54 +++++++++++ .../src/hooks/query/cashflowAccounts.tsx | 4 +- 33 files changed, 516 insertions(+), 138 deletions(-) create mode 100644 packages/server/src/modules/BankingCategorize/BankingCategorize.application.ts rename packages/server/src/modules/BankingCategorize/commands/{CategorizeCashflowTransaction.ts => CategorizeBankTransaction.ts} (96%) rename packages/server/src/modules/BankingCategorize/commands/{UncategorizeCashflowTransaction.service.ts => UncategorizeBankTransaction.service.ts} (98%) rename packages/server/src/modules/BankingCategorize/commands/{UncategorizeCashflowTransactionsBulk.service.ts => UncategorizeBankTransactionsBulk.service.ts} (76%) create mode 100644 packages/server/src/modules/BankingCategorize/dtos/CategorizeBankTransaction.dto.ts create mode 100644 packages/server/src/modules/BankingCategorize/dtos/CreateUncategorizedBankTransaction.dto.ts create mode 100644 packages/server/src/modules/Tenancy/TenancyInitializeModels.guard.ts create mode 100644 packages/server/src/modules/Tenancy/TenantModelsInitialize.module.ts diff --git a/packages/server/src/modules/App/App.module.ts b/packages/server/src/modules/App/App.module.ts index 8fffeab7f..0d9884a64 100644 --- a/packages/server/src/modules/App/App.module.ts +++ b/packages/server/src/modules/App/App.module.ts @@ -90,6 +90,8 @@ import { MiscellaneousModule } from '../Miscellaneous/Miscellaneous.module'; import { UsersModule } from '../UsersModule/Users.module'; import { ContactsModule } from '../Contacts/Contacts.module'; import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module'; +import { BankingCategorizeModule } from '../BankingCategorize/BankingCategorize.module'; +import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module'; @Module({ imports: [ @@ -151,6 +153,7 @@ import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module'; ScheduleModule.forRoot(), TenancyDatabaseModule, TenancyModelsModule, + TenantModelsInitializeModule, AuthModule, TenancyModule, ChromiumlyTenancyModule, @@ -188,6 +191,7 @@ import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module'; BankingTransactionsModule, BankingMatchingModule, BankingPlaidModule, + BankingCategorizeModule, TransactionsLockingModule, SettingsModule, FeaturesModule, diff --git a/packages/server/src/modules/BankingCategorize/BankingCategorize.application.ts b/packages/server/src/modules/BankingCategorize/BankingCategorize.application.ts new file mode 100644 index 000000000..6725b2578 --- /dev/null +++ b/packages/server/src/modules/BankingCategorize/BankingCategorize.application.ts @@ -0,0 +1,92 @@ +import { Knex } from 'knex'; +import { CategorizeBankTransaction } from './commands/CategorizeBankTransaction'; +import { UncategorizeBankTransactionService } from './commands/UncategorizeBankTransaction.service'; +import { UncategorizeBankTransactionsBulk } from './commands/UncategorizeBankTransactionsBulk.service'; +import { UncategorizedBankTransactionDto } from './dtos/CreateUncategorizedBankTransaction.dto'; +import { CategorizeBankTransactionDto } from './dtos/CategorizeBankTransaction.dto'; +import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense'; +import { CreateUncategorizedTransactionService } from './commands/CreateUncategorizedTransaction.service'; +import { ICategorizeCashflowTransactioDTO } from './types/BankingCategorize.types'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BankingCategorizeApplication { + constructor( + private readonly categorizeBankTransaction: CategorizeBankTransaction, + private readonly uncategorizeBankTransaction: UncategorizeBankTransactionService, + private readonly uncategorizeBankTransactionsBulk: UncategorizeBankTransactionsBulk, + private readonly categorizeTransactionAsExpense: CategorizeTransactionAsExpense, + private readonly createUncategorizedTransaction: CreateUncategorizedTransactionService, + ) {} + + /** + * Categorize a bank transaction with the given ID and categorization data. + * @param {number | Array} uncategorizedTransactionId - The ID(s) of the uncategorized transaction(s) to categorize. + * @param {CategorizeBankTransactionDto} categorizeDTO - Data for categorization. + * @returns {Promise} The result of the categorization operation. + */ + public categorizeTransaction( + uncategorizedTransactionId: number | Array, + categorizeDTO: CategorizeBankTransactionDto, + ) { + return this.categorizeBankTransaction.categorize( + uncategorizedTransactionId, + categorizeDTO, + ); + } + + /** + * Uncategorize a bank transaction with the given ID. + * @param {number} uncategorizedTransactionId - The ID of the transaction to uncategorize. + * @returns {Promise>} Array of affected transaction IDs. + */ + public uncategorizeTransaction( + uncategorizedTransactionId: number, + ): Promise> { + return this.uncategorizeBankTransaction.uncategorize( + uncategorizedTransactionId, + ); + } + + /** + * Uncategorize multiple bank transactions in bulk. + * @param {number | Array} uncategorizedTransactionIds - The ID(s) of the transaction(s) to uncategorize. + * @returns {Promise} + */ + public uncategorizeTransactionsBulk( + uncategorizedTransactionIds: number | Array, + ) { + return this.uncategorizeBankTransactionsBulk.uncategorizeBulk( + uncategorizedTransactionIds, + ); + } + + /** + * Categorize a transaction as an expense. + * @param {number} cashflowTransactionId - The ID of the cashflow transaction to categorize. + * @param {ICategorizeCashflowTransactioDTO} transactionDTO - Data for categorization. + * @returns {Promise} The result of the categorization operation. + */ + public categorizeTransactionAsExpenseType( + cashflowTransactionId: number, + transactionDTO: ICategorizeCashflowTransactioDTO, + ) { + return this.categorizeTransactionAsExpense.categorize( + cashflowTransactionId, + transactionDTO, + ); + } + + /** + * Create a new uncategorized bank transaction. + * @param {UncategorizedBankTransactionDto} createDTO - Data for creating the uncategorized transaction. + * @param {Knex.Transaction} [trx] - Optional Knex transaction. + * @returns {Promise} The created uncategorized transaction. + */ + public createUncategorizedBankTransaction( + createDTO: UncategorizedBankTransactionDto, + trx?: Knex.Transaction, + ) { + return this.createUncategorizedTransaction.create(createDTO, trx); + } +} diff --git a/packages/server/src/modules/BankingCategorize/BankingCategorize.controller.ts b/packages/server/src/modules/BankingCategorize/BankingCategorize.controller.ts index e69de29bb..995de1923 100644 --- a/packages/server/src/modules/BankingCategorize/BankingCategorize.controller.ts +++ b/packages/server/src/modules/BankingCategorize/BankingCategorize.controller.ts @@ -0,0 +1,39 @@ +import { Body, Controller, Delete, Param, Post, Query } from '@nestjs/common'; +import { castArray, omit } from 'lodash'; +import { BankingCategorizeApplication } from './BankingCategorize.application'; +import { CategorizeBankTransactionRouteDto } from './dtos/CategorizeBankTransaction.dto'; + +@Controller('banking/categorize') +export class BankingCategorizeController { + constructor( + private readonly bankingCategorizeApplication: BankingCategorizeApplication, + ) {} + + @Post() + public categorizeTransaction( + @Body() body: CategorizeBankTransactionRouteDto, + ) { + return this.bankingCategorizeApplication.categorizeTransaction( + castArray(body.uncategorizedTransactionIds), + omit(body, 'uncategorizedTransactionIds'), + ); + } + + @Delete('/bulk') + public uncategorizeTransactionsBulk( + @Query() uncategorizedTransactionIds: number[] | number, + ) { + return this.bankingCategorizeApplication.uncategorizeTransactionsBulk( + castArray(uncategorizedTransactionIds), + ); + } + + @Delete('/:id') + public uncategorizeTransaction( + @Param('id') uncategorizedTransactionId: number, + ) { + return this.bankingCategorizeApplication.uncategorizeTransaction( + Number(uncategorizedTransactionId), + ); + } +} diff --git a/packages/server/src/modules/BankingCategorize/BankingCategorize.module.ts b/packages/server/src/modules/BankingCategorize/BankingCategorize.module.ts index 42c4cbf12..e0b1026e5 100644 --- a/packages/server/src/modules/BankingCategorize/BankingCategorize.module.ts +++ b/packages/server/src/modules/BankingCategorize/BankingCategorize.module.ts @@ -1,20 +1,38 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { CreateUncategorizedTransactionService } from './commands/CreateUncategorizedTransaction.service'; import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense'; import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module'; import { ExpensesModule } from '../Expenses/Expenses.module'; import { UncategorizedTransactionsImportable } from './commands/UncategorizedTransactionsImportable'; +import { BankingCategorizeController } from './BankingCategorize.controller'; +import { BankingCategorizeApplication } from './BankingCategorize.application'; +import { CategorizeBankTransaction } from './commands/CategorizeBankTransaction'; +import { UncategorizeBankTransactionService } from './commands/UncategorizeBankTransaction.service'; +import { UncategorizeBankTransactionsBulk } from './commands/UncategorizeBankTransactionsBulk.service'; @Module({ - imports: [BankingTransactionsModule, ExpensesModule], + imports: [ + BankingTransactionsModule, + ExpensesModule, + forwardRef(() => BankingTransactionsModule), + ], providers: [ CreateUncategorizedTransactionService, CategorizeTransactionAsExpense, - UncategorizedTransactionsImportable + UncategorizedTransactionsImportable, + BankingCategorizeApplication, + CategorizeBankTransaction, + UncategorizeBankTransactionService, + UncategorizeBankTransactionsBulk, ], exports: [ CreateUncategorizedTransactionService, CategorizeTransactionAsExpense, + BankingCategorizeApplication, + CategorizeBankTransaction, + UncategorizeBankTransactionService, + UncategorizeBankTransactionsBulk, ], + controllers: [BankingCategorizeController], }) export class BankingCategorizeModule {} diff --git a/packages/server/src/modules/BankingCategorize/commands/CategorizeCashflowTransaction.ts b/packages/server/src/modules/BankingCategorize/commands/CategorizeBankTransaction.ts similarity index 96% rename from packages/server/src/modules/BankingCategorize/commands/CategorizeCashflowTransaction.ts rename to packages/server/src/modules/BankingCategorize/commands/CategorizeBankTransaction.ts index c4a4c1513..4002aab9e 100644 --- a/packages/server/src/modules/BankingCategorize/commands/CategorizeCashflowTransaction.ts +++ b/packages/server/src/modules/BankingCategorize/commands/CategorizeBankTransaction.ts @@ -5,7 +5,6 @@ import { Knex } from 'knex'; import { ICashflowTransactionCategorizedPayload, ICashflowTransactionUncategorizingPayload, - ICategorizeCashflowTransactioDTO, } from '../types/BankingCategorize.types'; import { transformCategorizeTransToCashflow, @@ -17,9 +16,10 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction'; import { events } from '@/common/events/events'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { CategorizeBankTransactionDto } from '../dtos/CategorizeBankTransaction.dto'; @Injectable() -export class CategorizeCashflowTransaction { +export class CategorizeBankTransaction { constructor( private readonly eventPublisher: EventEmitter2, private readonly uow: UnitOfWork, @@ -38,7 +38,7 @@ export class CategorizeCashflowTransaction { */ public async categorize( uncategorizedTransactionId: number | Array, - categorizeDTO: ICategorizeCashflowTransactioDTO, + categorizeDTO: CategorizeBankTransactionDto, ) { const uncategorizedTransactionIds = castArray(uncategorizedTransactionId); @@ -68,7 +68,6 @@ export class CategorizeCashflowTransaction { await this.eventPublisher.emitAsync( events.cashflow.onTransactionCategorizing, { - // tenantId, oldUncategorizedTransactions, trx, } as ICashflowTransactionUncategorizingPayload, diff --git a/packages/server/src/modules/BankingCategorize/commands/CreateUncategorizedTransaction.service.ts b/packages/server/src/modules/BankingCategorize/commands/CreateUncategorizedTransaction.service.ts index e4fc61156..2d41661e9 100644 --- a/packages/server/src/modules/BankingCategorize/commands/CreateUncategorizedTransaction.service.ts +++ b/packages/server/src/modules/BankingCategorize/commands/CreateUncategorizedTransaction.service.ts @@ -1,6 +1,5 @@ import { Knex } from 'knex'; import { - CreateUncategorizedTransactionDTO, IUncategorizedTransactionCreatedEventPayload, IUncategorizedTransactionCreatingEventPayload, } from '../types/BankingCategorize.types'; @@ -10,6 +9,7 @@ import { UncategorizedBankTransaction } from '../../BankingTransactions/models/U import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { events } from '@/common/events/events'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { UncategorizedBankTransactionDto } from '../dtos/CreateUncategorizedBankTransaction.dto'; @Injectable() export class CreateUncategorizedTransactionService { @@ -30,7 +30,7 @@ export class CreateUncategorizedTransactionService { * @returns {Promise} */ public create( - createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO, + createUncategorizedTransactionDTO: UncategorizedBankTransactionDto, trx?: Knex.Transaction, ) { return this.uow.withTransaction(async (trx: Knex.Transaction) => { diff --git a/packages/server/src/modules/BankingCategorize/commands/UncategorizeCashflowTransaction.service.ts b/packages/server/src/modules/BankingCategorize/commands/UncategorizeBankTransaction.service.ts similarity index 98% rename from packages/server/src/modules/BankingCategorize/commands/UncategorizeCashflowTransaction.service.ts rename to packages/server/src/modules/BankingCategorize/commands/UncategorizeBankTransaction.service.ts index 73e2cf343..31a77c54a 100644 --- a/packages/server/src/modules/BankingCategorize/commands/UncategorizeCashflowTransaction.service.ts +++ b/packages/server/src/modules/BankingCategorize/commands/UncategorizeBankTransaction.service.ts @@ -12,7 +12,7 @@ import { UncategorizedBankTransaction } from '../../BankingTransactions/models/U import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; @Injectable() -export class UncategorizeCashflowTransactionService { +export class UncategorizeBankTransactionService { constructor( private readonly eventPublisher: EventEmitter2, private readonly uow: UnitOfWork, diff --git a/packages/server/src/modules/BankingCategorize/commands/UncategorizeCashflowTransactionsBulk.service.ts b/packages/server/src/modules/BankingCategorize/commands/UncategorizeBankTransactionsBulk.service.ts similarity index 76% rename from packages/server/src/modules/BankingCategorize/commands/UncategorizeCashflowTransactionsBulk.service.ts rename to packages/server/src/modules/BankingCategorize/commands/UncategorizeBankTransactionsBulk.service.ts index e92011eed..dbc4cf2eb 100644 --- a/packages/server/src/modules/BankingCategorize/commands/UncategorizeCashflowTransactionsBulk.service.ts +++ b/packages/server/src/modules/BankingCategorize/commands/UncategorizeBankTransactionsBulk.service.ts @@ -1,18 +1,17 @@ import { castArray } from 'lodash'; import { PromisePool } from '@supercharge/promise-pool'; import { Injectable } from '@nestjs/common'; -import { UncategorizeCashflowTransactionService } from './UncategorizeCashflowTransaction.service'; +import { UncategorizeBankTransactionService } from './UncategorizeBankTransaction.service'; @Injectable() -export class UncategorizeCashflowTransactionsBulk { +export class UncategorizeBankTransactionsBulk { constructor( - private readonly uncategorizeTransactionService: UncategorizeCashflowTransactionService + private readonly uncategorizeTransactionService: UncategorizeBankTransactionService ) {} /** * Uncategorize the given bank transactions in bulk. - * @param {number} tenantId - * @param {number} uncategorizedTransactionId + * @param {number | Array} uncategorizedTransactionId */ public async uncategorizeBulk( uncategorizedTransactionId: number | Array diff --git a/packages/server/src/modules/BankingCategorize/dtos/CategorizeBankTransaction.dto.ts b/packages/server/src/modules/BankingCategorize/dtos/CategorizeBankTransaction.dto.ts new file mode 100644 index 000000000..698c1f599 --- /dev/null +++ b/packages/server/src/modules/BankingCategorize/dtos/CategorizeBankTransaction.dto.ts @@ -0,0 +1,55 @@ +import { ToNumber } from '@/common/decorators/Validators'; +import { + IsArray, + IsDateString, + IsInt, + IsNotEmpty, + IsNumber, + IsOptional, + IsString, +} from 'class-validator'; + +export class CategorizeBankTransactionDto { + @IsDateString() + @IsNotEmpty() + date: Date; + + @IsInt() + @ToNumber() + @IsNotEmpty() + creditAccountId: number; + + @IsString() + @IsOptional() + referenceNo: string; + + @IsString() + @IsOptional() + transactionNumber: string; + + @IsString() + @IsNotEmpty() + transactionType: string; + + @IsNumber() + @ToNumber() + @IsOptional() + exchangeRate: number = 1; + + @IsString() + @IsOptional() + currencyCode: string; + + @IsString() + @IsOptional() + description: string; + + @IsNumber() + @IsOptional() + branchId: number; +} + +export class CategorizeBankTransactionRouteDto extends CategorizeBankTransactionDto { + @IsArray() + uncategorizedTransactionIds: Array; +} diff --git a/packages/server/src/modules/BankingCategorize/dtos/CreateUncategorizedBankTransaction.dto.ts b/packages/server/src/modules/BankingCategorize/dtos/CreateUncategorizedBankTransaction.dto.ts new file mode 100644 index 000000000..f7a36ad98 --- /dev/null +++ b/packages/server/src/modules/BankingCategorize/dtos/CreateUncategorizedBankTransaction.dto.ts @@ -0,0 +1,36 @@ +import { IsBoolean, IsDateString, IsNumber, IsString } from 'class-validator'; + +export class UncategorizedBankTransactionDto { + @IsDateString() + date: Date | string; + + @IsNumber() + accountId: number; + + @IsNumber() + amount: number; + + @IsString() + currencyCode: string; + + @IsString() + payee?: string; + + @IsString() + description?: string; + + @IsString() + referenceNo?: string | null; + + @IsString() + plaidTransactionId?: string | null; + + @IsBoolean() + pending?: boolean; + + @IsString() + pendingPlaidTransactionId?: string | null; + + @IsString() + batch?: string; +} diff --git a/packages/server/src/modules/BankingMatching/BankingMatching.controller.ts b/packages/server/src/modules/BankingMatching/BankingMatching.controller.ts index bf95a2f31..4f9a5ce33 100644 --- a/packages/server/src/modules/BankingMatching/BankingMatching.controller.ts +++ b/packages/server/src/modules/BankingMatching/BankingMatching.controller.ts @@ -1,47 +1,48 @@ import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'; import { BankingMatchingApplication } from './BankingMatchingApplication'; -import { GetMatchedTransactionsFilter, IMatchTransactionDTO } from './types'; +import { GetMatchedTransactionsFilter } from './types'; import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto'; @Controller('banking/matching') @ApiTags('banking-transactions-matching') export class BankingMatchingController { constructor( - private readonly bankingMatchingApplication: BankingMatchingApplication + private readonly bankingMatchingApplication: BankingMatchingApplication, ) {} @Get('matched') @ApiOperation({ summary: 'Retrieves the matched transactions.' }) async getMatchedTransactions( @Query('uncategorizedTransactionIds') uncategorizedTransactionIds: number[], - @Query() filter: GetMatchedTransactionsFilter + @Query() filter: GetMatchedTransactionsFilter, ) { return this.bankingMatchingApplication.getMatchedTransactions( uncategorizedTransactionIds, - filter + filter, ); } @Post('/match/:uncategorizedTransactionId') @ApiOperation({ summary: 'Match the given uncategorized transaction.' }) async matchTransaction( - @Param('uncategorizedTransactionId') uncategorizedTransactionId: number | number[], - @Body() matchedTransactions: MatchBankTransactionDto + @Param('uncategorizedTransactionId') + uncategorizedTransactionId: number | number[], + @Body() matchedTransactions: MatchBankTransactionDto, ) { return this.bankingMatchingApplication.matchTransaction( uncategorizedTransactionId, - matchedTransactions + matchedTransactions, ); } @Post('/unmatch/:uncategorizedTransactionId') @ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' }) async unmatchMatchedTransaction( - @Param('uncategorizedTransactionId') uncategorizedTransactionId: number + @Param('uncategorizedTransactionId') uncategorizedTransactionId: number, ) { return this.bankingMatchingApplication.unmatchMatchedTransaction( - uncategorizedTransactionId + uncategorizedTransactionId, ); } } diff --git a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByCashflow.ts b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByCashflow.ts index 0419a33ff..60c4da14a 100644 --- a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByCashflow.ts +++ b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByCashflow.ts @@ -1,3 +1,4 @@ +import { Knex } from 'knex'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionCashflowTransformer } from './GetMatchedTransactionCashflowTransformer'; import { GetMatchedTransactionsFilter } from '../types'; diff --git a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByExpenses.ts b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByExpenses.ts index 3650e547b..c963969ec 100644 --- a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByExpenses.ts +++ b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByExpenses.ts @@ -1,10 +1,13 @@ import { Inject, Injectable } from '@nestjs/common'; -import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from '../types'; +import { GetMatchedTransactionsFilter, MatchedTransactionPOJO, MatchedTransactionsPOJO } from '../types'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionExpensesTransformer } from './GetMatchedTransactionExpensesTransformer'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { Expense } from '@/modules/Expenses/models/Expense.model'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { Knex } from 'knex'; +import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants'; +import { initialize } from 'objection'; @Injectable() export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByType { @@ -13,17 +16,26 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy @Inject(Expense.name) protected readonly expenseModel: TenantModelProxy, + + @Inject(TENANCY_DB_CONNECTION) + private readonly tenantDb: () => Knex, + + @Inject('TENANT_MODELS_INIT') + private readonly tenantModelsInit: () => Promise, + ) { super(); } /** * Retrieves the matched transactions of expenses. - * @param {number} tenantId * @param {GetMatchedTransactionsFilter} filter * @returns */ - async getMatchedTransactions(filter: GetMatchedTransactionsFilter) { + async getMatchedTransactions( + filter: GetMatchedTransactionsFilter, + ): Promise { + // await this.tenantModelsInit(); // Retrieve the expense matches. const expenses = await this.expenseModel() .query() @@ -49,6 +61,7 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy } query.orderBy('paymentDate', 'DESC'); }); + return this.transformer.transform( expenses, new GetMatchedTransactionExpensesTransformer(), diff --git a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByInvoices.service.ts b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByInvoices.service.ts index 3fc736aa1..6f983e70a 100644 --- a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByInvoices.service.ts +++ b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByInvoices.service.ts @@ -1,3 +1,4 @@ +import { Inject, Injectable } from '@nestjs/common'; import { Knex } from 'knex'; import { first } from 'lodash'; import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer'; @@ -9,7 +10,6 @@ import { } from '../types'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { CreatePaymentReceivedService } from '@/modules/PaymentReceived/commands/CreatePaymentReceived.serivce'; -import { Inject, Injectable } from '@nestjs/common'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction'; @@ -86,7 +86,6 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy /** * Creates the common matched transaction. - * @param {number} tenantId * @param {Array} uncategorizedTransactionIds * @param {IMatchTransactionDTO} matchTransactionDTO * @param {Knex.Transaction} trx diff --git a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByManualJournals.service.ts b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByManualJournals.service.ts index 85be269e9..f1c845f4e 100644 --- a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByManualJournals.service.ts +++ b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByManualJournals.service.ts @@ -1,10 +1,13 @@ +import { Knex } from 'knex'; import { Inject, Injectable } from '@nestjs/common'; +import { initialize } from 'objection'; import { GetMatchedTransactionManualJournalsTransformer } from './GetMatchedTransactionManualJournalsTransformer'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionsFilter } from '../types'; import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants'; @Injectable() export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactionsByType { diff --git a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByType.ts b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByType.ts index a256339df..7a4e29356 100644 --- a/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByType.ts +++ b/packages/server/src/modules/BankingMatching/queries/GetMatchedTransactionsByType.ts @@ -12,7 +12,7 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; export abstract class GetMatchedTransactionsByType { @Inject(MatchedBankTransaction.name) - private readonly matchedBankTransactionModel: TenantModelProxy< + matchedBankTransactionModel: TenantModelProxy< typeof MatchedBankTransaction >; diff --git a/packages/server/src/modules/BankingTransactions/BankingTransactions.module.ts b/packages/server/src/modules/BankingTransactions/BankingTransactions.module.ts index b66027e9c..ee2969786 100644 --- a/packages/server/src/modules/BankingTransactions/BankingTransactions.module.ts +++ b/packages/server/src/modules/BankingTransactions/BankingTransactions.module.ts @@ -76,6 +76,11 @@ const models = [ GetPendingBankAccountTransactions, GetAutofillCategorizeTransactionService, ], - exports: [...models, RemovePendingUncategorizedTransaction], + exports: [ + ...models, + RemovePendingUncategorizedTransaction, + CommandBankTransactionValidator, + CreateBankTransactionService + ], }) export class BankingTransactionsModule {} diff --git a/packages/server/src/modules/BankingTransactions/BankingTransactionsApplication.service.ts b/packages/server/src/modules/BankingTransactions/BankingTransactionsApplication.service.ts index 121eba609..7c07f616e 100644 --- a/packages/server/src/modules/BankingTransactions/BankingTransactionsApplication.service.ts +++ b/packages/server/src/modules/BankingTransactions/BankingTransactionsApplication.service.ts @@ -15,6 +15,7 @@ import { GetUncategorizedTransactionsQueryDto } from './dtos/GetUncategorizedTra import { GetPendingBankAccountTransactions } from './queries/GetPendingBankAccountTransaction.service'; import { GetPendingTransactionsQueryDto } from './dtos/GetPendingTransactionsQuery.dto'; import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction/GetAutofillCategorizeTransaction.service'; +import { GetBankTransactionsQueryDto } from './dtos/GetBankTranasctionsQuery.dto'; @Injectable() export class BankingTransactionsApplication { @@ -54,7 +55,7 @@ export class BankingTransactionsApplication { * Retrieves the bank transactions of the given bank id. * @param {ICashflowAccountTransactionsQuery} query */ - public getBankAccountTransactions(query: ICashflowAccountTransactionsQuery) { + public getBankAccountTransactions(query: GetBankTransactionsQueryDto) { return this.getBankAccountTransactionsService.bankAccountTransactions( query, ); diff --git a/packages/server/src/modules/BankingTransactions/commands/CommandCasflowValidator.service.ts b/packages/server/src/modules/BankingTransactions/commands/CommandCasflowValidator.service.ts index e7d6e2e57..5b1c86e4d 100644 --- a/packages/server/src/modules/BankingTransactions/commands/CommandCasflowValidator.service.ts +++ b/packages/server/src/modules/BankingTransactions/commands/CommandCasflowValidator.service.ts @@ -1,3 +1,4 @@ +import { Injectable } from '@nestjs/common'; import { includes, camelCase, upperFirst, sumBy } from 'lodash'; import { getCashflowTransactionType } from '../utils'; import { @@ -6,7 +7,6 @@ import { ERRORS, } from '../constants'; import { Account } from '@/modules/Accounts/models/Account.model'; -import { Injectable } from '@nestjs/common'; import { ServiceError } from '@/modules/Items/ServiceError'; import { BankTransaction } from '../models/BankTransaction'; import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction'; diff --git a/packages/server/src/modules/BankingTransactions/commands/CreateBankTransaction.service.ts b/packages/server/src/modules/BankingTransactions/commands/CreateBankTransaction.service.ts index f1244a311..6bfab8afe 100644 --- a/packages/server/src/modules/BankingTransactions/commands/CreateBankTransaction.service.ts +++ b/packages/server/src/modules/BankingTransactions/commands/CreateBankTransaction.service.ts @@ -1,7 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { pick } from 'lodash'; import { Knex } from 'knex'; -import * as R from 'ramda'; import * as composeAsync from 'async/compose'; import { CASHFLOW_TRANSACTION_TYPE } from '../constants'; import { transformCashflowTransactionType } from '../utils'; @@ -14,12 +13,12 @@ import { events } from '@/common/events/events'; import { Account } from '@/modules/Accounts/models/Account.model'; import { BankTransaction } from '../models/BankTransaction'; import { - ICashflowNewCommandDTO, ICommandCashflowCreatedPayload, ICommandCashflowCreatingPayload, } from '../types/BankingTransactions.types'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto'; +import { formatDateFields } from '@/utils/format-date-fields'; @Injectable() export class CreateBankTransactionService { @@ -42,7 +41,7 @@ export class CreateBankTransactionService { * @param {ICashflowNewCommandDTO} newCashflowTransactionDTO */ public authorize = async ( - newCashflowTransactionDTO: ICashflowNewCommandDTO, + newCashflowTransactionDTO: CreateBankTransactionDto, creditAccount: Account, ) => { const transactionType = transformCashflowTransactionType( @@ -60,7 +59,7 @@ export class CreateBankTransactionService { /** * Transformes owner contribution DTO to cashflow transaction. - * @param {ICashflowNewCommandDTO} newCashflowTransactionDTO - New transaction DTO. + * @param {CreateBankTransactionDto} newCashflowTransactionDTO - New transaction DTO. * @returns {ICashflowTransactionInput} - Cashflow transaction object. */ private transformCashflowTransactionDTO = async ( @@ -91,7 +90,7 @@ export class CreateBankTransactionService { const initialDTO = { amount, - ...fromDTO, + ...formatDateFields(fromDTO, ['date']), transactionNumber, currencyCode: cashflowAccount.currencyCode, exchangeRate: fromDTO?.exchangeRate || 1, @@ -117,7 +116,7 @@ export class CreateBankTransactionService { * @returns {Promise} */ public newCashflowTransaction = async ( - newTransactionDTO: ICashflowNewCommandDTO, + newTransactionDTO: CreateBankTransactionDto, userId?: number, ): Promise => { // Retrieves the cashflow account or throw not found error. diff --git a/packages/server/src/modules/BankingTransactions/dtos/CreateBankTransaction.dto.ts b/packages/server/src/modules/BankingTransactions/dtos/CreateBankTransaction.dto.ts index 5195ca91c..b549eba22 100644 --- a/packages/server/src/modules/BankingTransactions/dtos/CreateBankTransaction.dto.ts +++ b/packages/server/src/modules/BankingTransactions/dtos/CreateBankTransaction.dto.ts @@ -1,47 +1,64 @@ +import { ToNumber } from '@/common/decorators/Validators'; import { IsBoolean, - IsDate, + IsDateString, + IsInt, + IsNotEmpty, IsNumber, IsOptional, IsString, } from 'class-validator'; export class CreateBankTransactionDto { - @IsDate() + @IsDateString() + @IsNotEmpty() date: Date; @IsString() - transactionNumber: string; + @IsOptional() + transactionNumber?: string; @IsString() - referenceNo: string; + @IsOptional() + referenceNo?: string; + @IsNotEmpty() @IsString() transactionType: string; @IsString() description: string; + @IsNotEmpty() + @ToNumber() @IsNumber() amount: number; + @ToNumber() @IsNumber() - exchangeRate: number; + exchangeRate: number = 1; @IsString() + @IsOptional() currencyCode: string; - @IsNumber() + @IsNotEmpty() + @ToNumber() + @IsInt() creditAccountId: number; - @IsNumber() + @IsNotEmpty() + @ToNumber() + @IsInt() cashflowAccountId: number; @IsBoolean() - publish: boolean; + @IsOptional() + publish: boolean = true; @IsOptional() - @IsNumber() + @ToNumber() + @IsInt() branchId?: number; @IsOptional() @@ -53,6 +70,6 @@ export class CreateBankTransactionDto { plaidAccountId?: string; @IsOptional() - @IsNumber() + @IsInt() uncategorizedTransactionId?: number; } diff --git a/packages/server/src/modules/BankingTransactions/queries/GetBankAccountTransactions/GetBankAccountTransactions.service.ts b/packages/server/src/modules/BankingTransactions/queries/GetBankAccountTransactions/GetBankAccountTransactions.service.ts index 6af6a4338..989ae1096 100644 --- a/packages/server/src/modules/BankingTransactions/queries/GetBankAccountTransactions/GetBankAccountTransactions.service.ts +++ b/packages/server/src/modules/BankingTransactions/queries/GetBankAccountTransactions/GetBankAccountTransactions.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { getBankAccountTransactionsDefaultQuery } from './_utils'; import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service'; import { GetBankAccountTransactions } from './GetBankAccountTransactions'; -import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types'; +import { GetBankTransactionsQueryDto } from '../../dtos/GetBankTranasctionsQuery.dto'; @Injectable() export class GetBankAccountTransactionsService { @@ -16,7 +16,7 @@ export class GetBankAccountTransactionsService { * @return {Promise} */ public async bankAccountTransactions( - query: ICashflowAccountTransactionsQuery, + query: GetBankTransactionsQueryDto, ) { const parsedQuery = { ...getBankAccountTransactionsDefaultQuery(), diff --git a/packages/server/src/modules/BankingTransactions/queries/GetUncategorizedTransactions.ts b/packages/server/src/modules/BankingTransactions/queries/GetUncategorizedTransactions.ts index 6bb5d0778..bec164ec1 100644 --- a/packages/server/src/modules/BankingTransactions/queries/GetUncategorizedTransactions.ts +++ b/packages/server/src/modules/BankingTransactions/queries/GetUncategorizedTransactions.ts @@ -1,35 +1,23 @@ import { Inject, Injectable } from '@nestjs/common'; -import { initialize } from 'objection'; -import { Knex } from 'knex'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction'; import { UncategorizedTransactionTransformer } from '../../BankingCategorize/commands/UncategorizedTransaction.transformer'; import { GetUncategorizedTransactionsQueryDto } from '../dtos/GetUncategorizedTransactionsQuery.dto'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; -import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants'; -import { Account } from '@/modules/Accounts/models/Account.model'; -import { RecognizedBankTransaction } from '@/modules/BankingTranasctionsRegonize/models/RecognizedBankTransaction'; -import { MatchedBankTransaction } from '@/modules/BankingMatching/models/MatchedBankTransaction'; @Injectable() export class GetUncategorizedTransactions { + /** + * @param {TransformerInjectable} transformer + * @param {UncategorizedBankTransaction.name} uncategorizedBankTransactionModel + */ constructor( private readonly transformer: TransformerInjectable, - @Inject(TENANCY_DB_CONNECTION) - private readonly tenantDb: () => Knex, - @Inject(UncategorizedBankTransaction.name) - private readonly uncategorizedBankTransactionModel: TenantModelProxy, - - @Inject(Account.name) - private readonly accountModel: TenantModelProxy, - - @Inject(RecognizedBankTransaction.name) - private readonly recognizedTransactionModel: TenantModelProxy, - - @Inject(MatchedBankTransaction.name) - private readonly matchedTransactionModel: TenantModelProxy, + private readonly uncategorizedBankTransactionModel: TenantModelProxy< + typeof UncategorizedBankTransaction + >, ) {} /** @@ -39,7 +27,7 @@ export class GetUncategorizedTransactions { */ public async getTransactions( accountId: number, - query: GetUncategorizedTransactionsQueryDto + query: GetUncategorizedTransactionsQueryDto, ) { // Parsed query with default values. const _query = { @@ -47,16 +35,9 @@ export class GetUncategorizedTransactions { pageSize: 20, ...query, }; - - await initialize(this.tenantDb(), [ - this.accountModel(), - this.uncategorizedBankTransactionModel(), - this.recognizedTransactionModel(), - this.matchedTransactionModel(), - ]); - const { results, pagination } = - await this.uncategorizedBankTransactionModel().query() + await this.uncategorizedBankTransactionModel() + .query() .onBuild((q) => { q.where('accountId', accountId); q.where('categorized', false); @@ -89,7 +70,7 @@ export class GetUncategorizedTransactions { const data = await this.transformer.transform( results, - new UncategorizedTransactionTransformer() + new UncategorizedTransactionTransformer(), ); return { data, diff --git a/packages/server/src/modules/BankingTransactions/subscribers/CashflowTransactionSubscriber.ts b/packages/server/src/modules/BankingTransactions/subscribers/CashflowTransactionSubscriber.ts index 5da6c8bb1..a7007db33 100644 --- a/packages/server/src/modules/BankingTransactions/subscribers/CashflowTransactionSubscriber.ts +++ b/packages/server/src/modules/BankingTransactions/subscribers/CashflowTransactionSubscriber.ts @@ -3,7 +3,10 @@ import { OnEvent } from '@nestjs/event-emitter'; import { BankTransactionAutoIncrement } from '../commands/BankTransactionAutoIncrement.service'; import { BankTransactionGLEntriesService } from '../commands/BankTransactionGLEntries'; import { events } from '@/common/events/events'; -import { ICommandCashflowCreatedPayload, ICommandCashflowDeletedPayload } from '../types/BankingTransactions.types'; +import { + ICommandCashflowCreatedPayload, + ICommandCashflowDeletedPayload, +} from '../types/BankingTransactions.types'; @Injectable() export class BankingTransactionGLEntriesSubscriber { @@ -56,5 +59,5 @@ export class BankingTransactionGLEntriesSubscriber { cashflowTransactionId, trx, ); - }; + } } diff --git a/packages/server/src/modules/Bills/models/Bill.ts b/packages/server/src/modules/Bills/models/Bill.ts index 9c665bf6f..523c5ad24 100644 --- a/packages/server/src/modules/Bills/models/Bill.ts +++ b/packages/server/src/modules/Bills/models/Bill.ts @@ -492,7 +492,7 @@ export class Bill extends TenantBaseModel { TaxRateTransaction, } = require('../../TaxRates/models/TaxRateTransaction.model'); const { Document } = require('../../ChromiumlyTenancy/models/Document'); - // const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); + const { MatchedBankTransaction } = require('../../BankingMatching/models/MatchedBankTransaction'); return { vendor: { @@ -590,17 +590,17 @@ export class Bill extends TenantBaseModel { /** * Bill may belongs to matched bank transaction. */ - // matchedBankTransaction: { - // relation: Model.HasManyRelation, - // modelClass: MatchedBankTransaction, - // join: { - // from: 'bills.id', - // to: 'matched_bank_transactions.referenceId', - // }, - // filter(query) { - // query.where('reference_type', 'Bill'); - // }, - // }, + matchedBankTransaction: { + relation: Model.HasManyRelation, + modelClass: MatchedBankTransaction, + join: { + from: 'bills.id', + to: 'matched_bank_transactions.referenceId', + }, + filter(query) { + query.where('reference_type', 'Bill'); + }, + }, }; } diff --git a/packages/server/src/modules/DynamicListing/DynamicListFilterRoles.service.ts b/packages/server/src/modules/DynamicListing/DynamicListFilterRoles.service.ts index 0042af3b6..688fc56e3 100644 --- a/packages/server/src/modules/DynamicListing/DynamicListFilterRoles.service.ts +++ b/packages/server/src/modules/DynamicListing/DynamicListFilterRoles.service.ts @@ -1,6 +1,6 @@ import * as R from 'ramda'; import { Injectable } from '@nestjs/common'; -import validator from 'is-my-json-valid'; +import * as validator from 'is-my-json-valid'; import { IFilterRole } from './DynamicFilter/DynamicFilter.types'; import { DynamicFilterAdvancedFilter } from './DynamicFilter/DynamicFilterAdvancedFilter'; import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor'; @@ -21,7 +21,7 @@ export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor { properties: { condition: { type: 'string' }, fieldKey: { type: 'string' }, - value: { type: 'string' }, + // value: { type: ['number', 'string'] }, }, }); const invalidFields = filterRoles.filter((filterRole) => { diff --git a/packages/server/src/modules/Expenses/models/Expense.model.ts b/packages/server/src/modules/Expenses/models/Expense.model.ts index 99305b96e..2ea6e560a 100644 --- a/packages/server/src/modules/Expenses/models/Expense.model.ts +++ b/packages/server/src/modules/Expenses/models/Expense.model.ts @@ -202,7 +202,7 @@ export class Expense extends TenantBaseModel { const { ExpenseCategory } = require('./ExpenseCategory.model'); const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Branch } = require('../../Branches/models/Branch.model'); - // const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); + const { MatchedBankTransaction } = require('../../BankingMatching/models/MatchedBankTransaction'); return { /** @@ -263,20 +263,20 @@ export class Expense extends TenantBaseModel { }, }, - // /** - // * Expense may belongs to matched bank transaction. - // */ - // matchedBankTransaction: { - // relation: Model.HasManyRelation, - // modelClass: MatchedBankTransaction, - // join: { - // from: 'expenses_transactions.id', - // to: 'matched_bank_transactions.referenceId', - // }, - // filter(query) { - // query.where('reference_type', 'Expense'); - // }, - // }, + /** + * Expense may belongs to matched bank transaction. + */ + matchedBankTransaction: { + relation: Model.HasManyRelation, + modelClass: MatchedBankTransaction, + join: { + from: 'expenses_transactions.id', + to: 'matched_bank_transactions.referenceId', + }, + filter(query) { + query.where('reference_type', 'Expense'); + }, + }, }; } diff --git a/packages/server/src/modules/ManualJournals/models/ManualJournal.ts b/packages/server/src/modules/ManualJournals/models/ManualJournal.ts index beaba77e9..0e8c0508e 100644 --- a/packages/server/src/modules/ManualJournals/models/ManualJournal.ts +++ b/packages/server/src/modules/ManualJournals/models/ManualJournal.ts @@ -123,7 +123,7 @@ export class ManualJournal extends TenantBaseModel { const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model'); const { ManualJournalEntry } = require('./ManualJournalEntry'); const { Document } = require('../../ChromiumlyTenancy/models/Document'); - // const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); + const { MatchedBankTransaction } = require('../../BankingMatching/models/MatchedBankTransaction'); return { entries: { @@ -171,17 +171,17 @@ export class ManualJournal extends TenantBaseModel { /** * Manual journal may belongs to matched bank transaction. */ - // matchedBankTransaction: { - // relation: Model.BelongsToOneRelation, - // modelClass: MatchedBankTransaction, - // join: { - // from: 'manual_journals.id', - // to: 'matched_bank_transactions.referenceId', - // }, - // filter(query) { - // query.where('reference_type', 'ManualJournal'); - // }, - // }, + matchedBankTransaction: { + relation: Model.BelongsToOneRelation, + modelClass: MatchedBankTransaction, + join: { + from: 'manual_journals.id', + to: 'matched_bank_transactions.referenceId', + }, + filter(query) { + query.where('reference_type', 'ManualJournal'); + }, + }, }; } diff --git a/packages/server/src/modules/SaleInvoices/models/SaleInvoice.ts b/packages/server/src/modules/SaleInvoices/models/SaleInvoice.ts index ad3bbdd63..c9fa1bd31 100644 --- a/packages/server/src/modules/SaleInvoices/models/SaleInvoice.ts +++ b/packages/server/src/modules/SaleInvoices/models/SaleInvoice.ts @@ -512,7 +512,7 @@ export class SaleInvoice extends TenantBaseModel{ TaxRateTransaction, } = require('../../TaxRates/models/TaxRateTransaction.model'); const { Document } = require('../../ChromiumlyTenancy/models/Document'); - // const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); + const { MatchedBankTransaction } = require('../../BankingMatching/models/MatchedBankTransaction'); const { TransactionPaymentServiceEntry, } = require('../../PaymentServices/models/TransactionPaymentServiceEntry.model'); @@ -667,17 +667,17 @@ export class SaleInvoice extends TenantBaseModel{ /** * Sale invocie may belongs to matched bank transaction. */ - // matchedBankTransaction: { - // relation: Model.HasManyRelation, - // modelClass: MatchedBankTransaction, - // join: { - // from: 'sales_invoices.id', - // to: 'matched_bank_transactions.referenceId', - // }, - // filter(query) { - // query.where('reference_type', 'SaleInvoice'); - // }, - // }, + matchedBankTransaction: { + relation: Model.HasManyRelation, + modelClass: MatchedBankTransaction, + join: { + from: 'sales_invoices.id', + to: 'matched_bank_transactions.referenceId', + }, + filter(query) { + query.where('reference_type', 'SaleInvoice'); + }, + }, /** * Sale invoice may belongs to payment methods entries. diff --git a/packages/server/src/modules/Tenancy/Tenancy.module.ts b/packages/server/src/modules/Tenancy/Tenancy.module.ts index 0fe737d7a..de45d4046 100644 --- a/packages/server/src/modules/Tenancy/Tenancy.module.ts +++ b/packages/server/src/modules/Tenancy/Tenancy.module.ts @@ -5,6 +5,7 @@ import { EnsureTenantIsSeededGuard } from "./EnsureTenantIsSeeded.guards"; import { APP_GUARD } from "@nestjs/core"; import { TenancyContext } from "./TenancyContext.service"; import { TenantController } from "./Tenant.controller"; +import { TenancyInitializeModelsGuard } from "./TenancyInitializeModels.guard"; @Module({ @@ -23,6 +24,10 @@ import { TenantController } from "./Tenant.controller"; { provide: APP_GUARD, useClass: EnsureTenantIsSeededGuard + }, + { + provide: APP_GUARD, + useClass: TenancyInitializeModelsGuard } ] }) diff --git a/packages/server/src/modules/Tenancy/TenancyInitializeModels.guard.ts b/packages/server/src/modules/Tenancy/TenancyInitializeModels.guard.ts new file mode 100644 index 000000000..0ebcdd1a2 --- /dev/null +++ b/packages/server/src/modules/Tenancy/TenancyInitializeModels.guard.ts @@ -0,0 +1,54 @@ +import { + CanActivate, + ExecutionContext, + Inject, + Injectable, + SetMetadata, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; +import { TENANT_MODELS_INIT } from './TenantModelsInitialize.module'; + +export const IGNORE_TENANT_MODELS_INITIALIZE = + 'IGNORE_TENANT_MODELS_INITIALIZE'; +export const IgnoreTenantModelsInitialize = () => + SetMetadata(IGNORE_TENANT_MODELS_INITIALIZE, true); + +@Injectable() +export class TenancyInitializeModelsGuard implements CanActivate { + constructor( + @Inject(TENANT_MODELS_INIT) + private readonly tenantModelsInit: () => Promise, + private reflector: Reflector, + ) {} + + /** + * Initialize tenant models if the route is decorated with TriggerTenantModelsInitialize. + * @param {ExecutionContext} context + * @returns {Promise} + */ + async canActivate(context: ExecutionContext): Promise { + const isPublic = this.reflector.getAllAndOverride( + IS_PUBLIC_ROUTE, + [context.getHandler(), context.getClass()], + ); + // Skip initialization for public routes + if (isPublic) { + return true; + } + const shouldIgnoreInitialization = + this.reflector.getAllAndOverride( + IGNORE_TENANT_MODELS_INITIALIZE, + [context.getHandler(), context.getClass()], + ); + // Initialize models unless the route is decorated with IgnoreTenantModelsInitialize + if (!shouldIgnoreInitialization) { + try { + await this.tenantModelsInit(); + } catch (error) { + console.error('Failed to initialize tenant models:', error); + } + } + return true; + } +} diff --git a/packages/server/src/modules/Tenancy/TenantModelsInitialize.module.ts b/packages/server/src/modules/Tenancy/TenantModelsInitialize.module.ts new file mode 100644 index 000000000..8d1c7c936 --- /dev/null +++ b/packages/server/src/modules/Tenancy/TenantModelsInitialize.module.ts @@ -0,0 +1,54 @@ +import { ContextIdFactory, ModuleRef } from '@nestjs/core'; +import { ClsModule } from 'nestjs-cls'; +import { Global, Module } from '@nestjs/common'; +import { Knex } from 'knex'; +import { initialize } from 'objection'; +import { TENANCY_DB_CONNECTION } from './TenancyDB/TenancyDB.constants'; + +const RegisteredModels = [ + 'SaleInvoice', + 'Bill', + 'Expense', + 'BankTransaction', + 'MatchedBankTransaction', + 'ManualJournalEntry', + 'Account', + 'UncategorizedBankTransaction', + 'RecognizedBankTransaction', +]; +export const TENANT_MODELS_INIT = 'TENANT_MODELS_INIT'; + +const provider = ClsModule.forFeatureAsync({ + provide: TENANT_MODELS_INIT, + inject: [TENANCY_DB_CONNECTION, ModuleRef], + useFactory: (tenantKnex: () => Knex, moduleRef: ModuleRef) => async () => { + const knexInstance = tenantKnex(); + const contextId = ContextIdFactory.create(); + const models = await Promise.all( + RegisteredModels.map((model) => { + return moduleRef.resolve(model, contextId, { strict: false }); + }), + ); + const modelsInstances = models.map((model) => model()); + + if (modelsInstances.length > 0) { + try { + // Initialize all models with the knex instance + await initialize(knexInstance, modelsInstances); + } catch (error) { + console.error('Error initializing models:', error); + throw error; + } + } + return true; + }, + strict: true, + type: 'function', +}); + +@Module({ + imports: [provider], + exports: [provider], +}) +@Global() +export class TenantModelsInitializeModule {} diff --git a/packages/webapp/src/hooks/query/cashflowAccounts.tsx b/packages/webapp/src/hooks/query/cashflowAccounts.tsx index c7991cfb4..25ce5cb2d 100644 --- a/packages/webapp/src/hooks/query/cashflowAccounts.tsx +++ b/packages/webapp/src/hooks/query/cashflowAccounts.tsx @@ -248,7 +248,7 @@ export function useCategorizeTransaction(props) { const apiRequest = useApiRequest(); return useMutation( - (values) => apiRequest.post(`banking/transactions/categorize`, values), + (values) => apiRequest.post(`banking/categorize`, values), { onSuccess: (res, id) => { // Invalidate queries. @@ -274,7 +274,7 @@ export function useUncategorizeTransaction(props) { const apiRequest = useApiRequest(); return useMutation( - (id: number) => apiRequest.post(`banking/transactions/${id}/uncategorize`), + (id: number) => apiRequest.delete(`banking/categorize/${id}`), { onSuccess: (res, id) => { // Invalidate queries.