feat: delete uncategorized transactions before deleting bank account

This commit is contained in:
Ahmed Bouhuolia
2024-08-18 19:30:09 +02:00
parent fb8118bea8
commit 2f21107a43
9 changed files with 104 additions and 72 deletions

View File

@@ -1,6 +1,5 @@
/* eslint-disable global-require */ /* eslint-disable global-require */
import * as R from 'ramda'; import { Model, mixin } from 'objection';
import { Model, ModelOptions, QueryContext, mixin } from 'objection';
import TenantModel from 'models/TenantModel'; import TenantModel from 'models/TenantModel';
import ModelSettings from './ModelSetting'; import ModelSettings from './ModelSetting';
import Account from './Account'; import Account from './Account';

View File

@@ -43,8 +43,8 @@ export class AccountsApplication {
/** /**
* Creates a new account. * Creates a new account.
* @param {number} tenantId * @param {number} tenantId
* @param {IAccountCreateDTO} accountDTO * @param {IAccountCreateDTO} accountDTO
* @returns {Promise<IAccount>} * @returns {Promise<IAccount>}
*/ */
public createAccount = ( public createAccount = (
@@ -108,8 +108,8 @@ export class AccountsApplication {
/** /**
* Retrieves the account details. * Retrieves the account details.
* @param {number} tenantId * @param {number} tenantId
* @param {number} accountId * @param {number} accountId
* @returns {Promise<IAccount>} * @returns {Promise<IAccount>}
*/ */
public getAccount = (tenantId: number, accountId: number) => { public getAccount = (tenantId: number, accountId: number) => {

View File

@@ -23,46 +23,56 @@ export class DeleteUncategorizedTransactionsOnAccountDeleting {
public attach(bus) { public attach(bus) {
bus.subscribe( bus.subscribe(
events.accounts.onDelete, events.accounts.onDelete,
this.handleDeleteBankRulesOnAccountDeleting.bind(this), this.handleDeleteBankRulesOnAccountDeleting.bind(this)
)
bus.subscribe(
events.accounts.onDelete,
this.handleDeleteUncategorizedTransactions.bind(this)
); );
} }
/**
* Handles delete the uncategorized transactions.
* @param {IAccountEventDeletePayload} payload -
*/
private async handleDeleteUncategorizedTransactions({ tenantId, oldAccount, trx }: IAccountEventDeletePayload) {
const { UncategorizedCashflowTransaction } = this.tenancy.models(tenantId);
await UncategorizedCashflowTransaction.query(trx)
.where('accountId', oldAccount.id)
.delete();
}
/** /**
* Handles revert the recognized transactions and delete all the bank rules * Handles revert the recognized transactions and delete all the bank rules
* associated to the deleted bank account. * associated to the deleted bank account.
* @param {IAccountEventDeletePayload} * @param {IAccountEventDeletePayload}
*/ */
private async handleDeleteBankRulesOnAccountDeleting({ tenantId, oldAccount, trx }: IAccountEventDeletePayload) { private async handleDeleteBankRulesOnAccountDeleting({
tenantId,
oldAccount,
trx,
}: IAccountEventDeletePayload) {
const knex = this.tenancy.knex(tenantId); const knex = this.tenancy.knex(tenantId);
const { BankRule, UncategorizedCashflowTransaction, MatchedBankTransaction, RecognizedBankTransaction } = this.tenancy.models(tenantId); const {
BankRule,
UncategorizedCashflowTransaction,
MatchedBankTransaction,
RecognizedBankTransaction,
} = this.tenancy.models(tenantId);
const foundAssociatedRules = await BankRule.query(trx).where('applyIfAccountId', oldAccount.id); const foundAssociatedRules = await BankRule.query(trx).where(
const foundAssociatedRulesIds = foundAssociatedRules.map(rule => rule.id); 'applyIfAccountId',
oldAccount.id
);
const foundAssociatedRulesIds = foundAssociatedRules.map((rule) => rule.id);
await initialize(knex, [ await initialize(knex, [
UncategorizedCashflowTransaction, UncategorizedCashflowTransaction,
RecognizedBankTransaction, RecognizedBankTransaction,
MatchedBankTransaction, MatchedBankTransaction,
]); ]);
// Revert the recognized transactions of the given bank rules.
await this.revertRecognizedTransactins.revertRecognizedTransactions(
tenantId,
foundAssociatedRulesIds,
null,
trx
);
// Delete the associated uncategorized transactions.
await UncategorizedCashflowTransaction.query(trx)
.where('accountId', oldAccount.id)
.delete();
await this.revertRecognizedTransactins.revertRecognizedTransactions(tenantId, foundAssociatedRulesIds, null, trx) // Delete the given bank rules.
await this.deleteBankRules.deleteBankRules(
await this.deleteBankRules.deleteBankRules(tenantId, foundAssociatedRulesIds); tenantId,
foundAssociatedRulesIds,
trx
);
} }
} }

View File

@@ -2,8 +2,8 @@ import { Inject, Service } from 'typedi';
import { IAccountEventDeletedPayload } from '@/interfaces'; import { IAccountEventDeletedPayload } from '@/interfaces';
import { PlaidClientWrapper } from '@/lib/Plaid'; import { PlaidClientWrapper } from '@/lib/Plaid';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import events from '@/subscribers/events';
import { runAfterTransaction } from '@/services/UnitOfWork/TransactionsHooks'; import { runAfterTransaction } from '@/services/UnitOfWork/TransactionsHooks';
import events from '@/subscribers/events';
@Service() @Service()
export class DisconnectPlaidItemOnAccountDeleted { export class DisconnectPlaidItemOnAccountDeleted {

View File

@@ -1,10 +1,10 @@
import HasTenancyService from '@/services/Tenancy/TenancyService'; import { Knex } from 'knex';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { PlaidClientWrapper } from '@/lib/Plaid/Plaid'; import { PlaidClientWrapper } from '@/lib/Plaid/Plaid';
import { PlaidSyncDb } from './PlaidSyncDB'; import { PlaidSyncDb } from './PlaidSyncDB';
import { PlaidFetchedTransactionsUpdates } from '@/interfaces'; import { PlaidFetchedTransactionsUpdates } from '@/interfaces';
import UnitOfWork from '@/services/UnitOfWork'; import UnitOfWork from '@/services/UnitOfWork';
import { Knex } from 'knex';
@Service() @Service()
export class PlaidUpdateTransactions { export class PlaidUpdateTransactions {
@@ -19,9 +19,9 @@ export class PlaidUpdateTransactions {
/** /**
* Handles sync the Plaid item to Bigcaptial under UOW. * Handles sync the Plaid item to Bigcaptial under UOW.
* @param {number} tenantId * @param {number} tenantId - Tenant id.
* @param {number} plaidItemId * @param {number} plaidItemId - Plaid item id.
* @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>} * @returns {Promise<{ addedCount: number; modifiedCount: number; removedCount: number; }>}
*/ */
public async updateTransactions(tenantId: number, plaidItemId: string) { public async updateTransactions(tenantId: number, plaidItemId: string) {
return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => { return this.uow.withTransaction(tenantId, (trx: Knex.Transaction) => {

View File

@@ -26,31 +26,39 @@ export class DeleteBankRuleSerivce {
* @param {number} ruleId * @param {number} ruleId
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async deleteBankRule(tenantId: number, ruleId: number, trx?: Knex.Transaction): Promise<void> { public async deleteBankRule(
tenantId: number,
ruleId: number,
trx?: Knex.Transaction
): Promise<void> {
const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId); const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId);
const oldBankRule = await BankRule.query() const oldBankRule = await BankRule.query()
.findById(ruleId) .findById(ruleId)
.throwIfNotFound(); .throwIfNotFound();
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { return this.uow.withTransaction(
// Triggers `onBankRuleDeleting` event. tenantId,
await this.eventPublisher.emitAsync(events.bankRules.onDeleting, { async (trx: Knex.Transaction) => {
tenantId, // Triggers `onBankRuleDeleting` event.
oldBankRule, await this.eventPublisher.emitAsync(events.bankRules.onDeleting, {
ruleId, tenantId,
trx, oldBankRule,
} as IBankRuleEventDeletingPayload); ruleId,
trx,
} as IBankRuleEventDeletingPayload);
await BankRuleCondition.query(trx).where('ruleId', ruleId).delete(); await BankRuleCondition.query(trx).where('ruleId', ruleId).delete()
await BankRule.query(trx).findById(ruleId).delete(); await BankRule.query(trx).findById(ruleId).delete();
// Triggers `onBankRuleDeleted` event. // Triggers `onBankRuleDeleted` event.
await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, { await await this.eventPublisher.emitAsync(events.bankRules.onDeleted, {
tenantId, tenantId,
ruleId, ruleId,
trx, trx,
} as IBankRuleEventDeletedPayload); } as IBankRuleEventDeletedPayload);
}, trx); },
trx
);
} }
} }

View File

@@ -1,8 +1,8 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Inject, Service } from "typedi"; import { Inject, Service } from 'typedi';
import PromisePool from "@supercharge/promise-pool"; import PromisePool from '@supercharge/promise-pool';
import { castArray, uniq } from "lodash"; import { castArray, uniq } from 'lodash';
import { DeleteBankRuleSerivce } from "./DeleteBankRule"; import { DeleteBankRuleSerivce } from './DeleteBankRule';
@Service() @Service()
export class DeleteBankRulesService { export class DeleteBankRulesService {
@@ -14,13 +14,21 @@ export class DeleteBankRulesService {
* @param {number} tenantId * @param {number} tenantId
* @param {number | Array<number>} bankRuleId * @param {number | Array<number>} bankRuleId
*/ */
async deleteBankRules(tenantId: number, bankRuleId: number | Array<number>, trx?: Knex.Transaction) { async deleteBankRules(
tenantId: number,
bankRuleId: number | Array<number>,
trx?: Knex.Transaction
) {
const bankRulesIds = uniq(castArray(bankRuleId)); const bankRulesIds = uniq(castArray(bankRuleId));
await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(bankRulesIds) .for(bankRulesIds)
.process(async (bankRuleId: number) => { .process(async (bankRuleId: number) => {
await this.deleteBankRuleService.deleteBankRule(tenantId, bankRuleId, trx); await this.deleteBankRuleService.deleteBankRule(
tenantId,
bankRuleId,
trx
);
}); });
} }
} }

View File

@@ -208,7 +208,6 @@ function AccountTransactionsActionsBar({
bankAccountId: accountId, bankAccountId: accountId,
}); });
}; };
// Handles uncategorize the categorized transactions in bulk. // Handles uncategorize the categorized transactions in bulk.
const handleUncategorizeCategorizedBulkBtnClick = () => { const handleUncategorizeCategorizedBulkBtnClick = () => {
openAlert('uncategorize-transactions-bulk', { openAlert('uncategorize-transactions-bulk', {
@@ -218,9 +217,9 @@ function AccountTransactionsActionsBar({
// Handles the delete account button click. // Handles the delete account button click.
const handleDeleteAccountClick = () => { const handleDeleteAccountClick = () => {
openAlert('account-delete', { openAlert('account-delete', {
accountId accountId,
}) });
} };
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
@@ -372,9 +371,17 @@ function AccountTransactionsActionsBar({
<MenuDivider /> <MenuDivider />
<If condition={isSyncingOwner && isFeedsActive}> <If condition={isSyncingOwner && isFeedsActive}>
<MenuItem intent={Intent.DANGER} onClick={handleDisconnectClick} text={'Disconnect'} /> <MenuItem
intent={Intent.DANGER}
onClick={handleDisconnectClick}
text={'Disconnect'}
/>
</If> </If>
<MenuItem intent={Intent.DANGER} onClick={handleDeleteAccountClick} text={'Delete'} /> <MenuItem
intent={Intent.DANGER}
onClick={handleDeleteAccountClick}
text={'Delete'}
/>
</Menu> </Menu>
} }
> >

View File

@@ -17,6 +17,7 @@ import { TABLES } from '@/constants/tables';
import withSettings from '@/containers/Settings/withSettings'; import withSettings from '@/containers/Settings/withSettings';
import withAlertsActions from '@/containers/Alert/withAlertActions'; import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions'; import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { withBankingActions } from '../withBankingActions';
import { useMemorizedColumnsWidths } from '@/hooks'; import { useMemorizedColumnsWidths } from '@/hooks';
import { useAccountTransactionsColumns, ActionsMenu } from './components'; import { useAccountTransactionsColumns, ActionsMenu } from './components';
@@ -26,7 +27,6 @@ import { useUncategorizeTransaction } from '@/hooks/query';
import { handleCashFlowTransactionType } from './utils'; import { handleCashFlowTransactionType } from './utils';
import { compose } from '@/utils'; import { compose } from '@/utils';
import { withBankingActions } from '../withBankingActions';
/** /**
* Account transactions data table. * Account transactions data table.