mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
refactor(nestjs): banking module
This commit is contained in:
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { castArray, first, uniq } from 'lodash';
|
||||
import { GetAutofillCategorizeTransctionTransformer } from './GetAutofillCategorizeTransactionTransformer';
|
||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetAutofillCategorizeTransactionService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(UncategorizedBankTransaction.name)
|
||||
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||
typeof UncategorizedBankTransaction
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the autofill values of categorize transactions form.
|
||||
* @param {Array<number> | number} uncategorizeTransactionsId - Uncategorized transactions ids.
|
||||
*/
|
||||
public async getAutofillCategorizeTransaction(
|
||||
uncategorizeTransactionsId: Array<number> | number,
|
||||
) {
|
||||
const uncategorizeTransactionsIds = uniq(
|
||||
castArray(uncategorizeTransactionsId),
|
||||
);
|
||||
const uncategorizedTransactions =
|
||||
await this.uncategorizedBankTransactionModel()
|
||||
.query()
|
||||
.whereIn('id', uncategorizeTransactionsIds)
|
||||
.withGraphFetched('recognizedTransaction.assignAccount')
|
||||
.withGraphFetched('recognizedTransaction.bankRule')
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(
|
||||
{},
|
||||
new GetAutofillCategorizeTransctionTransformer(),
|
||||
{
|
||||
uncategorizedTransactions,
|
||||
firstUncategorizedTransaction: first(uncategorizedTransactions),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
import { sumBy } from 'lodash';
|
||||
import { Transformer } from '@/modules/Transformer/Transformer';
|
||||
|
||||
export class GetAutofillCategorizeTransctionTransformer extends Transformer {
|
||||
/**
|
||||
* Included attributes to the object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'amount',
|
||||
'formattedAmount',
|
||||
'isRecognized',
|
||||
'date',
|
||||
'formattedDate',
|
||||
'creditAccountId',
|
||||
'debitAccountId',
|
||||
'referenceNo',
|
||||
'transactionType',
|
||||
'recognizedByRuleId',
|
||||
'recognizedByRuleName',
|
||||
'isWithdrawalTransaction',
|
||||
'isDepositTransaction',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is recognized.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isRecognized() {
|
||||
return !!this.options.firstUncategorizedTransaction?.recognizedTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the total amount of uncategorized transactions.
|
||||
* @returns {number}
|
||||
*/
|
||||
public amount() {
|
||||
return sumBy(this.options.uncategorizedTransactions, 'amount');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted total amount of uncategorized transactions.
|
||||
* @returns {string}
|
||||
*/
|
||||
public formattedAmount() {
|
||||
return this.formatNumber(this.amount(), {
|
||||
currencyCode: 'USD',
|
||||
money: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is deposit.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isDepositTransaction() {
|
||||
const amount = this.amount();
|
||||
|
||||
return amount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is withdrawal.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public isWithdrawalTransaction() {
|
||||
const amount = this.amount();
|
||||
|
||||
return amount < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string}
|
||||
*/
|
||||
public date() {
|
||||
return this.options.firstUncategorizedTransaction?.date || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the formatted date of uncategorized transaction.
|
||||
* @returns {string}
|
||||
*/
|
||||
public formattedDate() {
|
||||
return this.formatDate(this.date());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string}
|
||||
*/
|
||||
public referenceNo() {
|
||||
return this.options.firstUncategorizedTransaction?.referenceNo || null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
public creditAccountId() {
|
||||
return (
|
||||
this.options.firstUncategorizedTransaction?.recognizedTransaction
|
||||
?.assignedAccountId || null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
public debitAccountId() {
|
||||
return this.options.firstUncategorizedTransaction?.accountId || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the assigned category of recognized transaction, if is not recognized
|
||||
* returns the default transaction type depends on the transaction normal.
|
||||
* @returns {string}
|
||||
*/
|
||||
public transactionType() {
|
||||
const assignedCategory =
|
||||
this.options.firstUncategorizedTransaction?.recognizedTransaction
|
||||
?.assignedCategory;
|
||||
|
||||
return (
|
||||
assignedCategory ||
|
||||
(this.isDepositTransaction() ? 'other_income' : 'other_expense')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
public payee() {
|
||||
return (
|
||||
this.options.firstUncategorizedTransaction?.recognizedTransaction
|
||||
?.assignedPayee || null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
public memo() {
|
||||
return (
|
||||
this.options.firstUncategorizedTransaction?.recognizedTransaction
|
||||
?.assignedMemo || null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the rule id the transaction recongized by.
|
||||
* @returns {string}
|
||||
*/
|
||||
public recognizedByRuleId() {
|
||||
return (
|
||||
this.options.firstUncategorizedTransaction?.recognizedTransaction
|
||||
?.bankRuleId || null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the rule name the transaction recongized by.
|
||||
* @returns {string}
|
||||
*/
|
||||
public recognizedByRuleName() {
|
||||
return (
|
||||
this.options.firstUncategorizedTransaction?.recognizedTransaction
|
||||
?.bankRule?.name || null
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user