refactor: tenant proxy providers

This commit is contained in:
Ahmed Bouhuolia
2025-02-15 23:52:12 +02:00
parent 36851d3209
commit 5c0bb52b59
302 changed files with 2396 additions and 1677 deletions

View File

@@ -14,6 +14,7 @@ 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 {
@@ -21,9 +22,12 @@ export class DisconnectBankAccountService {
private eventPublisher: EventEmitter2,
private uow: UnitOfWork,
@Inject(Account.name) private accountModel: typeof Account,
@Inject(PlaidItem.name) private plaidItemModel: typeof PlaidItem,
@Inject(PLAID_CLIENT) private plaidClient: PlaidApi,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
@Inject(PlaidItem.name)
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
) {}
/**
@@ -33,7 +37,7 @@ export class DisconnectBankAccountService {
*/
public async disconnectBankAccount(bankAccountId: number) {
// Retrieve the bank account or throw not found error.
const account = await this.accountModel
const account = await this.accountModel()
.query()
.findById(bankAccountId)
.whereIn('account_type', [ACCOUNT_TYPE.CASH, ACCOUNT_TYPE.BANK])
@@ -52,10 +56,13 @@ export class DisconnectBankAccountService {
} as IBankAccountDisconnectingEventPayload);
// Remove the Plaid item from the system.
await this.plaidItemModel.query(trx).findById(account.plaidItemId).delete();
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({
await this.accountModel().query(trx).findById(bankAccountId).patch({
plaidAccountId: null,
plaidItemId: null,
isFeedsActive: false,

View File

@@ -5,23 +5,26 @@ 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: typeof Account,
@Inject(PlaidItem.name) private plaidItemModel: typeof PlaidItem,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
@Inject(PlaidItem.name)
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
private uow: UnitOfWork,
) {}
/**
* Pauses the bankfeed syncing of the given bank account.
* 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
const oldAccount = await this.accountModel()
.query()
.findById(bankAccountId)
.withGraphFetched('plaidItem')
@@ -36,7 +39,7 @@ export class PauseBankAccountFeeds {
throw new ServiceError(ERRORS.BANK_ACCOUNT_FEEDS_ALREADY_PAUSED);
}
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.plaidItemModel
await this.plaidItemModel()
.query(trx)
.findById(oldAccount.plaidItem.id)
.patch({

View File

@@ -4,12 +4,14 @@ 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: typeof Account,
@Inject(Account.name)
private readonly accountModel: TenantModelProxy<typeof Account>,
) {}
/**
@@ -18,7 +20,7 @@ export class RefreshBankAccountService {
* @returns {Promise<void>}
*/
public async refreshBankAccount(bankAccountId: number) {
const bankAccount = await this.accountModel
const bankAccount = await this.accountModel()
.query()
.findById(bankAccountId)
.withGraphFetched('plaidItem')

View File

@@ -5,12 +5,16 @@ 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: typeof Account,
@Inject(PlaidItem.name) private plaidItemModel: typeof PlaidItem,
@Inject(Account.name)
private accountModel: TenantModelProxy<typeof Account>,
@Inject(PlaidItem.name)
private plaidItemModel: TenantModelProxy<typeof PlaidItem>,
private uow: UnitOfWork,
) {}
@@ -21,7 +25,7 @@ export class ResumeBankAccountFeedsService {
* @returns {Promise<void>}
*/
public async resumeBankAccountFeeds(bankAccountId: number) {
const oldAccount = await this.accountModel
const oldAccount = await this.accountModel()
.query()
.findById(bankAccountId)
.withGraphFetched('plaidItem');
@@ -35,7 +39,7 @@ export class ResumeBankAccountFeedsService {
throw new ServiceError(ERRORS.BANK_ACCOUNT_FEEDS_ALREADY_RESUMED);
}
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.plaidItemModel
await this.plaidItemModel()
.query(trx)
.findById(oldAccount.plaidItem.id)
.patch({

View File

@@ -2,15 +2,18 @@ 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: typeof Account,
private readonly accountModel: TenantModelProxy<typeof Account>,
@Inject(UncategorizedBankTransaction.name)
private readonly uncategorizedBankTransactionModel: typeof UncategorizedBankTransaction,
private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -19,7 +22,7 @@ export class GetBankAccountSummary {
* @returns {Promise<IBankAccountSummary>}
*/
public async getBankAccountSummary(bankAccountId: number) {
const bankAccount = await this.accountModel
const bankAccount = await this.accountModel()
.query()
.findById(bankAccountId)
.throwIfNotFound();
@@ -41,60 +44,68 @@ export class GetBankAccountSummary {
// Retrieves the uncategorized transactions count of the given bank account.
const uncategorizedTranasctionsCount =
await this.uncategorizedBankTransactionModel.query().onBuild((q) => {
commonQuery(q);
await this.uncategorizedBankTransactionModel()
.query()
.onBuild((q) => {
commonQuery(q);
// Only the not matched bank transactions.
q.withGraphJoined('matchedBankTransactions');
q.whereNull('matchedBankTransactions.id');
// Only the not matched bank transactions.
q.withGraphJoined('matchedBankTransactions');
q.whereNull('matchedBankTransactions.id');
// Exclude the pending transactions.
q.modify('notPending');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// 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);
await this.uncategorizedBankTransactionModel()
.query()
.onBuild((q) => {
commonQuery(q);
q.withGraphJoined('recognizedTransaction');
q.whereNotNull('recognizedTransaction.id');
q.withGraphJoined('recognizedTransaction');
q.whereNotNull('recognizedTransaction.id');
// Exclude the pending transactions.
q.modify('notPending');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// 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');
await this.uncategorizedBankTransactionModel()
.query()
.onBuild((q) => {
q.where('accountId', bankAccountId);
q.modify('excluded');
// Exclude the pending transactions.
q.modify('notPending');
// Exclude the pending transactions.
q.modify('notPending');
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
// 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');
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();
});
// Count the results.
q.count('uncategorized_cashflow_transactions.id as total');
q.first();
});
const totalUncategorizedTransactions =
// @ts-ignore

View File

@@ -6,6 +6,7 @@ import { RevertRecognizedTransactionsService } from '@/modules/BankingTranasctio
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 {
@@ -13,9 +14,13 @@ export class DeleteUncategorizedTransactionsOnAccountDeleting {
private readonly deleteBankRules: DeleteBankRulesService,
private readonly revertRecognizedTransactins: RevertRecognizedTransactionsService,
@Inject(BankRule.name) private bankRuleModel: typeof BankRule,
@Inject(BankRule.name)
private bankRuleModel: TenantModelProxy<typeof BankRule>,
@Inject(UncategorizedBankTransaction.name)
private uncategorizedCashflowTransactionModel: typeof UncategorizedBankTransaction,
private uncategorizedCashflowTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction
>,
) {}
/**
@@ -28,10 +33,9 @@ export class DeleteUncategorizedTransactionsOnAccountDeleting {
oldAccount,
trx,
}: IAccountEventDeletePayload) {
const foundAssociatedRules = await this.bankRuleModel.query(trx).where(
'applyIfAccountId',
oldAccount.id,
);
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.
@@ -41,15 +45,12 @@ export class DeleteUncategorizedTransactionsOnAccountDeleting {
trx,
);
// Delete the associated uncategorized transactions.
await this.uncategorizedCashflowTransactionModel
await this.uncategorizedCashflowTransactionModel()
.query(trx)
.where('accountId', oldAccount.id)
.delete();
// Delete the given bank rules.
await this.deleteBankRules.deleteBankRules(
foundAssociatedRulesIds,
trx,
);
await this.deleteBankRules.deleteBankRules(foundAssociatedRulesIds, trx);
}
}

View File

@@ -6,14 +6,20 @@ 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: typeof PlaidItem,
@Inject(Account.name) private accountModel: typeof Account,
@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
@@ -21,7 +27,6 @@ export class DisconnectPlaidItemOnAccountDeleted {
*/
@OnEvent(events.accounts.onDeleted)
public async handleDisconnectPlaidItemOnAccountDelete({
tenantId,
oldAccount,
trx,
}: IAccountEventDeletedPayload) {
@@ -29,11 +34,11 @@ export class DisconnectPlaidItemOnAccountDeleted {
if (!oldAccount.plaidItemId) return;
// Retrieves the Plaid item that associated to the deleted account.
const oldPlaidItem = await this.plaidItemModel
const oldPlaidItem = await this.plaidItemModel()
.query(trx)
.findOne('plaidItemId', oldAccount.plaidItemId);
// Unlink the Plaid item from all account before deleting it.
await this.accountModel
await this.accountModel()
.query(trx)
.where('plaidItemId', oldAccount.plaidItemId)
.patch({
@@ -41,7 +46,7 @@ export class DisconnectPlaidItemOnAccountDeleted {
plaidItemId: null,
});
// Remove the Plaid item from the system.
await this.plaidItemModel
await this.plaidItemModel()
.query(trx)
.findOne('plaidItemId', oldAccount.plaidItemId)
.delete();