mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: bulk transcations delete
This commit is contained in:
@@ -26,12 +26,17 @@ import { GetAccountTransactionResponseDto } from './dtos/GetAccountTransactionRe
|
||||
import { GetAccountTransactionsQueryDto } from './dtos/GetAccountTransactionsQuery.dto';
|
||||
import { GetAccountsQueryDto } from './dtos/GetAccountsQuery.dto';
|
||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||
import {
|
||||
BulkDeleteDto,
|
||||
ValidateBulkDeleteResponseDto,
|
||||
} from '@/common/dtos/BulkDelete.dto';
|
||||
|
||||
@Controller('accounts')
|
||||
@ApiTags('Accounts')
|
||||
@ApiExtraModels(AccountResponseDto)
|
||||
@ApiExtraModels(AccountTypeResponseDto)
|
||||
@ApiExtraModels(GetAccountTransactionResponseDto)
|
||||
@ApiExtraModels(ValidateBulkDeleteResponseDto)
|
||||
@ApiCommonHeaders()
|
||||
export class AccountsController {
|
||||
constructor(private readonly accountsApplication: AccountsApplication) { }
|
||||
@@ -83,6 +88,37 @@ export class AccountsController {
|
||||
return this.accountsApplication.deleteAccount(id);
|
||||
}
|
||||
|
||||
@Post('validate-bulk-delete')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Validates which accounts can be deleted and returns counts of deletable and non-deletable accounts.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description:
|
||||
'Validation completed. Returns counts and IDs of deletable and non-deletable accounts.',
|
||||
schema: {
|
||||
$ref: getSchemaPath(ValidateBulkDeleteResponseDto),
|
||||
},
|
||||
})
|
||||
async validateBulkDeleteAccounts(
|
||||
@Body() bulkDeleteDto: BulkDeleteDto,
|
||||
): Promise<ValidateBulkDeleteResponseDto> {
|
||||
return this.accountsApplication.validateBulkDeleteAccounts(
|
||||
bulkDeleteDto.ids,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('bulk-delete')
|
||||
@ApiOperation({ summary: 'Deletes multiple accounts in bulk.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The accounts have been successfully deleted.',
|
||||
})
|
||||
async bulkDeleteAccounts(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> {
|
||||
return this.accountsApplication.bulkDeleteAccounts(bulkDeleteDto.ids);
|
||||
}
|
||||
|
||||
@Post(':id/activate')
|
||||
@ApiOperation({ summary: 'Activate the given account.' })
|
||||
@ApiResponse({
|
||||
|
||||
@@ -19,6 +19,8 @@ import { GetAccountsService } from './GetAccounts.service';
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
import { AccountsExportable } from './AccountsExportable.service';
|
||||
import { AccountsImportable } from './AccountsImportable.service';
|
||||
import { BulkDeleteAccountsService } from './BulkDeleteAccounts.service';
|
||||
import { ValidateBulkDeleteAccountsService } from './ValidateBulkDeleteAccounts.service';
|
||||
|
||||
const models = [RegisterTenancyModel(BankAccount)];
|
||||
|
||||
@@ -40,7 +42,9 @@ const models = [RegisterTenancyModel(BankAccount)];
|
||||
GetAccountTransactionsService,
|
||||
GetAccountsService,
|
||||
AccountsExportable,
|
||||
AccountsImportable
|
||||
AccountsImportable,
|
||||
BulkDeleteAccountsService,
|
||||
ValidateBulkDeleteAccountsService,
|
||||
],
|
||||
exports: [
|
||||
AccountRepository,
|
||||
|
||||
@@ -15,6 +15,9 @@ import { GetAccountsService } from './GetAccounts.service';
|
||||
import { IFilterMeta } from '@/interfaces/Model';
|
||||
import { GetAccountTransactionResponseDto } from './dtos/GetAccountTransactionResponse.dto';
|
||||
import { GetAccountsQueryDto } from './dtos/GetAccountsQuery.dto';
|
||||
import { BulkDeleteAccountsService } from './BulkDeleteAccounts.service';
|
||||
import { ValidateBulkDeleteAccountsService } from './ValidateBulkDeleteAccounts.service';
|
||||
import { ValidateBulkDeleteResponseDto } from '@/common/dtos/BulkDelete.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsApplication {
|
||||
@@ -37,7 +40,9 @@ export class AccountsApplication {
|
||||
private readonly getAccountService: GetAccount,
|
||||
private readonly getAccountTransactionsService: GetAccountTransactionsService,
|
||||
private readonly getAccountsService: GetAccountsService,
|
||||
) { }
|
||||
private readonly bulkDeleteAccountsService: BulkDeleteAccountsService,
|
||||
private readonly validateBulkDeleteAccountsService: ValidateBulkDeleteAccountsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new account.
|
||||
@@ -128,4 +133,22 @@ export class AccountsApplication {
|
||||
): Promise<Array<GetAccountTransactionResponseDto>> => {
|
||||
return this.getAccountTransactionsService.getAccountsTransactions(filter);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates which accounts can be deleted in bulk.
|
||||
*/
|
||||
public validateBulkDeleteAccounts = (
|
||||
accountIds: number[],
|
||||
): Promise<ValidateBulkDeleteResponseDto> => {
|
||||
return this.validateBulkDeleteAccountsService.validateBulkDeleteAccounts(
|
||||
accountIds,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes multiple accounts in bulk.
|
||||
*/
|
||||
public bulkDeleteAccounts = (accountIds: number[]): Promise<void> => {
|
||||
return this.bulkDeleteAccountsService.bulkDeleteAccounts(accountIds);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { PromisePool } from '@supercharge/promise-pool';
|
||||
import { castArray, uniq } from 'lodash';
|
||||
import { DeleteAccount } from './DeleteAccount.service';
|
||||
|
||||
@Injectable()
|
||||
export class BulkDeleteAccountsService {
|
||||
constructor(private readonly deleteAccountService: DeleteAccount) { }
|
||||
|
||||
/**
|
||||
* Deletes multiple accounts.
|
||||
* @param {number | Array<number>} accountIds - The account id or ids.
|
||||
* @param {Knex.Transaction} trx - The transaction.
|
||||
*/
|
||||
async bulkDeleteAccounts(
|
||||
accountIds: number | Array<number>,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
const accountsIds = uniq(castArray(accountIds));
|
||||
|
||||
const results = await PromisePool.withConcurrency(1)
|
||||
.for(accountsIds)
|
||||
.process(async (accountId: number) => {
|
||||
await this.deleteAccountService.deleteAccount(accountId);
|
||||
});
|
||||
|
||||
if (results.errors && results.errors.length > 0) {
|
||||
throw results.errors[0].raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||
import { DeleteAccount } from './DeleteAccount.service';
|
||||
import { ModelHasRelationsError } from '@/common/exceptions/ModelHasRelations.exception';
|
||||
|
||||
@Injectable()
|
||||
export class ValidateBulkDeleteAccountsService {
|
||||
constructor(
|
||||
private readonly deleteAccountService: DeleteAccount,
|
||||
@Inject(TENANCY_DB_CONNECTION)
|
||||
private readonly tenantKnex: () => Knex,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validates which accounts from the provided IDs can be deleted.
|
||||
* Uses the actual deleteAccount service to validate, ensuring the same validation logic.
|
||||
* Uses a transaction that is always rolled back to ensure no database changes.
|
||||
* @param {number[]} accountIds - Array of account IDs to validate
|
||||
* @returns {Promise<{deletableCount: number, nonDeletableCount: number, deletableIds: number[], nonDeletableIds: number[]}>}
|
||||
*/
|
||||
public async validateBulkDeleteAccounts(accountIds: number[]): Promise<{
|
||||
deletableCount: number;
|
||||
nonDeletableCount: number;
|
||||
deletableIds: number[];
|
||||
nonDeletableIds: number[];
|
||||
}> {
|
||||
const trx = await this.tenantKnex().transaction({
|
||||
isolationLevel: 'read uncommitted',
|
||||
});
|
||||
|
||||
try {
|
||||
const deletableIds: number[] = [];
|
||||
const nonDeletableIds: number[] = [];
|
||||
|
||||
for (const accountId of accountIds) {
|
||||
try {
|
||||
await this.deleteAccountService.deleteAccount(accountId);
|
||||
deletableIds.push(accountId);
|
||||
} catch (error) {
|
||||
if (error instanceof ModelHasRelationsError) {
|
||||
nonDeletableIds.push(accountId);
|
||||
} else {
|
||||
nonDeletableIds.push(accountId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await trx.rollback();
|
||||
|
||||
return {
|
||||
deletableCount: deletableIds.length,
|
||||
nonDeletableCount: nonDeletableIds.length,
|
||||
deletableIds,
|
||||
nonDeletableIds,
|
||||
};
|
||||
} catch (error) {
|
||||
await trx.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user