mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
fix: Delete bank account with uncategorized transactions
This commit is contained in:
@@ -115,6 +115,7 @@ import { DecrementUncategorizedTransactionOnExclude } from '@/services/Banking/E
|
||||
import { DecrementUncategorizedTransactionOnCategorize } from '@/services/Cashflow/subscribers/DecrementUncategorizedTransactionOnCategorize';
|
||||
import { DisconnectPlaidItemOnAccountDeleted } from '@/services/Banking/BankAccounts/events/DisconnectPlaidItemOnAccountDeleted';
|
||||
import { LoopsEventsSubscriber } from '@/services/Loops/LoopsEventsSubscriber';
|
||||
import { DeleteUncategorizedTransactionsOnAccountDeleting } from '@/services/Banking/BankAccounts/events/DeleteUncategorizedTransactionsOnAccountDeleting';
|
||||
|
||||
export default () => {
|
||||
return new EventPublisher();
|
||||
@@ -277,6 +278,7 @@ export const susbcribers = () => {
|
||||
// Plaid
|
||||
RecognizeSyncedBankTranasctions,
|
||||
DisconnectPlaidItemOnAccountDeleted,
|
||||
DeleteUncategorizedTransactionsOnAccountDeleting,
|
||||
|
||||
// Loops
|
||||
LoopsEventsSubscriber
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Model } from 'objection';
|
||||
import { castArray, omit, pick } from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
|
||||
@@ -16,7 +17,10 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
queryAndThrowIfHasRelations = ({ type, message }) => {
|
||||
queryAndThrowIfHasRelations = ({ type, message, excludeRelations = [], includedRelations = [] }) => {
|
||||
const _excludeRelations = castArray(excludeRelations);
|
||||
const _includedRelations = castArray(includedRelations);
|
||||
|
||||
const model = this.modelClass();
|
||||
const modelRelations = Object.keys(model.relationMappings).filter(
|
||||
(relation) =>
|
||||
@@ -25,9 +29,13 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
||||
) !== -1
|
||||
);
|
||||
const relations = model.secureDeleteRelations || modelRelations;
|
||||
const filteredRelations = !isEmpty(_includedRelations) ?
|
||||
relations.filter(r => _includedRelations.includes(r)) :
|
||||
!isEmpty(_excludeRelations) ? relations.filter(r => !excludeRelations.includes(r)) : relations;
|
||||
|
||||
|
||||
this.runAfter((model, query) => {
|
||||
const nonEmptyRelations = relations.filter(
|
||||
const nonEmptyRelations = filteredRelations.filter(
|
||||
(relation) => !isEmpty(model[relation])
|
||||
);
|
||||
if (nonEmptyRelations.length > 0) {
|
||||
@@ -36,7 +44,7 @@ export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
||||
return model;
|
||||
});
|
||||
return this.onBuild((query) => {
|
||||
relations.forEach((relation) => {
|
||||
filteredRelations.forEach((relation) => {
|
||||
query.withGraphFetched(`${relation}(selectId)`).modifiers({
|
||||
selectId(builder) {
|
||||
builder.select('id');
|
||||
|
||||
@@ -73,6 +73,7 @@ export class DeleteAccount {
|
||||
.throwIfNotFound()
|
||||
.queryAndThrowIfHasRelations({
|
||||
type: ERRORS.ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS,
|
||||
excludeRelations: ['uncategorizedTransactions', 'plaidItem']
|
||||
});
|
||||
// Authorize before delete account.
|
||||
await this.authorize(tenantId, accountId, oldAccount);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { initialize } from 'objection';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import events from '@/subscribers/events';
|
||||
import { IAccountEventDeletePayload } from '@/interfaces';
|
||||
import { DeleteBankRulesService } from '../../Rules/DeleteBankRules';
|
||||
import { RevertRecognizedTransactions } from '../../RegonizeTranasctions/RevertRecognizedTransactions';
|
||||
|
||||
@Service()
|
||||
export class DeleteUncategorizedTransactionsOnAccountDeleting {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private deleteBankRules: DeleteBankRulesService;
|
||||
|
||||
@Inject()
|
||||
private revertRecognizedTransactins: RevertRecognizedTransactions;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.accounts.onDelete,
|
||||
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
|
||||
* associated to the deleted bank account.
|
||||
* @param {IAccountEventDeletePayload}
|
||||
*/
|
||||
private async handleDeleteBankRulesOnAccountDeleting({ tenantId, oldAccount, trx }: IAccountEventDeletePayload) {
|
||||
const knex = this.tenancy.knex(tenantId);
|
||||
const { BankRule, UncategorizedCashflowTransaction, MatchedBankTransaction, RecognizedBankTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const foundAssociatedRules = await BankRule.query(trx).where('applyIfAccountId', oldAccount.id);
|
||||
const foundAssociatedRulesIds = foundAssociatedRules.map(rule => rule.id);
|
||||
|
||||
await initialize(knex, [
|
||||
UncategorizedCashflowTransaction,
|
||||
RecognizedBankTransaction,
|
||||
MatchedBankTransaction,
|
||||
]);
|
||||
|
||||
await this.revertRecognizedTransactins.revertRecognizedTransactions(tenantId, foundAssociatedRulesIds, null, trx)
|
||||
|
||||
await this.deleteBankRules.deleteBankRules(tenantId, foundAssociatedRulesIds);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { IAccountEventDeletedPayload } from '@/interfaces';
|
||||
import { PlaidClientWrapper } from '@/lib/Plaid';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import events from '@/subscribers/events';
|
||||
import { runAfterTransaction } from '@/services/UnitOfWork/TransactionsHooks';
|
||||
|
||||
@Service()
|
||||
export class DisconnectPlaidItemOnAccountDeleted {
|
||||
@@ -51,13 +52,17 @@ export class DisconnectPlaidItemOnAccountDeleted {
|
||||
.findOne('plaidItemId', oldAccount.plaidItemId)
|
||||
.delete();
|
||||
|
||||
if (oldPlaidItem) {
|
||||
const plaidInstance = PlaidClientWrapper.getClient();
|
||||
// Remove Plaid item once the transaction resolve.
|
||||
runAfterTransaction(trx, async () => {
|
||||
if (oldPlaidItem) {
|
||||
const plaidInstance = PlaidClientWrapper.getClient();
|
||||
|
||||
// Remove the Plaid item.
|
||||
await plaidInstance.itemRemove({
|
||||
access_token: oldPlaidItem.plaidAccessToken,
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
// Remove the Plaid item.
|
||||
await plaidInstance.itemRemove({
|
||||
access_token: oldPlaidItem.plaidAccessToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class DeleteBankRuleSerivce {
|
||||
* @param {number} ruleId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async deleteBankRule(tenantId: number, ruleId: number): Promise<void> {
|
||||
public async deleteBankRule(tenantId: number, ruleId: number, trx?: Knex.Transaction): Promise<void> {
|
||||
const { BankRule, BankRuleCondition } = this.tenancy.models(tenantId);
|
||||
|
||||
const oldBankRule = await BankRule.query()
|
||||
@@ -51,6 +51,6 @@ export class DeleteBankRuleSerivce {
|
||||
ruleId,
|
||||
trx,
|
||||
} as IBankRuleEventDeletedPayload);
|
||||
});
|
||||
}, trx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from "typedi";
|
||||
import PromisePool from "@supercharge/promise-pool";
|
||||
import { castArray, uniq } from "lodash";
|
||||
import { DeleteBankRuleSerivce } from "./DeleteBankRule";
|
||||
|
||||
@Service()
|
||||
export class DeleteBankRulesService {
|
||||
@Inject()
|
||||
private deleteBankRuleService: DeleteBankRuleSerivce;
|
||||
|
||||
/**
|
||||
* Delete bank rules.
|
||||
* @param {number} tenantId
|
||||
* @param {number | Array<number>} bankRuleId
|
||||
*/
|
||||
async deleteBankRules(tenantId: number, bankRuleId: number | Array<number>, trx?: Knex.Transaction) {
|
||||
const bankRulesIds = uniq(castArray(bankRuleId));
|
||||
|
||||
await PromisePool.withConcurrency(1)
|
||||
.for(bankRulesIds)
|
||||
.process(async (bankRuleId: number) => {
|
||||
await this.deleteBankRuleService.deleteBankRule(tenantId, bankRuleId, trx);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user