mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { Controller, Param, Post } from '@nestjs/common';
|
||||
import { BankAccountsApplication } from './BankAccountsApplication.service';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@Controller('banking/accounts')
|
||||
@ApiTags('banking-accounts')
|
||||
export class BankAccountsController {
|
||||
constructor(private bankAccountsApplication: BankAccountsApplication) {}
|
||||
|
||||
@Post(':id/disconnect')
|
||||
@ApiOperation({
|
||||
summary: 'Disconnect the bank connection of the given bank account.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Bank account disconnected successfully.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: 'Bank account not found.',
|
||||
})
|
||||
async disconnectBankAccount(@Param('id') bankAccountId: number) {
|
||||
return this.bankAccountsApplication.disconnectBankAccount(bankAccountId);
|
||||
}
|
||||
|
||||
@Post(':id/refresh')
|
||||
@ApiOperation({
|
||||
summary: 'Refresh the bank account transactions.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Bank account transactions refreshed successfully.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: 'Bank account not found.',
|
||||
})
|
||||
async refreshBankAccount(@Param('id') bankAccountId: number) {
|
||||
return this.bankAccountsApplication.refreshBankAccount(bankAccountId);
|
||||
}
|
||||
|
||||
@Post(':id/pause')
|
||||
@ApiOperation({
|
||||
summary: 'Pause transactions syncing of the given bank account.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Bank account transactions paused successfully.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: 'Bank account not found.',
|
||||
})
|
||||
async pauseBankAccount(@Param('id') bankAccountId: number) {
|
||||
return this.bankAccountsApplication.pauseBankAccount(bankAccountId);
|
||||
}
|
||||
|
||||
@Post(':id/resume')
|
||||
@ApiOperation({
|
||||
summary: 'Resume transactions syncing of the given bank account.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Bank account transactions resumed successfully.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 404,
|
||||
description: 'Bank account not found.',
|
||||
})
|
||||
async resumeBankAccount(@Param('id') bankAccountId: number) {
|
||||
return this.bankAccountsApplication.resumeBankAccount(bankAccountId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BankAccountsApplication } from './BankAccountsApplication.service';
|
||||
import { DisconnectBankAccountService } from './commands/DisconnectBankAccount.service';
|
||||
import { RefreshBankAccountService } from './commands/RefreshBankAccount.service';
|
||||
import { ResumeBankAccountFeedsService } from './commands/ResumeBankAccountFeeds.service';
|
||||
import { PauseBankAccountFeeds } from './commands/PauseBankAccountFeeds.service';
|
||||
import { DeleteUncategorizedTransactionsOnAccountDeleting } from './subscribers/DeleteUncategorizedTransactionsOnAccountDeleting';
|
||||
import { DisconnectPlaidItemOnAccountDeleted } from './subscribers/DisconnectPlaidItemOnAccountDeleted';
|
||||
import { BankAccountsController } from './BankAccounts.controller';
|
||||
import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
|
||||
import { PlaidModule } from '../Plaid/Plaid.module';
|
||||
import { BankRulesModule } from '../BankRules/BankRules.module';
|
||||
import { BankingTransactionsRegonizeModule } from '../BankingTranasctionsRegonize/BankingTransactionsRegonize.module';
|
||||
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
BankingPlaidModule,
|
||||
PlaidModule,
|
||||
BankRulesModule,
|
||||
BankingTransactionsRegonizeModule,
|
||||
BankingTransactionsModule,
|
||||
],
|
||||
providers: [
|
||||
DisconnectBankAccountService,
|
||||
RefreshBankAccountService,
|
||||
ResumeBankAccountFeedsService,
|
||||
PauseBankAccountFeeds,
|
||||
DeleteUncategorizedTransactionsOnAccountDeleting,
|
||||
DisconnectPlaidItemOnAccountDeleted,
|
||||
BankAccountsApplication,
|
||||
],
|
||||
exports: [BankAccountsApplication],
|
||||
controllers: [BankAccountsController],
|
||||
})
|
||||
export class BankAccountsModule {}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DisconnectBankAccountService } from './commands/DisconnectBankAccount.service';
|
||||
import { RefreshBankAccountService } from './commands/RefreshBankAccount.service';
|
||||
import { ResumeBankAccountFeedsService } from './commands/ResumeBankAccountFeeds.service';
|
||||
import { PauseBankAccountFeeds } from './commands/PauseBankAccountFeeds.service';
|
||||
|
||||
@Injectable()
|
||||
export class BankAccountsApplication {
|
||||
constructor(
|
||||
private disconnectBankAccountService: DisconnectBankAccountService,
|
||||
private readonly refreshBankAccountService: RefreshBankAccountService,
|
||||
private readonly resumeBankAccountFeedsService: ResumeBankAccountFeedsService,
|
||||
private readonly pauseBankAccountFeedsService: PauseBankAccountFeeds,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Disconnects the given bank account.
|
||||
* @param {number} bankAccountId - Bank account identifier.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async disconnectBankAccount(bankAccountId: number) {
|
||||
return this.disconnectBankAccountService.disconnectBankAccount(
|
||||
bankAccountId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the bank transactions of the given bank account.
|
||||
* @param {number} bankAccountId - Bank account identifier.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async refreshBankAccount(bankAccountId: number) {
|
||||
return this.refreshBankAccountService.refreshBankAccount(bankAccountId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the feeds sync of the given bank account.
|
||||
* @param {number} bankAccountId - Bank account identifier.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async pauseBankAccount(bankAccountId: number) {
|
||||
return this.pauseBankAccountFeedsService.pauseBankAccountFeeds(
|
||||
bankAccountId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the feeds sync of the given bank account.
|
||||
* @param {number} bankAccountId - Bank account identifier.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resumeBankAccount(bankAccountId: number) {
|
||||
return this.resumeBankAccountFeedsService.resumeBankAccountFeeds(
|
||||
bankAccountId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { PlaidApi } from 'plaid';
|
||||
import {
|
||||
ERRORS,
|
||||
IBankAccountDisconnectedEventPayload,
|
||||
IBankAccountDisconnectingEventPayload,
|
||||
} from '../types/BankAccounts.types';
|
||||
import { ACCOUNT_TYPE } from '@/constants/accounts';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { events } from '@/common/events/events';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { PLAID_CLIENT } from '@/modules/Plaid/Plaid.module';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class DisconnectBankAccountService {
|
||||
constructor(
|
||||
private eventPublisher: EventEmitter2,
|
||||
private uow: UnitOfWork,
|
||||
|
||||
@Inject(PLAID_CLIENT) private plaidClient: PlaidApi,
|
||||
@Inject(Account.name)
|
||||
private accountModel: TenantModelProxy<typeof Account>,
|
||||
|
||||
@Inject(PlaidItem.name)
|
||||
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Disconnects the given bank account.
|
||||
* @param {number} bankAccountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async disconnectBankAccount(bankAccountId: number) {
|
||||
// Retrieve the bank account or throw not found error.
|
||||
const account = await this.accountModel()
|
||||
.query()
|
||||
.findById(bankAccountId)
|
||||
.whereIn('account_type', [ACCOUNT_TYPE.CASH, ACCOUNT_TYPE.BANK])
|
||||
.withGraphFetched('plaidItem')
|
||||
.throwIfNotFound();
|
||||
|
||||
const oldPlaidItem = account.plaidItem;
|
||||
|
||||
if (!oldPlaidItem) {
|
||||
throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED);
|
||||
}
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers `onBankAccountDisconnecting` event.
|
||||
await this.eventPublisher.emitAsync(events.bankAccount.onDisconnecting, {
|
||||
bankAccountId,
|
||||
} as IBankAccountDisconnectingEventPayload);
|
||||
|
||||
// Remove the Plaid item from the system.
|
||||
await this.plaidItemModel()
|
||||
.query(trx)
|
||||
.findById(account.plaidItemId)
|
||||
.delete();
|
||||
|
||||
// Remove the plaid item association to the bank account.
|
||||
await this.accountModel().query(trx).findById(bankAccountId).patch({
|
||||
plaidAccountId: null,
|
||||
plaidItemId: null,
|
||||
isFeedsActive: false,
|
||||
});
|
||||
// Remove the Plaid item.
|
||||
await this.plaidClient.itemRemove({
|
||||
access_token: oldPlaidItem.plaidAccessToken,
|
||||
});
|
||||
// Triggers `onBankAccountDisconnected` event.
|
||||
await this.eventPublisher.emitAsync(events.bankAccount.onDisconnected, {
|
||||
bankAccountId,
|
||||
trx,
|
||||
} as IBankAccountDisconnectedEventPayload);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { ERRORS } from '../types/BankAccounts.types';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class PauseBankAccountFeeds {
|
||||
constructor(
|
||||
@Inject(Account.name)
|
||||
private accountModel: TenantModelProxy<typeof Account>,
|
||||
@Inject(PlaidItem.name)
|
||||
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||
|
||||
private uow: UnitOfWork,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Pauses the bank feed syncing of the given bank account.
|
||||
* @param {number} bankAccountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async pauseBankAccountFeeds(bankAccountId: number) {
|
||||
const oldAccount = await this.accountModel()
|
||||
.query()
|
||||
.findById(bankAccountId)
|
||||
.withGraphFetched('plaidItem')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Can't continue if the bank account is not connected.
|
||||
if (!oldAccount.plaidItem) {
|
||||
throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED);
|
||||
}
|
||||
// Cannot continue if the bank account feeds is already paused.
|
||||
if (oldAccount.plaidItem.isPaused) {
|
||||
throw new ServiceError(ERRORS.BANK_ACCOUNT_FEEDS_ALREADY_PAUSED);
|
||||
}
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
await this.plaidItemModel()
|
||||
.query(trx)
|
||||
.findById(oldAccount.plaidItem.id)
|
||||
.patch({
|
||||
pausedAt: new Date(),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { PlaidApi } from 'plaid';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { PLAID_CLIENT } from '@/modules/Plaid/Plaid.module';
|
||||
import { ERRORS } from '../types/BankAccounts.types';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class RefreshBankAccountService {
|
||||
constructor(
|
||||
@Inject(PLAID_CLIENT) private plaidClient: PlaidApi,
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Asks Plaid to trigger syncing the given bank account.
|
||||
* @param {number} bankAccountId - Bank account identifier.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async refreshBankAccount(bankAccountId: number) {
|
||||
const bankAccount = await this.accountModel()
|
||||
.query()
|
||||
.findById(bankAccountId)
|
||||
.withGraphFetched('plaidItem')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Can't continue if the given account is not linked with Plaid item.
|
||||
if (!bankAccount.plaidItem) {
|
||||
throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED);
|
||||
}
|
||||
await this.plaidClient.transactionsRefresh({
|
||||
access_token: bankAccount.plaidItem.plaidAccessToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { ERRORS } from '../types/BankAccounts.types';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { ServiceError } from '@/modules/Items/ServiceError';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class ResumeBankAccountFeedsService {
|
||||
constructor(
|
||||
@Inject(Account.name)
|
||||
private accountModel: TenantModelProxy<typeof Account>,
|
||||
|
||||
@Inject(PlaidItem.name)
|
||||
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||
|
||||
private uow: UnitOfWork,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Resumes the bank feeds syncing of the bank account.
|
||||
* @param {number} bankAccountId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async resumeBankAccountFeeds(bankAccountId: number) {
|
||||
const oldAccount = await this.accountModel()
|
||||
.query()
|
||||
.findById(bankAccountId)
|
||||
.withGraphFetched('plaidItem');
|
||||
|
||||
// Can't continue if the bank account is not connected.
|
||||
if (!oldAccount.plaidItem) {
|
||||
throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED);
|
||||
}
|
||||
// Cannot continue if the bank account feeds is already paused.
|
||||
if (!oldAccount.plaidItem.isPaused) {
|
||||
throw new ServiceError(ERRORS.BANK_ACCOUNT_FEEDS_ALREADY_RESUMED);
|
||||
}
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
await this.plaidItemModel()
|
||||
.query(trx)
|
||||
.findById(oldAccount.plaidItem.id)
|
||||
.patch({
|
||||
pausedAt: null,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetBankAccountSummary {
|
||||
constructor(
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||
|
||||
@Inject(UncategorizedBankTransaction.name)
|
||||
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
|
||||
typeof UncategorizedBankTransaction
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the bank account meta summary
|
||||
* @param {number} bankAccountId - The bank account id.
|
||||
* @returns {Promise<IBankAccountSummary>}
|
||||
*/
|
||||
public async getBankAccountSummary(bankAccountId: number) {
|
||||
const bankAccount = await this.accountModel()
|
||||
.query()
|
||||
.findById(bankAccountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const commonQuery = (q) => {
|
||||
// Include just the given account.
|
||||
q.where('accountId', bankAccountId);
|
||||
|
||||
// Only the not excluded.
|
||||
q.modify('notExcluded');
|
||||
|
||||
// Only the not categorized.
|
||||
q.modify('notCategorized');
|
||||
};
|
||||
|
||||
interface UncategorizedTransactionsCount {
|
||||
total: number;
|
||||
}
|
||||
|
||||
// Retrieves the uncategorized transactions count of the given bank account.
|
||||
const uncategorizedTranasctionsCount =
|
||||
await this.uncategorizedBankTransactionModel()
|
||||
.query()
|
||||
.onBuild((q) => {
|
||||
commonQuery(q);
|
||||
|
||||
// Only the not matched bank transactions.
|
||||
q.withGraphJoined('matchedBankTransactions');
|
||||
q.whereNull('matchedBankTransactions.id');
|
||||
|
||||
// Exclude the pending transactions.
|
||||
q.modify('notPending');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
});
|
||||
|
||||
// Retrives the recognized transactions count.
|
||||
const recognizedTransactionsCount =
|
||||
await this.uncategorizedBankTransactionModel()
|
||||
.query()
|
||||
.onBuild((q) => {
|
||||
commonQuery(q);
|
||||
|
||||
q.withGraphJoined('recognizedTransaction');
|
||||
q.whereNotNull('recognizedTransaction.id');
|
||||
|
||||
// Exclude the pending transactions.
|
||||
q.modify('notPending');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
});
|
||||
// Retrieves excluded transactions count.
|
||||
const excludedTransactionsCount =
|
||||
await this.uncategorizedBankTransactionModel()
|
||||
.query()
|
||||
.onBuild((q) => {
|
||||
q.where('accountId', bankAccountId);
|
||||
q.modify('excluded');
|
||||
|
||||
// Exclude the pending transactions.
|
||||
q.modify('notPending');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
});
|
||||
|
||||
// Retrieves the pending transactions count.
|
||||
const pendingTransactionsCount =
|
||||
await this.uncategorizedBankTransactionModel()
|
||||
.query()
|
||||
.onBuild((q) => {
|
||||
q.where('accountId', bankAccountId);
|
||||
q.modify('pending');
|
||||
|
||||
// Count the results.
|
||||
q.count('uncategorized_cashflow_transactions.id as total');
|
||||
q.first();
|
||||
});
|
||||
|
||||
const totalUncategorizedTransactions =
|
||||
// @ts-ignore
|
||||
uncategorizedTranasctionsCount?.total || 0;
|
||||
// @ts-ignore
|
||||
const totalRecognizedTransactions = recognizedTransactionsCount?.total || 0;
|
||||
// @ts-ignore
|
||||
const totalExcludedTransactions = excludedTransactionsCount?.total || 0;
|
||||
// @ts-ignore
|
||||
const totalPendingTransactions = pendingTransactionsCount?.total || 0;
|
||||
|
||||
return {
|
||||
name: bankAccount.name,
|
||||
totalUncategorizedTransactions,
|
||||
totalRecognizedTransactions,
|
||||
totalExcludedTransactions,
|
||||
totalPendingTransactions,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { IAccountEventDeletePayload } from '@/interfaces/Account';
|
||||
import { RevertRecognizedTransactionsService } from '@/modules/BankingTranasctionsRegonize/commands/RevertRecognizedTransactions.service';
|
||||
import { UncategorizedBankTransaction } from '@/modules/BankingTransactions/models/UncategorizedBankTransaction';
|
||||
import { DeleteBankRulesService } from '@/modules/BankRules/commands/DeleteBankRules.service';
|
||||
import { BankRule } from '@/modules/BankRules/models/BankRule';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteUncategorizedTransactionsOnAccountDeleting {
|
||||
constructor(
|
||||
private readonly deleteBankRules: DeleteBankRulesService,
|
||||
private readonly revertRecognizedTransactins: RevertRecognizedTransactionsService,
|
||||
|
||||
@Inject(BankRule.name)
|
||||
private bankRuleModel: TenantModelProxy<typeof BankRule>,
|
||||
|
||||
@Inject(UncategorizedBankTransaction.name)
|
||||
private uncategorizedCashflowTransactionModel: TenantModelProxy<
|
||||
typeof UncategorizedBankTransaction
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handles revert the recognized transactions and delete all the bank rules
|
||||
* associated to the deleted bank account.
|
||||
* @param {IAccountEventDeletePayload}
|
||||
*/
|
||||
@OnEvent(events.accounts.onDelete)
|
||||
public async handleDeleteBankRulesOnAccountDeleting({
|
||||
oldAccount,
|
||||
trx,
|
||||
}: IAccountEventDeletePayload) {
|
||||
const foundAssociatedRules = await this.bankRuleModel()
|
||||
.query(trx)
|
||||
.where('applyIfAccountId', oldAccount.id);
|
||||
const foundAssociatedRulesIds = foundAssociatedRules.map((rule) => rule.id);
|
||||
|
||||
// Revert the recognized transactions of the given bank rules.
|
||||
await this.revertRecognizedTransactins.revertRecognizedTransactions(
|
||||
foundAssociatedRulesIds,
|
||||
null,
|
||||
trx,
|
||||
);
|
||||
// Delete the associated uncategorized transactions.
|
||||
await this.uncategorizedCashflowTransactionModel()
|
||||
.query(trx)
|
||||
.where('accountId', oldAccount.id)
|
||||
.delete();
|
||||
|
||||
// Delete the given bank rules.
|
||||
await this.deleteBankRules.deleteBankRules(foundAssociatedRulesIds, trx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IAccountEventDeletedPayload } from '@/interfaces/Account';
|
||||
import { events } from '@/common/events/events';
|
||||
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
|
||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||
import { PlaidApi } from 'plaid';
|
||||
import { PLAID_CLIENT } from '@/modules/Plaid/Plaid.module';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class DisconnectPlaidItemOnAccountDeleted {
|
||||
constructor(
|
||||
@Inject(PLAID_CLIENT) private plaidClient: PlaidApi,
|
||||
|
||||
@Inject(PlaidItem.name)
|
||||
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
|
||||
|
||||
@Inject(Account.name)
|
||||
private accountModel: TenantModelProxy<typeof Account>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deletes Plaid item from the system and Plaid once the account deleted.
|
||||
* @param {IAccountEventDeletedPayload} payload
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@OnEvent(events.accounts.onDeleted)
|
||||
public async handleDisconnectPlaidItemOnAccountDelete({
|
||||
oldAccount,
|
||||
trx,
|
||||
}: IAccountEventDeletedPayload) {
|
||||
// Can't continue if the deleted account is not linked to Plaid item.
|
||||
if (!oldAccount.plaidItemId) return;
|
||||
|
||||
// Retrieves the Plaid item that associated to the deleted account.
|
||||
const oldPlaidItem = await this.plaidItemModel()
|
||||
.query(trx)
|
||||
.findOne('plaidItemId', oldAccount.plaidItemId);
|
||||
// Unlink the Plaid item from all account before deleting it.
|
||||
await this.accountModel()
|
||||
.query(trx)
|
||||
.where('plaidItemId', oldAccount.plaidItemId)
|
||||
.patch({
|
||||
plaidAccountId: null,
|
||||
plaidItemId: null,
|
||||
});
|
||||
// Remove the Plaid item from the system.
|
||||
await this.plaidItemModel()
|
||||
.query(trx)
|
||||
.findOne('plaidItemId', oldAccount.plaidItemId)
|
||||
.delete();
|
||||
|
||||
// Remove Plaid item once the transaction resolve.
|
||||
if (oldPlaidItem) {
|
||||
// Remove the Plaid item.
|
||||
await this.plaidClient.itemRemove({
|
||||
access_token: oldPlaidItem.plaidAccessToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export interface IBankAccountDisconnectingEventPayload {
|
||||
bankAccountId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IBankAccountDisconnectedEventPayload {
|
||||
bankAccountId: number;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export const ERRORS = {
|
||||
BANK_ACCOUNT_NOT_CONNECTED: 'BANK_ACCOUNT_NOT_CONNECTED',
|
||||
BANK_ACCOUNT_FEEDS_ALREADY_PAUSED: 'BANK_ACCOUNT_FEEDS_ALREADY_PAUSED',
|
||||
BANK_ACCOUNT_FEEDS_ALREADY_RESUMED: 'BANK_ACCOUNT_FEEDS_ALREADY_RESUMED',
|
||||
};
|
||||
Reference in New Issue
Block a user