refactor(nestjs): banking modules

This commit is contained in:
Ahmed Bouhuolia
2025-06-03 21:42:09 +02:00
parent 5595478e19
commit f87bd341e9
33 changed files with 516 additions and 138 deletions

View File

@@ -90,6 +90,8 @@ import { MiscellaneousModule } from '../Miscellaneous/Miscellaneous.module';
import { UsersModule } from '../UsersModule/Users.module'; import { UsersModule } from '../UsersModule/Users.module';
import { ContactsModule } from '../Contacts/Contacts.module'; import { ContactsModule } from '../Contacts/Contacts.module';
import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module'; import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
import { BankingCategorizeModule } from '../BankingCategorize/BankingCategorize.module';
import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module';
@Module({ @Module({
imports: [ imports: [
@@ -151,6 +153,7 @@ import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
ScheduleModule.forRoot(), ScheduleModule.forRoot(),
TenancyDatabaseModule, TenancyDatabaseModule,
TenancyModelsModule, TenancyModelsModule,
TenantModelsInitializeModule,
AuthModule, AuthModule,
TenancyModule, TenancyModule,
ChromiumlyTenancyModule, ChromiumlyTenancyModule,
@@ -188,6 +191,7 @@ import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
BankingTransactionsModule, BankingTransactionsModule,
BankingMatchingModule, BankingMatchingModule,
BankingPlaidModule, BankingPlaidModule,
BankingCategorizeModule,
TransactionsLockingModule, TransactionsLockingModule,
SettingsModule, SettingsModule,
FeaturesModule, FeaturesModule,

View File

@@ -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<number>} uncategorizedTransactionId - The ID(s) of the uncategorized transaction(s) to categorize.
* @param {CategorizeBankTransactionDto} categorizeDTO - Data for categorization.
* @returns {Promise<any>} The result of the categorization operation.
*/
public categorizeTransaction(
uncategorizedTransactionId: number | Array<number>,
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<number>>} Array of affected transaction IDs.
*/
public uncategorizeTransaction(
uncategorizedTransactionId: number,
): Promise<Array<number>> {
return this.uncategorizeBankTransaction.uncategorize(
uncategorizedTransactionId,
);
}
/**
* Uncategorize multiple bank transactions in bulk.
* @param {number | Array<number>} uncategorizedTransactionIds - The ID(s) of the transaction(s) to uncategorize.
* @returns {Promise<void>}
*/
public uncategorizeTransactionsBulk(
uncategorizedTransactionIds: number | Array<number>,
) {
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<any>} 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<any>} The created uncategorized transaction.
*/
public createUncategorizedBankTransaction(
createDTO: UncategorizedBankTransactionDto,
trx?: Knex.Transaction,
) {
return this.createUncategorizedTransaction.create(createDTO, trx);
}
}

View File

@@ -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),
);
}
}

View File

@@ -1,20 +1,38 @@
import { Module } from '@nestjs/common'; import { forwardRef, Module } from '@nestjs/common';
import { CreateUncategorizedTransactionService } from './commands/CreateUncategorizedTransaction.service'; import { CreateUncategorizedTransactionService } from './commands/CreateUncategorizedTransaction.service';
import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense'; import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense';
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module'; import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
import { ExpensesModule } from '../Expenses/Expenses.module'; import { ExpensesModule } from '../Expenses/Expenses.module';
import { UncategorizedTransactionsImportable } from './commands/UncategorizedTransactionsImportable'; 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({ @Module({
imports: [BankingTransactionsModule, ExpensesModule], imports: [
BankingTransactionsModule,
ExpensesModule,
forwardRef(() => BankingTransactionsModule),
],
providers: [ providers: [
CreateUncategorizedTransactionService, CreateUncategorizedTransactionService,
CategorizeTransactionAsExpense, CategorizeTransactionAsExpense,
UncategorizedTransactionsImportable UncategorizedTransactionsImportable,
BankingCategorizeApplication,
CategorizeBankTransaction,
UncategorizeBankTransactionService,
UncategorizeBankTransactionsBulk,
], ],
exports: [ exports: [
CreateUncategorizedTransactionService, CreateUncategorizedTransactionService,
CategorizeTransactionAsExpense, CategorizeTransactionAsExpense,
BankingCategorizeApplication,
CategorizeBankTransaction,
UncategorizeBankTransactionService,
UncategorizeBankTransactionsBulk,
], ],
controllers: [BankingCategorizeController],
}) })
export class BankingCategorizeModule {} export class BankingCategorizeModule {}

View File

@@ -5,7 +5,6 @@ import { Knex } from 'knex';
import { import {
ICashflowTransactionCategorizedPayload, ICashflowTransactionCategorizedPayload,
ICashflowTransactionUncategorizingPayload, ICashflowTransactionUncategorizingPayload,
ICategorizeCashflowTransactioDTO,
} from '../types/BankingCategorize.types'; } from '../types/BankingCategorize.types';
import { import {
transformCategorizeTransToCashflow, transformCategorizeTransToCashflow,
@@ -17,9 +16,10 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction'; import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CategorizeBankTransactionDto } from '../dtos/CategorizeBankTransaction.dto';
@Injectable() @Injectable()
export class CategorizeCashflowTransaction { export class CategorizeBankTransaction {
constructor( constructor(
private readonly eventPublisher: EventEmitter2, private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
@@ -38,7 +38,7 @@ export class CategorizeCashflowTransaction {
*/ */
public async categorize( public async categorize(
uncategorizedTransactionId: number | Array<number>, uncategorizedTransactionId: number | Array<number>,
categorizeDTO: ICategorizeCashflowTransactioDTO, categorizeDTO: CategorizeBankTransactionDto,
) { ) {
const uncategorizedTransactionIds = castArray(uncategorizedTransactionId); const uncategorizedTransactionIds = castArray(uncategorizedTransactionId);
@@ -68,7 +68,6 @@ export class CategorizeCashflowTransaction {
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(
events.cashflow.onTransactionCategorizing, events.cashflow.onTransactionCategorizing,
{ {
// tenantId,
oldUncategorizedTransactions, oldUncategorizedTransactions,
trx, trx,
} as ICashflowTransactionUncategorizingPayload, } as ICashflowTransactionUncategorizingPayload,

View File

@@ -1,6 +1,5 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { import {
CreateUncategorizedTransactionDTO,
IUncategorizedTransactionCreatedEventPayload, IUncategorizedTransactionCreatedEventPayload,
IUncategorizedTransactionCreatingEventPayload, IUncategorizedTransactionCreatingEventPayload,
} from '../types/BankingCategorize.types'; } from '../types/BankingCategorize.types';
@@ -10,6 +9,7 @@ import { UncategorizedBankTransaction } from '../../BankingTransactions/models/U
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { UncategorizedBankTransactionDto } from '../dtos/CreateUncategorizedBankTransaction.dto';
@Injectable() @Injectable()
export class CreateUncategorizedTransactionService { export class CreateUncategorizedTransactionService {
@@ -30,7 +30,7 @@ export class CreateUncategorizedTransactionService {
* @returns {Promise<UncategorizedBankTransaction>} * @returns {Promise<UncategorizedBankTransaction>}
*/ */
public create( public create(
createUncategorizedTransactionDTO: CreateUncategorizedTransactionDTO, createUncategorizedTransactionDTO: UncategorizedBankTransactionDto,
trx?: Knex.Transaction, trx?: Knex.Transaction,
) { ) {
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {

View File

@@ -12,7 +12,7 @@ import { UncategorizedBankTransaction } from '../../BankingTransactions/models/U
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class UncategorizeCashflowTransactionService { export class UncategorizeBankTransactionService {
constructor( constructor(
private readonly eventPublisher: EventEmitter2, private readonly eventPublisher: EventEmitter2,
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,

View File

@@ -1,18 +1,17 @@
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import { PromisePool } from '@supercharge/promise-pool'; import { PromisePool } from '@supercharge/promise-pool';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { UncategorizeCashflowTransactionService } from './UncategorizeCashflowTransaction.service'; import { UncategorizeBankTransactionService } from './UncategorizeBankTransaction.service';
@Injectable() @Injectable()
export class UncategorizeCashflowTransactionsBulk { export class UncategorizeBankTransactionsBulk {
constructor( constructor(
private readonly uncategorizeTransactionService: UncategorizeCashflowTransactionService private readonly uncategorizeTransactionService: UncategorizeBankTransactionService
) {} ) {}
/** /**
* Uncategorize the given bank transactions in bulk. * Uncategorize the given bank transactions in bulk.
* @param {number} tenantId * @param {number | Array<number>} uncategorizedTransactionId
* @param {number} uncategorizedTransactionId
*/ */
public async uncategorizeBulk( public async uncategorizeBulk(
uncategorizedTransactionId: number | Array<number> uncategorizedTransactionId: number | Array<number>

View File

@@ -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<number>;
}

View File

@@ -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;
}

View File

@@ -1,47 +1,48 @@
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'; import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { BankingMatchingApplication } from './BankingMatchingApplication'; import { BankingMatchingApplication } from './BankingMatchingApplication';
import { GetMatchedTransactionsFilter, IMatchTransactionDTO } from './types'; import { GetMatchedTransactionsFilter } from './types';
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto'; import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
@Controller('banking/matching') @Controller('banking/matching')
@ApiTags('banking-transactions-matching') @ApiTags('banking-transactions-matching')
export class BankingMatchingController { export class BankingMatchingController {
constructor( constructor(
private readonly bankingMatchingApplication: BankingMatchingApplication private readonly bankingMatchingApplication: BankingMatchingApplication,
) {} ) {}
@Get('matched') @Get('matched')
@ApiOperation({ summary: 'Retrieves the matched transactions.' }) @ApiOperation({ summary: 'Retrieves the matched transactions.' })
async getMatchedTransactions( async getMatchedTransactions(
@Query('uncategorizedTransactionIds') uncategorizedTransactionIds: number[], @Query('uncategorizedTransactionIds') uncategorizedTransactionIds: number[],
@Query() filter: GetMatchedTransactionsFilter @Query() filter: GetMatchedTransactionsFilter,
) { ) {
return this.bankingMatchingApplication.getMatchedTransactions( return this.bankingMatchingApplication.getMatchedTransactions(
uncategorizedTransactionIds, uncategorizedTransactionIds,
filter filter,
); );
} }
@Post('/match/:uncategorizedTransactionId') @Post('/match/:uncategorizedTransactionId')
@ApiOperation({ summary: 'Match the given uncategorized transaction.' }) @ApiOperation({ summary: 'Match the given uncategorized transaction.' })
async matchTransaction( async matchTransaction(
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number | number[], @Param('uncategorizedTransactionId')
@Body() matchedTransactions: MatchBankTransactionDto uncategorizedTransactionId: number | number[],
@Body() matchedTransactions: MatchBankTransactionDto,
) { ) {
return this.bankingMatchingApplication.matchTransaction( return this.bankingMatchingApplication.matchTransaction(
uncategorizedTransactionId, uncategorizedTransactionId,
matchedTransactions matchedTransactions,
); );
} }
@Post('/unmatch/:uncategorizedTransactionId') @Post('/unmatch/:uncategorizedTransactionId')
@ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' }) @ApiOperation({ summary: 'Unmatch the given uncategorized transaction.' })
async unmatchMatchedTransaction( async unmatchMatchedTransaction(
@Param('uncategorizedTransactionId') uncategorizedTransactionId: number @Param('uncategorizedTransactionId') uncategorizedTransactionId: number,
) { ) {
return this.bankingMatchingApplication.unmatchMatchedTransaction( return this.bankingMatchingApplication.unmatchMatchedTransaction(
uncategorizedTransactionId uncategorizedTransactionId,
); );
} }
} }

View File

@@ -1,3 +1,4 @@
import { Knex } from 'knex';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { GetMatchedTransactionCashflowTransformer } from './GetMatchedTransactionCashflowTransformer'; import { GetMatchedTransactionCashflowTransformer } from './GetMatchedTransactionCashflowTransformer';
import { GetMatchedTransactionsFilter } from '../types'; import { GetMatchedTransactionsFilter } from '../types';

View File

@@ -1,10 +1,13 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { GetMatchedTransactionsFilter, MatchedTransactionPOJO } from '../types'; import { GetMatchedTransactionsFilter, MatchedTransactionPOJO, MatchedTransactionsPOJO } from '../types';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { GetMatchedTransactionExpensesTransformer } from './GetMatchedTransactionExpensesTransformer'; import { GetMatchedTransactionExpensesTransformer } from './GetMatchedTransactionExpensesTransformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { Expense } from '@/modules/Expenses/models/Expense.model'; import { Expense } from '@/modules/Expenses/models/Expense.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; 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() @Injectable()
export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByType { export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByType {
@@ -13,17 +16,26 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
@Inject(Expense.name) @Inject(Expense.name)
protected readonly expenseModel: TenantModelProxy<typeof Expense>, protected readonly expenseModel: TenantModelProxy<typeof Expense>,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantDb: () => Knex,
@Inject('TENANT_MODELS_INIT')
private readonly tenantModelsInit: () => Promise<boolean>,
) { ) {
super(); super();
} }
/** /**
* Retrieves the matched transactions of expenses. * Retrieves the matched transactions of expenses.
* @param {number} tenantId
* @param {GetMatchedTransactionsFilter} filter * @param {GetMatchedTransactionsFilter} filter
* @returns * @returns
*/ */
async getMatchedTransactions(filter: GetMatchedTransactionsFilter) { async getMatchedTransactions(
filter: GetMatchedTransactionsFilter,
): Promise<MatchedTransactionsPOJO> {
// await this.tenantModelsInit();
// Retrieve the expense matches. // Retrieve the expense matches.
const expenses = await this.expenseModel() const expenses = await this.expenseModel()
.query() .query()
@@ -49,6 +61,7 @@ export class GetMatchedTransactionsByExpenses extends GetMatchedTransactionsByTy
} }
query.orderBy('paymentDate', 'DESC'); query.orderBy('paymentDate', 'DESC');
}); });
return this.transformer.transform( return this.transformer.transform(
expenses, expenses,
new GetMatchedTransactionExpensesTransformer(), new GetMatchedTransactionExpensesTransformer(),

View File

@@ -1,3 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { first } from 'lodash'; import { first } from 'lodash';
import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer'; import { GetMatchedTransactionInvoicesTransformer } from './GetMatchedTransactionInvoicesTransformer';
@@ -9,7 +10,6 @@ import {
} from '../types'; } from '../types';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { CreatePaymentReceivedService } from '@/modules/PaymentReceived/commands/CreatePaymentReceived.serivce'; import { CreatePaymentReceivedService } from '@/modules/PaymentReceived/commands/CreatePaymentReceived.serivce';
import { Inject, Injectable } from '@nestjs/common';
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction'; import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
@@ -86,7 +86,6 @@ export class GetMatchedTransactionsByInvoices extends GetMatchedTransactionsByTy
/** /**
* Creates the common matched transaction. * Creates the common matched transaction.
* @param {number} tenantId
* @param {Array<number>} uncategorizedTransactionIds * @param {Array<number>} uncategorizedTransactionIds
* @param {IMatchTransactionDTO} matchTransactionDTO * @param {IMatchTransactionDTO} matchTransactionDTO
* @param {Knex.Transaction} trx * @param {Knex.Transaction} trx

View File

@@ -1,10 +1,13 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { initialize } from 'objection';
import { GetMatchedTransactionManualJournalsTransformer } from './GetMatchedTransactionManualJournalsTransformer'; import { GetMatchedTransactionManualJournalsTransformer } from './GetMatchedTransactionManualJournalsTransformer';
import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType'; import { GetMatchedTransactionsByType } from './GetMatchedTransactionsByType';
import { GetMatchedTransactionsFilter } from '../types'; import { GetMatchedTransactionsFilter } from '../types';
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal'; import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
@Injectable() @Injectable()
export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactionsByType { export class GetMatchedTransactionsByManualJournals extends GetMatchedTransactionsByType {

View File

@@ -12,7 +12,7 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
export abstract class GetMatchedTransactionsByType { export abstract class GetMatchedTransactionsByType {
@Inject(MatchedBankTransaction.name) @Inject(MatchedBankTransaction.name)
private readonly matchedBankTransactionModel: TenantModelProxy< matchedBankTransactionModel: TenantModelProxy<
typeof MatchedBankTransaction typeof MatchedBankTransaction
>; >;

View File

@@ -76,6 +76,11 @@ const models = [
GetPendingBankAccountTransactions, GetPendingBankAccountTransactions,
GetAutofillCategorizeTransactionService, GetAutofillCategorizeTransactionService,
], ],
exports: [...models, RemovePendingUncategorizedTransaction], exports: [
...models,
RemovePendingUncategorizedTransaction,
CommandBankTransactionValidator,
CreateBankTransactionService
],
}) })
export class BankingTransactionsModule {} export class BankingTransactionsModule {}

View File

@@ -15,6 +15,7 @@ import { GetUncategorizedTransactionsQueryDto } from './dtos/GetUncategorizedTra
import { GetPendingBankAccountTransactions } from './queries/GetPendingBankAccountTransaction.service'; import { GetPendingBankAccountTransactions } from './queries/GetPendingBankAccountTransaction.service';
import { GetPendingTransactionsQueryDto } from './dtos/GetPendingTransactionsQuery.dto'; import { GetPendingTransactionsQueryDto } from './dtos/GetPendingTransactionsQuery.dto';
import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction/GetAutofillCategorizeTransaction.service'; import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction/GetAutofillCategorizeTransaction.service';
import { GetBankTransactionsQueryDto } from './dtos/GetBankTranasctionsQuery.dto';
@Injectable() @Injectable()
export class BankingTransactionsApplication { export class BankingTransactionsApplication {
@@ -54,7 +55,7 @@ export class BankingTransactionsApplication {
* Retrieves the bank transactions of the given bank id. * Retrieves the bank transactions of the given bank id.
* @param {ICashflowAccountTransactionsQuery} query * @param {ICashflowAccountTransactionsQuery} query
*/ */
public getBankAccountTransactions(query: ICashflowAccountTransactionsQuery) { public getBankAccountTransactions(query: GetBankTransactionsQueryDto) {
return this.getBankAccountTransactionsService.bankAccountTransactions( return this.getBankAccountTransactionsService.bankAccountTransactions(
query, query,
); );

View File

@@ -1,3 +1,4 @@
import { Injectable } from '@nestjs/common';
import { includes, camelCase, upperFirst, sumBy } from 'lodash'; import { includes, camelCase, upperFirst, sumBy } from 'lodash';
import { getCashflowTransactionType } from '../utils'; import { getCashflowTransactionType } from '../utils';
import { import {
@@ -6,7 +7,6 @@ import {
ERRORS, ERRORS,
} from '../constants'; } from '../constants';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { Injectable } from '@nestjs/common';
import { ServiceError } from '@/modules/Items/ServiceError'; import { ServiceError } from '@/modules/Items/ServiceError';
import { BankTransaction } from '../models/BankTransaction'; import { BankTransaction } from '../models/BankTransaction';
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction'; import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';

View File

@@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { Knex } from 'knex'; import { Knex } from 'knex';
import * as R from 'ramda';
import * as composeAsync from 'async/compose'; import * as composeAsync from 'async/compose';
import { CASHFLOW_TRANSACTION_TYPE } from '../constants'; import { CASHFLOW_TRANSACTION_TYPE } from '../constants';
import { transformCashflowTransactionType } from '../utils'; import { transformCashflowTransactionType } from '../utils';
@@ -14,12 +13,12 @@ import { events } from '@/common/events/events';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { BankTransaction } from '../models/BankTransaction'; import { BankTransaction } from '../models/BankTransaction';
import { import {
ICashflowNewCommandDTO,
ICommandCashflowCreatedPayload, ICommandCashflowCreatedPayload,
ICommandCashflowCreatingPayload, ICommandCashflowCreatingPayload,
} from '../types/BankingTransactions.types'; } from '../types/BankingTransactions.types';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto'; import { CreateBankTransactionDto } from '../dtos/CreateBankTransaction.dto';
import { formatDateFields } from '@/utils/format-date-fields';
@Injectable() @Injectable()
export class CreateBankTransactionService { export class CreateBankTransactionService {
@@ -42,7 +41,7 @@ export class CreateBankTransactionService {
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO * @param {ICashflowNewCommandDTO} newCashflowTransactionDTO
*/ */
public authorize = async ( public authorize = async (
newCashflowTransactionDTO: ICashflowNewCommandDTO, newCashflowTransactionDTO: CreateBankTransactionDto,
creditAccount: Account, creditAccount: Account,
) => { ) => {
const transactionType = transformCashflowTransactionType( const transactionType = transformCashflowTransactionType(
@@ -60,7 +59,7 @@ export class CreateBankTransactionService {
/** /**
* Transformes owner contribution DTO to cashflow transaction. * Transformes owner contribution DTO to cashflow transaction.
* @param {ICashflowNewCommandDTO} newCashflowTransactionDTO - New transaction DTO. * @param {CreateBankTransactionDto} newCashflowTransactionDTO - New transaction DTO.
* @returns {ICashflowTransactionInput} - Cashflow transaction object. * @returns {ICashflowTransactionInput} - Cashflow transaction object.
*/ */
private transformCashflowTransactionDTO = async ( private transformCashflowTransactionDTO = async (
@@ -91,7 +90,7 @@ export class CreateBankTransactionService {
const initialDTO = { const initialDTO = {
amount, amount,
...fromDTO, ...formatDateFields(fromDTO, ['date']),
transactionNumber, transactionNumber,
currencyCode: cashflowAccount.currencyCode, currencyCode: cashflowAccount.currencyCode,
exchangeRate: fromDTO?.exchangeRate || 1, exchangeRate: fromDTO?.exchangeRate || 1,
@@ -117,7 +116,7 @@ export class CreateBankTransactionService {
* @returns {Promise<ICashflowTransaction>} * @returns {Promise<ICashflowTransaction>}
*/ */
public newCashflowTransaction = async ( public newCashflowTransaction = async (
newTransactionDTO: ICashflowNewCommandDTO, newTransactionDTO: CreateBankTransactionDto,
userId?: number, userId?: number,
): Promise<BankTransaction> => { ): Promise<BankTransaction> => {
// Retrieves the cashflow account or throw not found error. // Retrieves the cashflow account or throw not found error.

View File

@@ -1,47 +1,64 @@
import { ToNumber } from '@/common/decorators/Validators';
import { import {
IsBoolean, IsBoolean,
IsDate, IsDateString,
IsInt,
IsNotEmpty,
IsNumber, IsNumber,
IsOptional, IsOptional,
IsString, IsString,
} from 'class-validator'; } from 'class-validator';
export class CreateBankTransactionDto { export class CreateBankTransactionDto {
@IsDate() @IsDateString()
@IsNotEmpty()
date: Date; date: Date;
@IsString() @IsString()
transactionNumber: string; @IsOptional()
transactionNumber?: string;
@IsString() @IsString()
referenceNo: string; @IsOptional()
referenceNo?: string;
@IsNotEmpty()
@IsString() @IsString()
transactionType: string; transactionType: string;
@IsString() @IsString()
description: string; description: string;
@IsNotEmpty()
@ToNumber()
@IsNumber() @IsNumber()
amount: number; amount: number;
@ToNumber()
@IsNumber() @IsNumber()
exchangeRate: number; exchangeRate: number = 1;
@IsString() @IsString()
@IsOptional()
currencyCode: string; currencyCode: string;
@IsNumber() @IsNotEmpty()
@ToNumber()
@IsInt()
creditAccountId: number; creditAccountId: number;
@IsNumber() @IsNotEmpty()
@ToNumber()
@IsInt()
cashflowAccountId: number; cashflowAccountId: number;
@IsBoolean() @IsBoolean()
publish: boolean; @IsOptional()
publish: boolean = true;
@IsOptional() @IsOptional()
@IsNumber() @ToNumber()
@IsInt()
branchId?: number; branchId?: number;
@IsOptional() @IsOptional()
@@ -53,6 +70,6 @@ export class CreateBankTransactionDto {
plaidAccountId?: string; plaidAccountId?: string;
@IsOptional() @IsOptional()
@IsNumber() @IsInt()
uncategorizedTransactionId?: number; uncategorizedTransactionId?: number;
} }

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { getBankAccountTransactionsDefaultQuery } from './_utils'; import { getBankAccountTransactionsDefaultQuery } from './_utils';
import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service'; import { GetBankAccountTransactionsRepository } from './GetBankAccountTransactionsRepo.service';
import { GetBankAccountTransactions } from './GetBankAccountTransactions'; import { GetBankAccountTransactions } from './GetBankAccountTransactions';
import { ICashflowAccountTransactionsQuery } from '../../types/BankingTransactions.types'; import { GetBankTransactionsQueryDto } from '../../dtos/GetBankTranasctionsQuery.dto';
@Injectable() @Injectable()
export class GetBankAccountTransactionsService { export class GetBankAccountTransactionsService {
@@ -16,7 +16,7 @@ export class GetBankAccountTransactionsService {
* @return {Promise<IInvetoryItemDetailDOO>} * @return {Promise<IInvetoryItemDetailDOO>}
*/ */
public async bankAccountTransactions( public async bankAccountTransactions(
query: ICashflowAccountTransactionsQuery, query: GetBankTransactionsQueryDto,
) { ) {
const parsedQuery = { const parsedQuery = {
...getBankAccountTransactionsDefaultQuery(), ...getBankAccountTransactionsDefaultQuery(),

View File

@@ -1,35 +1,23 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { initialize } from 'objection';
import { Knex } from 'knex';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction'; import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction';
import { UncategorizedTransactionTransformer } from '../../BankingCategorize/commands/UncategorizedTransaction.transformer'; import { UncategorizedTransactionTransformer } from '../../BankingCategorize/commands/UncategorizedTransaction.transformer';
import { GetUncategorizedTransactionsQueryDto } from '../dtos/GetUncategorizedTransactionsQuery.dto'; import { GetUncategorizedTransactionsQueryDto } from '../dtos/GetUncategorizedTransactionsQuery.dto';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; 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() @Injectable()
export class GetUncategorizedTransactions { export class GetUncategorizedTransactions {
/**
* @param {TransformerInjectable} transformer
* @param {UncategorizedBankTransaction.name} uncategorizedBankTransactionModel
*/
constructor( constructor(
private readonly transformer: TransformerInjectable, private readonly transformer: TransformerInjectable,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantDb: () => Knex,
@Inject(UncategorizedBankTransaction.name) @Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: TenantModelProxy<typeof UncategorizedBankTransaction>, private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
@Inject(Account.name) >,
private readonly accountModel: TenantModelProxy<typeof Account>,
@Inject(RecognizedBankTransaction.name)
private readonly recognizedTransactionModel: TenantModelProxy<typeof RecognizedBankTransaction>,
@Inject(MatchedBankTransaction.name)
private readonly matchedTransactionModel: TenantModelProxy<typeof MatchedBankTransaction>,
) {} ) {}
/** /**
@@ -39,7 +27,7 @@ export class GetUncategorizedTransactions {
*/ */
public async getTransactions( public async getTransactions(
accountId: number, accountId: number,
query: GetUncategorizedTransactionsQueryDto query: GetUncategorizedTransactionsQueryDto,
) { ) {
// Parsed query with default values. // Parsed query with default values.
const _query = { const _query = {
@@ -47,16 +35,9 @@ export class GetUncategorizedTransactions {
pageSize: 20, pageSize: 20,
...query, ...query,
}; };
await initialize(this.tenantDb(), [
this.accountModel(),
this.uncategorizedBankTransactionModel(),
this.recognizedTransactionModel(),
this.matchedTransactionModel(),
]);
const { results, pagination } = const { results, pagination } =
await this.uncategorizedBankTransactionModel().query() await this.uncategorizedBankTransactionModel()
.query()
.onBuild((q) => { .onBuild((q) => {
q.where('accountId', accountId); q.where('accountId', accountId);
q.where('categorized', false); q.where('categorized', false);
@@ -89,7 +70,7 @@ export class GetUncategorizedTransactions {
const data = await this.transformer.transform( const data = await this.transformer.transform(
results, results,
new UncategorizedTransactionTransformer() new UncategorizedTransactionTransformer(),
); );
return { return {
data, data,

View File

@@ -3,7 +3,10 @@ import { OnEvent } from '@nestjs/event-emitter';
import { BankTransactionAutoIncrement } from '../commands/BankTransactionAutoIncrement.service'; import { BankTransactionAutoIncrement } from '../commands/BankTransactionAutoIncrement.service';
import { BankTransactionGLEntriesService } from '../commands/BankTransactionGLEntries'; import { BankTransactionGLEntriesService } from '../commands/BankTransactionGLEntries';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { ICommandCashflowCreatedPayload, ICommandCashflowDeletedPayload } from '../types/BankingTransactions.types'; import {
ICommandCashflowCreatedPayload,
ICommandCashflowDeletedPayload,
} from '../types/BankingTransactions.types';
@Injectable() @Injectable()
export class BankingTransactionGLEntriesSubscriber { export class BankingTransactionGLEntriesSubscriber {
@@ -56,5 +59,5 @@ export class BankingTransactionGLEntriesSubscriber {
cashflowTransactionId, cashflowTransactionId,
trx, trx,
); );
}; }
} }

View File

@@ -492,7 +492,7 @@ export class Bill extends TenantBaseModel {
TaxRateTransaction, TaxRateTransaction,
} = require('../../TaxRates/models/TaxRateTransaction.model'); } = require('../../TaxRates/models/TaxRateTransaction.model');
const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); const { MatchedBankTransaction } = require('../../BankingMatching/models/MatchedBankTransaction');
return { return {
vendor: { vendor: {
@@ -590,17 +590,17 @@ export class Bill extends TenantBaseModel {
/** /**
* Bill may belongs to matched bank transaction. * Bill may belongs to matched bank transaction.
*/ */
// matchedBankTransaction: { matchedBankTransaction: {
// relation: Model.HasManyRelation, relation: Model.HasManyRelation,
// modelClass: MatchedBankTransaction, modelClass: MatchedBankTransaction,
// join: { join: {
// from: 'bills.id', from: 'bills.id',
// to: 'matched_bank_transactions.referenceId', to: 'matched_bank_transactions.referenceId',
// }, },
// filter(query) { filter(query) {
// query.where('reference_type', 'Bill'); query.where('reference_type', 'Bill');
// }, },
// }, },
}; };
} }

View File

@@ -1,6 +1,6 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { Injectable } from '@nestjs/common'; 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 { IFilterRole } from './DynamicFilter/DynamicFilter.types';
import { DynamicFilterAdvancedFilter } from './DynamicFilter/DynamicFilterAdvancedFilter'; import { DynamicFilterAdvancedFilter } from './DynamicFilter/DynamicFilterAdvancedFilter';
import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor'; import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor';
@@ -21,7 +21,7 @@ export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
properties: { properties: {
condition: { type: 'string' }, condition: { type: 'string' },
fieldKey: { type: 'string' }, fieldKey: { type: 'string' },
value: { type: 'string' }, // value: { type: ['number', 'string'] },
}, },
}); });
const invalidFields = filterRoles.filter((filterRole) => { const invalidFields = filterRoles.filter((filterRole) => {

View File

@@ -202,7 +202,7 @@ export class Expense extends TenantBaseModel {
const { ExpenseCategory } = require('./ExpenseCategory.model'); const { ExpenseCategory } = require('./ExpenseCategory.model');
const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
const { Branch } = require('../../Branches/models/Branch.model'); const { Branch } = require('../../Branches/models/Branch.model');
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); const { MatchedBankTransaction } = require('../../BankingMatching/models/MatchedBankTransaction');
return { return {
/** /**
@@ -263,20 +263,20 @@ export class Expense extends TenantBaseModel {
}, },
}, },
// /** /**
// * Expense may belongs to matched bank transaction. * Expense may belongs to matched bank transaction.
// */ */
// matchedBankTransaction: { matchedBankTransaction: {
// relation: Model.HasManyRelation, relation: Model.HasManyRelation,
// modelClass: MatchedBankTransaction, modelClass: MatchedBankTransaction,
// join: { join: {
// from: 'expenses_transactions.id', from: 'expenses_transactions.id',
// to: 'matched_bank_transactions.referenceId', to: 'matched_bank_transactions.referenceId',
// }, },
// filter(query) { filter(query) {
// query.where('reference_type', 'Expense'); query.where('reference_type', 'Expense');
// }, },
// }, },
}; };
} }

View File

@@ -123,7 +123,7 @@ export class ManualJournal extends TenantBaseModel {
const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model'); const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
const { ManualJournalEntry } = require('./ManualJournalEntry'); const { ManualJournalEntry } = require('./ManualJournalEntry');
const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); const { MatchedBankTransaction } = require('../../BankingMatching/models/MatchedBankTransaction');
return { return {
entries: { entries: {
@@ -171,17 +171,17 @@ export class ManualJournal extends TenantBaseModel {
/** /**
* Manual journal may belongs to matched bank transaction. * Manual journal may belongs to matched bank transaction.
*/ */
// matchedBankTransaction: { matchedBankTransaction: {
// relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
// modelClass: MatchedBankTransaction, modelClass: MatchedBankTransaction,
// join: { join: {
// from: 'manual_journals.id', from: 'manual_journals.id',
// to: 'matched_bank_transactions.referenceId', to: 'matched_bank_transactions.referenceId',
// }, },
// filter(query) { filter(query) {
// query.where('reference_type', 'ManualJournal'); query.where('reference_type', 'ManualJournal');
// }, },
// }, },
}; };
} }

View File

@@ -512,7 +512,7 @@ export class SaleInvoice extends TenantBaseModel{
TaxRateTransaction, TaxRateTransaction,
} = require('../../TaxRates/models/TaxRateTransaction.model'); } = require('../../TaxRates/models/TaxRateTransaction.model');
const { Document } = require('../../ChromiumlyTenancy/models/Document'); const { Document } = require('../../ChromiumlyTenancy/models/Document');
// const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); const { MatchedBankTransaction } = require('../../BankingMatching/models/MatchedBankTransaction');
const { const {
TransactionPaymentServiceEntry, TransactionPaymentServiceEntry,
} = require('../../PaymentServices/models/TransactionPaymentServiceEntry.model'); } = require('../../PaymentServices/models/TransactionPaymentServiceEntry.model');
@@ -667,17 +667,17 @@ export class SaleInvoice extends TenantBaseModel{
/** /**
* Sale invocie may belongs to matched bank transaction. * Sale invocie may belongs to matched bank transaction.
*/ */
// matchedBankTransaction: { matchedBankTransaction: {
// relation: Model.HasManyRelation, relation: Model.HasManyRelation,
// modelClass: MatchedBankTransaction, modelClass: MatchedBankTransaction,
// join: { join: {
// from: 'sales_invoices.id', from: 'sales_invoices.id',
// to: 'matched_bank_transactions.referenceId', to: 'matched_bank_transactions.referenceId',
// }, },
// filter(query) { filter(query) {
// query.where('reference_type', 'SaleInvoice'); query.where('reference_type', 'SaleInvoice');
// }, },
// }, },
/** /**
* Sale invoice may belongs to payment methods entries. * Sale invoice may belongs to payment methods entries.

View File

@@ -5,6 +5,7 @@ import { EnsureTenantIsSeededGuard } from "./EnsureTenantIsSeeded.guards";
import { APP_GUARD } from "@nestjs/core"; import { APP_GUARD } from "@nestjs/core";
import { TenancyContext } from "./TenancyContext.service"; import { TenancyContext } from "./TenancyContext.service";
import { TenantController } from "./Tenant.controller"; import { TenantController } from "./Tenant.controller";
import { TenancyInitializeModelsGuard } from "./TenancyInitializeModels.guard";
@Module({ @Module({
@@ -23,6 +24,10 @@ import { TenantController } from "./Tenant.controller";
{ {
provide: APP_GUARD, provide: APP_GUARD,
useClass: EnsureTenantIsSeededGuard useClass: EnsureTenantIsSeededGuard
},
{
provide: APP_GUARD,
useClass: TenancyInitializeModelsGuard
} }
] ]
}) })

View File

@@ -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<boolean>,
private reflector: Reflector,
) {}
/**
* Initialize tenant models if the route is decorated with TriggerTenantModelsInitialize.
* @param {ExecutionContext} context
* @returns {Promise<boolean>}
*/
async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(
IS_PUBLIC_ROUTE,
[context.getHandler(), context.getClass()],
);
// Skip initialization for public routes
if (isPublic) {
return true;
}
const shouldIgnoreInitialization =
this.reflector.getAllAndOverride<boolean>(
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;
}
}

View File

@@ -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 {}

View File

@@ -248,7 +248,7 @@ export function useCategorizeTransaction(props) {
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
return useMutation( return useMutation(
(values) => apiRequest.post(`banking/transactions/categorize`, values), (values) => apiRequest.post(`banking/categorize`, values),
{ {
onSuccess: (res, id) => { onSuccess: (res, id) => {
// Invalidate queries. // Invalidate queries.
@@ -274,7 +274,7 @@ export function useUncategorizeTransaction(props) {
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
return useMutation( return useMutation(
(id: number) => apiRequest.post(`banking/transactions/${id}/uncategorize`), (id: number) => apiRequest.delete(`banking/categorize/${id}`),
{ {
onSuccess: (res, id) => { onSuccess: (res, id) => {
// Invalidate queries. // Invalidate queries.