refactor(nestjs): banking module

This commit is contained in:
Ahmed Bouhuolia
2025-06-02 21:32:53 +02:00
parent 7247b52fe5
commit 5595478e19
18 changed files with 216 additions and 103 deletions

View File

@@ -23,13 +23,13 @@ export class EditBankRuleService {
) {}
/**
*
* @param createDTO
* Transforms the given edit bank rule dto to model object.
* @param editDTO
* @returns
*/
private transformDTO(createDTO: EditBankRuleDto): ModelObject<BankRule> {
private transformDTO(editDTO: EditBankRuleDto): ModelObject<BankRule> {
return {
...createDTO,
...editDTO,
} as ModelObject<BankRule>;
}

View File

@@ -11,7 +11,7 @@ export class BankingMatchingController {
private readonly bankingMatchingApplication: BankingMatchingApplication
) {}
@Get('matched/transactions')
@Get('matched')
@ApiOperation({ summary: 'Retrieves the matched transactions.' })
async getMatchedTransactions(
@Query('uncategorizedTransactionIds') uncategorizedTransactionIds: number[],

View File

@@ -1,5 +1,4 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import { BankingTransactionsApplication } from '../BankingTransactions/BankingTransactionsApplication.service';
import { ApiTags } from '@nestjs/swagger';
import { RecognizedTransactionsApplication } from './RecognizedTransactions.application';

View File

@@ -1,7 +1,6 @@
import { forwardRef, Module } from '@nestjs/common';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { RecognizedBankTransaction } from './models/RecognizedBankTransaction';
import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction.service';
import { RevertRecognizedTransactionsService } from './commands/RevertRecognizedTransactions.service';
import { RecognizeTranasctionsService } from './commands/RecognizeTranasctions.service';
import { TriggerRecognizedTransactionsSubscriber } from './events/TriggerRecognizedTransactions';
@@ -11,27 +10,34 @@ import { BankingRecognizedTransactionsController } from './BankingRecognizedTran
import { RecognizedTransactionsApplication } from './RecognizedTransactions.application';
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
import { GetRecognizedTransactionService } from './queries/GetRecognizedTransaction.service';
import { BullModule } from '@nestjs/bullmq';
import { RecognizeUncategorizedTransactionsQueue } from './_types';
import { RegonizeTransactionsPrcessor } from './jobs/RecognizeTransactionsJob';
import { TenancyModule } from '../Tenancy/Tenancy.module';
const models = [RegisterTenancyModel(RecognizedBankTransaction)];
@Module({
imports: [
BankingTransactionsModule,
TenancyModule,
forwardRef(() => BankRulesModule),
BullModule.registerQueue({
name: RecognizeUncategorizedTransactionsQueue,
}),
...models,
],
providers: [
RecognizedTransactionsApplication,
GetRecognizedTransactionsService,
GetAutofillCategorizeTransactionService,
RevertRecognizedTransactionsService,
RecognizeTranasctionsService,
TriggerRecognizedTransactionsSubscriber,
GetRecognizedTransactionService,
RegonizeTransactionsPrcessor,
],
exports: [
...models,
GetAutofillCategorizeTransactionService,
RevertRecognizedTransactionsService,
RecognizeTranasctionsService,
],

View File

@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
import { GetRecognizedTransactionService } from './queries/GetRecognizedTransaction.service';
import { RevertRecognizedTransactionsService } from './commands/RevertRecognizedTransactions.service';
import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction.service';
import { IGetRecognizedTransactionsQuery } from '../BankingTransactions/types/BankingTransactions.types';
import { RevertRecognizedTransactionsCriteria } from './_types';
@@ -13,7 +12,6 @@ export class RecognizedTransactionsApplication {
private readonly getRecognizedTransactionsService: GetRecognizedTransactionsService,
private readonly getRecognizedTransactionService: GetRecognizedTransactionService,
private readonly revertRecognizedTransactionsService: RevertRecognizedTransactionsService,
private readonly getAutofillCategorizeTransactionService: GetAutofillCategorizeTransactionService,
) {}
/**
@@ -56,15 +54,4 @@ export class RecognizedTransactionsApplication {
trx,
);
}
/**
* Gets autofill categorize suggestions for a transaction.
* @param {number} uncategorizedTransactionId - The ID of the uncategorized transaction.
* @returns {Promise<any>}
*/
public getAutofillCategorizeTransaction(uncategorizedTransactionId: number) {
return this.getAutofillCategorizeTransactionService.getAutofillCategorizeTransaction(
uncategorizedTransactionId,
);
}
}

View File

@@ -1,3 +1,5 @@
import { TenantJobPayload } from "@/interfaces/Tenant";
export interface RevertRecognizedTransactionsCriteria {
batch?: string;
accountId?: number;
@@ -7,3 +9,14 @@ export interface RecognizeTransactionsCriteria {
batch?: string;
accountId?: number;
}
export const RecognizeUncategorizedTransactionsJob =
'recognize-uncategorized-transactions-job';
export const RecognizeUncategorizedTransactionsQueue =
'recognize-uncategorized-transactions-queue';
export interface RecognizeUncategorizedTransactionsJobPayload extends TenantJobPayload {
ruleId: number,
transactionsCriteria: any;
}

View File

@@ -2,21 +2,47 @@ import { isEqual, omit } from 'lodash';
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { IBankRuleEventCreatedPayload, IBankRuleEventDeletedPayload, IBankRuleEventEditedPayload } from '@/modules/BankRules/types';
import {
IBankRuleEventCreatedPayload,
IBankRuleEventDeletedPayload,
IBankRuleEventEditedPayload,
} from '@/modules/BankRules/types';
import { Queue } from 'bullmq';
import { InjectQueue } from '@nestjs/bullmq';
import {
RecognizeUncategorizedTransactionsJob,
RecognizeUncategorizedTransactionsJobPayload,
RecognizeUncategorizedTransactionsQueue,
} from '../_types';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable()
export class TriggerRecognizedTransactionsSubscriber {
constructor(
private readonly tenancyContect: TenancyContext,
@InjectQueue(RecognizeUncategorizedTransactionsQueue)
private readonly recognizeTransactionsQueue: Queue,
) {}
/**
* Triggers the recognize uncategorized transactions job on rule created.
* @param {IBankRuleEventCreatedPayload} payload -
*/
@OnEvent(events.bankRules.onCreated)
private async recognizedTransactionsOnRuleCreated({
async recognizedTransactionsOnRuleCreated({
bankRule,
}: IBankRuleEventCreatedPayload) {
const payload = { ruleId: bankRule.id };
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
const payload = {
ruleId: bankRule.id,
...tenantPayload,
} as RecognizeUncategorizedTransactionsJobPayload;
// await this.agenda.now('recognize-uncategorized-transactions-job', payload);
await this.recognizeTransactionsQueue.add(
RecognizeUncategorizedTransactionsJob,
payload,
);
}
/**
@@ -29,22 +55,28 @@ export class TriggerRecognizedTransactionsSubscriber {
oldBankRule,
bankRule,
}: IBankRuleEventEditedPayload) {
const payload = { ruleId: bankRule.id };
// Cannot continue if the new and old bank rule values are the same,
// after excluding `createdAt` and `updatedAt` dates.
if (
isEqual(
omit(bankRule, ['createdAt', 'updatedAt']),
omit(oldBankRule, ['createdAt', 'updatedAt'])
omit(oldBankRule, ['createdAt', 'updatedAt']),
)
) {
return;
}
// await this.agenda.now(
// 'rerecognize-uncategorized-transactions-job',
// payload
// );
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
const payload = {
ruleId: bankRule.id,
...tenantPayload,
} as RecognizeUncategorizedTransactionsJobPayload;
// Re-recognize the transactions based on the new rules.
await this.recognizeTransactionsQueue.add(
RecognizeUncategorizedTransactionsJob,
payload,
);
}
/**
@@ -52,15 +84,20 @@ export class TriggerRecognizedTransactionsSubscriber {
* @param {IBankRuleEventDeletedPayload} payload -
*/
@OnEvent(events.bankRules.onDeleted)
private async recognizedTransactionsOnRuleDeleted({
async recognizedTransactionsOnRuleDeleted({
ruleId,
}: IBankRuleEventDeletedPayload) {
const payload = { ruleId };
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
const payload = {
ruleId,
...tenantPayload,
} as RecognizeUncategorizedTransactionsJobPayload;
// await this.agenda.now(
// 'revert-recognized-uncategorized-transactions-job',
// payload
// );
// Re-recognize the transactions based on the new rules.
await this.recognizeTransactionsQueue.add(
RecognizeUncategorizedTransactionsJob,
payload,
);
}
/**
@@ -68,7 +105,7 @@ export class TriggerRecognizedTransactionsSubscriber {
* @param {IImportFileCommitedEventPayload} payload -
*/
@OnEvent(events.import.onImportCommitted)
private async triggerRecognizeTransactionsOnImportCommitted({
async triggerRecognizeTransactionsOnImportCommitted({
importId,
// @ts-ignore
@@ -76,10 +113,8 @@ export class TriggerRecognizedTransactionsSubscriber {
// const importFile = await Import.query().findOne({ importId });
// const batch = importFile.paramsParsed.batch;
// const payload = { transactionsCriteria: { batch } };
// // Cannot continue if the imported resource is not bank account transactions.
// if (importFile.resource !== 'UncategorizedCashflowTransaction') return;
// await this.agenda.now('recognize-uncategorized-transactions-job', payload);
}
}

View File

@@ -1,36 +1,48 @@
// import Container, { Service } from 'typedi';
// import { RecognizeTranasctionsService } from '../commands/RecognizeTranasctions.service';
import { Job } from 'bullmq';
import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Scope } from '@nestjs/common';
import { ClsService, UseCls } from 'nestjs-cls';
import { RecognizeTranasctionsService } from '../commands/RecognizeTranasctions.service';
import {
RecognizeUncategorizedTransactionsJobPayload,
RecognizeUncategorizedTransactionsQueue,
} from '../_types';
import { Process } from '@nestjs/bull';
// @Service()
// export class RegonizeTransactionsJob {
// /**
// * Constructor method.
// */
// constructor(agenda) {
// agenda.define(
// 'recognize-uncategorized-transactions-job',
// { priority: 'high', concurrency: 2 },
// this.handler
// );
// }
@Processor({
name: RecognizeUncategorizedTransactionsQueue,
scope: Scope.REQUEST,
})
export class RegonizeTransactionsPrcessor extends WorkerHost {
/**
* @param {RecognizeTranasctionsService} recognizeTranasctionsService -
* @param {ClsService} clsService -
*/
constructor(
private readonly recognizeTranasctionsService: RecognizeTranasctionsService,
private readonly clsService: ClsService,
) {
super();
}
// /**
// * Triggers sending invoice mail.
// */
// private handler = async (job, done: Function) => {
// const { tenantId, ruleId, transactionsCriteria } = job.attrs.data;
// const regonizeTransactions = Container.get(RecognizeTranasctionsService);
/**
* Triggers sending invoice mail.
*/
@Process(RecognizeUncategorizedTransactionsQueue)
@UseCls()
async process(job: Job<RecognizeUncategorizedTransactionsJobPayload>) {
const { ruleId, transactionsCriteria } = job.data;
// try {
// await regonizeTransactions.recognizeTransactions(
// tenantId,
// ruleId,
// transactionsCriteria
// );
// done();
// } catch (error) {
// console.log(error);
// done(error);
// }
// };
// }
this.clsService.set('organizationId', job.data.organizationId);
this.clsService.set('userId', job.data.userId);
try {
await this.recognizeTranasctionsService.recognizeTransactions(
ruleId,
transactionsCriteria,
);
} catch (error) {
console.log(error);
}
}
}

View File

@@ -31,6 +31,7 @@ import { GetUncategorizedBankTransactionService } from './queries/GetUncategoriz
import { BankingUncategorizedTransactionsController } from './controllers/BankingUncategorizedTransactions.controller';
import { BankingPendingTransactionsController } from './controllers/BankingPendingTransactions.controller';
import { GetPendingBankAccountTransactions } from './queries/GetPendingBankAccountTransaction.service';
import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction/GetAutofillCategorizeTransaction.service';
const models = [
RegisterTenancyModel(UncategorizedBankTransaction),
@@ -72,7 +73,8 @@ const models = [
GetBankAccountTransactionsService,
GetUncategorizedTransactions,
GetUncategorizedBankTransactionService,
GetPendingBankAccountTransactions
GetPendingBankAccountTransactions,
GetAutofillCategorizeTransactionService,
],
exports: [...models, RemovePendingUncategorizedTransaction],
})

View File

@@ -14,6 +14,7 @@ import { GetUncategorizedBankTransactionService } from './queries/GetUncategoriz
import { GetUncategorizedTransactionsQueryDto } from './dtos/GetUncategorizedTransactionsQuery.dto';
import { GetPendingBankAccountTransactions } from './queries/GetPendingBankAccountTransaction.service';
import { GetPendingTransactionsQueryDto } from './dtos/GetPendingTransactionsQuery.dto';
import { GetAutofillCategorizeTransactionService } from './queries/GetAutofillCategorizeTransaction/GetAutofillCategorizeTransaction.service';
@Injectable()
export class BankingTransactionsApplication {
@@ -25,7 +26,8 @@ export class BankingTransactionsApplication {
private readonly getBankAccountTransactionsService: GetBankAccountTransactionsService,
private readonly getBankAccountUncategorizedTransitionsService: GetUncategorizedTransactions,
private readonly getBankAccountUncategorizedTransactionService: GetUncategorizedBankTransactionService,
private readonly getPendingBankAccountTransactionsService: GetPendingBankAccountTransactions
private readonly getPendingBankAccountTransactionsService: GetPendingBankAccountTransactions,
private readonly getAutofillCategorizeTransactionService: GetAutofillCategorizeTransactionService,
) {}
/**
@@ -106,7 +108,23 @@ export class BankingTransactionsApplication {
* Retrieves the pending bank account transactions.
* @param {GetPendingTransactionsQueryDto} filter - Pending transactions query.
*/
public getPendingBankAccountTransactions(filter?: GetPendingTransactionsQueryDto) {
return this.getPendingBankAccountTransactionsService.getPendingTransactions(filter);
public getPendingBankAccountTransactions(
filter?: GetPendingTransactionsQueryDto,
) {
return this.getPendingBankAccountTransactionsService.getPendingTransactions(
filter,
);
}
/**
* Retrieves the autofill values of categorize transactions form.
* @param {Array<number> | number} uncategorizeTransactionsId - Uncategorized transactions ids.
*/
public getAutofillCategorizeTransaction(
uncategorizeTransactionsId: Array<number> | number,
) {
return this.getAutofillCategorizeTransactionService.getAutofillCategorizeTransaction(
uncategorizeTransactionsId,
);
}
}

View File

@@ -1,5 +1,11 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiParam,
ApiQuery,
} from '@nestjs/swagger';
import { GetUncategorizedTransactionsQueryDto } from '../dtos/GetUncategorizedTransactionsQuery.dto';
import { BankingTransactionsApplication } from '../BankingTransactionsApplication.service';
@@ -10,11 +16,41 @@ export class BankingUncategorizedTransactionsController {
private readonly bankingTransactionsApplication: BankingTransactionsApplication,
) {}
@Get('accounts/:accountId')
@ApiOperation({ summary: 'Get uncategorized transactions for a specific bank account' })
@Get('autofill')
@ApiOperation({ summary: 'Get autofill values for categorize transactions' })
@ApiResponse({
status: 200,
description: 'Returns a list of uncategorized transactions for the specified bank account',
description: 'Returns autofill values for categorize transactions',
})
@ApiParam({
name: 'accountId',
required: true,
type: Number,
description: 'Bank account ID',
})
@ApiQuery({
name: 'uncategorizeTransactionsId',
required: true,
type: Number,
description: 'Uncategorize transactions ID',
})
async getAutofillCategorizeTransaction(
@Query('uncategorizedTransactionIds') uncategorizedTransactionIds: Array<number> | number,
) {
console.log(uncategorizedTransactionIds)
return this.bankingTransactionsApplication.getAutofillCategorizeTransaction(
uncategorizedTransactionIds,
);
}
@Get('accounts/:accountId')
@ApiOperation({
summary: 'Get uncategorized transactions for a specific bank account',
})
@ApiResponse({
status: 200,
description:
'Returns a list of uncategorized transactions for the specified bank account',
})
@ApiParam({
name: 'accountId',

View File

@@ -5,6 +5,7 @@ import {
Get,
Param,
Post,
Put,
Query,
} from '@nestjs/common';
import { ExcludeBankTransactionsApplication } from './ExcludeBankTransactionsApplication';
@@ -18,7 +19,7 @@ export class BankingTransactionsExcludeController {
private readonly excludeBankTransactionsApplication: ExcludeBankTransactionsApplication,
) {}
@Post('bulk')
@Put('bulk')
@ApiOperation({ summary: 'Exclude the given bank transactions.' })
public excludeBankTransactions(@Body('ids') ids: number[]) {
return this.excludeBankTransactionsApplication.excludeBankTransactions(ids);
@@ -42,7 +43,7 @@ export class BankingTransactionsExcludeController {
);
}
@Post(':id')
@Put(':id')
@ApiOperation({ summary: 'Exclude the given bank transaction.' })
public excludeBankTransaction(@Param('id') id: string) {
return this.excludeBankTransactionsApplication.excludeBankTransaction(

View File

@@ -51,4 +51,14 @@ export class TenancyContext {
return this.systemUserModel.query().findById(userId);
}
async getTenantJobPayload() {
const tenant = await this.getTenant();
const user = await this.getSystemUser();
const organizationId = tenant.organizationId;
const userId = user.id;
return { organizationId, userId };
}
}