This commit is contained in:
Ahmed Bouhuolia
2025-11-19 22:59:21 +02:00
parent 17bcc14231
commit 2b384b2f6f
31 changed files with 405 additions and 62 deletions

View File

@@ -7,6 +7,8 @@ import {
Get, Get,
Query, Query,
ParseIntPipe, ParseIntPipe,
DefaultValuePipe,
ParseBoolPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { AccountsApplication } from './AccountsApplication.service'; import { AccountsApplication } from './AccountsApplication.service';
import { CreateAccountDTO } from './CreateAccount.dto'; import { CreateAccountDTO } from './CreateAccount.dto';
@@ -64,14 +66,25 @@ export class AccountsController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple accounts in bulk.' }) @ApiOperation({ summary: 'Deletes multiple accounts in bulk.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable accounts will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'The accounts have been successfully deleted.', description: 'The accounts have been successfully deleted.',
}) })
async bulkDeleteAccounts( async bulkDeleteAccounts(
@Body() bulkDeleteDto: BulkDeleteDto, @Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
): Promise<void> { ): Promise<void> {
return this.accountsApplication.bulkDeleteAccounts(bulkDeleteDto.ids); return this.accountsApplication.bulkDeleteAccounts(bulkDeleteDto.ids, {
skipUndeletable,
});
} }
@Post() @Post()

View File

@@ -42,7 +42,7 @@ export class AccountsApplication {
private readonly getAccountsService: GetAccountsService, private readonly getAccountsService: GetAccountsService,
private readonly bulkDeleteAccountsService: BulkDeleteAccountsService, private readonly bulkDeleteAccountsService: BulkDeleteAccountsService,
private readonly validateBulkDeleteAccountsService: ValidateBulkDeleteAccountsService, private readonly validateBulkDeleteAccountsService: ValidateBulkDeleteAccountsService,
) {} ) { }
/** /**
* Creates a new account. * Creates a new account.
@@ -148,7 +148,13 @@ export class AccountsApplication {
/** /**
* Deletes multiple accounts in bulk. * Deletes multiple accounts in bulk.
*/ */
public bulkDeleteAccounts = (accountIds: number[]): Promise<void> => { public bulkDeleteAccounts = (
return this.bulkDeleteAccountsService.bulkDeleteAccounts(accountIds); accountIds: number[],
options?: { skipUndeletable?: boolean },
): Promise<void> => {
return this.bulkDeleteAccountsService.bulkDeleteAccounts(
accountIds,
options,
);
}; };
} }

View File

@@ -15,17 +15,25 @@ export class BulkDeleteAccountsService {
*/ */
async bulkDeleteAccounts( async bulkDeleteAccounts(
accountIds: number | Array<number>, accountIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const accountsIds = uniq(castArray(accountIds)); const accountsIds = uniq(castArray(accountIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(accountsIds) .for(accountsIds)
.process(async (accountId: number) => { .process(async (accountId: number) => {
await this.deleteAccountService.deleteAccount(accountId); try {
await this.deleteAccountService.deleteAccount(accountId);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -61,8 +61,11 @@ export class BillsApplication {
* Deletes multiple bills. * Deletes multiple bills.
* @param {number[]} billIds * @param {number[]} billIds
*/ */
public bulkDeleteBills(billIds: number[]) { public bulkDeleteBills(
return this.bulkDeleteBillsService.bulkDeleteBills(billIds); billIds: number[],
options?: { skipUndeletable?: boolean },
) {
return this.bulkDeleteBillsService.bulkDeleteBills(billIds, options);
} }
/** /**

View File

@@ -2,6 +2,7 @@ import {
ApiExtraModels, ApiExtraModels,
ApiOperation, ApiOperation,
ApiParam, ApiParam,
ApiQuery,
ApiResponse, ApiResponse,
ApiTags, ApiTags,
getSchemaPath, getSchemaPath,
@@ -15,6 +16,8 @@ import {
Delete, Delete,
Get, Get,
Query, Query,
DefaultValuePipe,
ParseBoolPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { BillsApplication } from './Bills.application'; import { BillsApplication } from './Bills.application';
import { IBillsFilter } from './Bills.types'; import { IBillsFilter } from './Bills.types';
@@ -56,12 +59,25 @@ export class BillsController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple bills.' }) @ApiOperation({ summary: 'Deletes multiple bills.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable bills will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Bills deleted successfully', description: 'Bills deleted successfully',
}) })
bulkDeleteBills(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> { bulkDeleteBills(
return this.billsApplication.bulkDeleteBills(bulkDeleteDto.ids); @Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
): Promise<void> {
return this.billsApplication.bulkDeleteBills(bulkDeleteDto.ids, {
skipUndeletable,
});
} }
@Post() @Post()

View File

@@ -10,17 +10,25 @@ export class BulkDeleteBillsService {
async bulkDeleteBills( async bulkDeleteBills(
billIds: number | Array<number>, billIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const billsIds = uniq(castArray(billIds)); const billsIds = uniq(castArray(billIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(billsIds) .for(billsIds)
.process(async (billId: number) => { .process(async (billId: number) => {
await this.deleteBillService.deleteBill(billId); try {
await this.deleteBillService.deleteBill(billId);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -12,17 +12,25 @@ export class BulkDeleteCreditNotesService {
async bulkDeleteCreditNotes( async bulkDeleteCreditNotes(
creditNoteIds: number | Array<number>, creditNoteIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const notesIds = uniq(castArray(creditNoteIds)); const notesIds = uniq(castArray(creditNoteIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(notesIds) .for(notesIds)
.process(async (creditNoteId: number) => { .process(async (creditNoteId: number) => {
await this.deleteCreditNoteService.deleteCreditNote(creditNoteId); try {
await this.deleteCreditNoteService.deleteCreditNote(creditNoteId);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -107,9 +107,13 @@ export class CreditNoteApplication {
* @param {number[]} creditNoteIds * @param {number[]} creditNoteIds
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
bulkDeleteCreditNotes(creditNoteIds: number[]) { bulkDeleteCreditNotes(
creditNoteIds: number[],
options?: { skipUndeletable?: boolean },
) {
return this.bulkDeleteCreditNotesService.bulkDeleteCreditNotes( return this.bulkDeleteCreditNotesService.bulkDeleteCreditNotes(
creditNoteIds, creditNoteIds,
options,
); );
} }

View File

@@ -3,6 +3,7 @@ import {
ApiOperation, ApiOperation,
ApiResponse, ApiResponse,
ApiParam, ApiParam,
ApiQuery,
ApiExtraModels, ApiExtraModels,
getSchemaPath, getSchemaPath,
} from '@nestjs/swagger'; } from '@nestjs/swagger';
@@ -11,7 +12,9 @@ import {
Controller, Controller,
Delete, Delete,
Get, Get,
DefaultValuePipe,
Param, Param,
ParseBoolPipe,
Post, Post,
Put, Put,
Query, Query,
@@ -37,7 +40,7 @@ export class CreditNotesController {
/** /**
* @param {CreditNoteApplication} creditNoteApplication - The credit note application service. * @param {CreditNoteApplication} creditNoteApplication - The credit note application service.
*/ */
constructor(private creditNoteApplication: CreditNoteApplication) {} constructor(private creditNoteApplication: CreditNoteApplication) { }
@Post() @Post()
@ApiOperation({ summary: 'Create a new credit note' }) @ApiOperation({ summary: 'Create a new credit note' })
@@ -140,13 +143,25 @@ export class CreditNotesController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple credit notes.' }) @ApiOperation({ summary: 'Deletes multiple credit notes.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable credit notes will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Credit notes deleted successfully', description: 'Credit notes deleted successfully',
}) })
bulkDeleteCreditNotes(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> { bulkDeleteCreditNotes(
@Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
): Promise<void> {
return this.creditNoteApplication.bulkDeleteCreditNotes( return this.creditNoteApplication.bulkDeleteCreditNotes(
bulkDeleteDto.ids, bulkDeleteDto.ids,
{ skipUndeletable },
); );
} }

View File

@@ -6,21 +6,29 @@ import { DeleteExpense } from './commands/DeleteExpense.service';
@Injectable() @Injectable()
export class BulkDeleteExpensesService { export class BulkDeleteExpensesService {
constructor(private readonly deleteExpenseService: DeleteExpense) {} constructor(private readonly deleteExpenseService: DeleteExpense) { }
async bulkDeleteExpenses( async bulkDeleteExpenses(
expenseIds: number | Array<number>, expenseIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const expensesIds = uniq(castArray(expenseIds)); const expensesIds = uniq(castArray(expenseIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(expensesIds) .for(expensesIds)
.process(async (expenseId: number) => { .process(async (expenseId: number) => {
await this.deleteExpenseService.deleteExpense(expenseId); try {
await this.deleteExpenseService.deleteExpense(expenseId);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -7,12 +7,15 @@ import {
Post, Post,
Put, Put,
Query, Query,
DefaultValuePipe,
ParseBoolPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { ExpensesApplication } from './ExpensesApplication.service'; import { ExpensesApplication } from './ExpensesApplication.service';
import { IExpensesFilter } from './Expenses.types'; import { IExpensesFilter } from './Expenses.types';
import { import {
ApiExtraModels, ApiExtraModels,
ApiOperation, ApiOperation,
ApiQuery,
ApiResponse, ApiResponse,
ApiTags, ApiTags,
getSchemaPath, getSchemaPath,
@@ -59,12 +62,25 @@ export class ExpensesController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple expenses.' }) @ApiOperation({ summary: 'Deletes multiple expenses.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable expenses will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Expenses deleted successfully', description: 'Expenses deleted successfully',
}) })
public bulkDeleteExpenses(@Body() bulkDeleteDto: BulkDeleteDto) { public bulkDeleteExpenses(
return this.expensesApplication.bulkDeleteExpenses(bulkDeleteDto.ids); @Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
) {
return this.expensesApplication.bulkDeleteExpenses(bulkDeleteDto.ids, {
skipUndeletable,
});
} }
/** /**

View File

@@ -21,7 +21,7 @@ export class ExpensesApplication {
private readonly getExpensesService: GetExpensesService, private readonly getExpensesService: GetExpensesService,
private readonly bulkDeleteExpensesService: BulkDeleteExpensesService, private readonly bulkDeleteExpensesService: BulkDeleteExpensesService,
private readonly validateBulkDeleteExpensesService: ValidateBulkDeleteExpensesService, private readonly validateBulkDeleteExpensesService: ValidateBulkDeleteExpensesService,
) {} ) { }
/** /**
* Create a new expense transaction. * Create a new expense transaction.
@@ -55,8 +55,14 @@ export class ExpensesApplication {
* Deletes expenses in bulk. * Deletes expenses in bulk.
* @param {number[]} expenseIds - Expense ids. * @param {number[]} expenseIds - Expense ids.
*/ */
public bulkDeleteExpenses(expenseIds: number[]) { public bulkDeleteExpenses(
return this.bulkDeleteExpensesService.bulkDeleteExpenses(expenseIds); expenseIds: number[],
options?: { skipUndeletable?: boolean },
) {
return this.bulkDeleteExpensesService.bulkDeleteExpenses(
expenseIds,
options,
);
} }
/** /**

View File

@@ -8,23 +8,31 @@ import { DeleteManualJournalService } from './commands/DeleteManualJournal.servi
export class BulkDeleteManualJournalsService { export class BulkDeleteManualJournalsService {
constructor( constructor(
private readonly deleteManualJournalService: DeleteManualJournalService, private readonly deleteManualJournalService: DeleteManualJournalService,
) {} ) { }
async bulkDeleteManualJournals( async bulkDeleteManualJournals(
manualJournalIds: number | Array<number>, manualJournalIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const journalsIds = uniq(castArray(manualJournalIds)); const journalsIds = uniq(castArray(manualJournalIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(journalsIds) .for(journalsIds)
.process(async (manualJournalId: number) => { .process(async (manualJournalId: number) => {
await this.deleteManualJournalService.deleteManualJournal( try {
manualJournalId, await this.deleteManualJournalService.deleteManualJournal(
); manualJournalId,
);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -8,12 +8,15 @@ import {
Post, Post,
Put, Put,
Query, Query,
DefaultValuePipe,
ParseBoolPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { ManualJournalsApplication } from './ManualJournalsApplication.service'; import { ManualJournalsApplication } from './ManualJournalsApplication.service';
import { import {
ApiExtraModels, ApiExtraModels,
ApiOperation, ApiOperation,
ApiParam, ApiParam,
ApiQuery,
ApiResponse, ApiResponse,
ApiTags, ApiTags,
getSchemaPath, getSchemaPath,
@@ -61,15 +64,25 @@ export class ManualJournalsController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple manual journals.' }) @ApiOperation({ summary: 'Deletes multiple manual journals.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable journals will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Manual journals deleted successfully', description: 'Manual journals deleted successfully',
}) })
public bulkDeleteManualJournals( public bulkDeleteManualJournals(
@Body() bulkDeleteDto: BulkDeleteDto, @Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
): Promise<void> { ): Promise<void> {
return this.manualJournalsApplication.bulkDeleteManualJournals( return this.manualJournalsApplication.bulkDeleteManualJournals(
bulkDeleteDto.ids, bulkDeleteDto.ids,
{ skipUndeletable },
); );
} }

View File

@@ -25,7 +25,7 @@ export class ManualJournalsApplication {
private getManualJournalsService: GetManualJournals, private getManualJournalsService: GetManualJournals,
private bulkDeleteManualJournalsService: BulkDeleteManualJournalsService, private bulkDeleteManualJournalsService: BulkDeleteManualJournalsService,
private validateBulkDeleteManualJournalsService: ValidateBulkDeleteManualJournalsService, private validateBulkDeleteManualJournalsService: ValidateBulkDeleteManualJournalsService,
) {} ) { }
/** /**
* Make journal entries. * Make journal entries.
@@ -65,9 +65,13 @@ export class ManualJournalsApplication {
* Bulk deletes manual journals. * Bulk deletes manual journals.
* @param {number[]} manualJournalIds * @param {number[]} manualJournalIds
*/ */
public bulkDeleteManualJournals = (manualJournalIds: number[]) => { public bulkDeleteManualJournals = (
manualJournalIds: number[],
options?: { skipUndeletable?: boolean },
) => {
return this.bulkDeleteManualJournalsService.bulkDeleteManualJournals( return this.bulkDeleteManualJournalsService.bulkDeleteManualJournals(
manualJournalIds, manualJournalIds,
options,
); );
}; };

View File

@@ -8,23 +8,31 @@ import { DeletePaymentReceivedService } from './commands/DeletePaymentReceived.s
export class BulkDeletePaymentReceivedService { export class BulkDeletePaymentReceivedService {
constructor( constructor(
private readonly deletePaymentReceivedService: DeletePaymentReceivedService, private readonly deletePaymentReceivedService: DeletePaymentReceivedService,
) {} ) { }
async bulkDeletePaymentReceived( async bulkDeletePaymentReceived(
paymentReceiveIds: number | Array<number>, paymentReceiveIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const paymentsIds = uniq(castArray(paymentReceiveIds)); const paymentsIds = uniq(castArray(paymentReceiveIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(paymentsIds) .for(paymentsIds)
.process(async (paymentReceiveId: number) => { .process(async (paymentReceiveId: number) => {
await this.deletePaymentReceivedService.deletePaymentReceive( try {
paymentReceiveId, await this.deletePaymentReceivedService.deletePaymentReceive(
); paymentReceiveId,
);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -81,9 +81,13 @@ export class PaymentReceivesApplication {
* Deletes multiple payment receives. * Deletes multiple payment receives.
* @param {number[]} paymentReceiveIds * @param {number[]} paymentReceiveIds
*/ */
public bulkDeletePaymentReceives(paymentReceiveIds: number[]) { public bulkDeletePaymentReceives(
paymentReceiveIds: number[],
options?: { skipUndeletable?: boolean },
) {
return this.bulkDeletePaymentReceivedService.bulkDeletePaymentReceived( return this.bulkDeletePaymentReceivedService.bulkDeletePaymentReceived(
paymentReceiveIds, paymentReceiveIds,
options,
); );
} }

View File

@@ -1,6 +1,7 @@
import { import {
ApiExtraModels, ApiExtraModels,
ApiOperation, ApiOperation,
ApiQuery,
ApiResponse, ApiResponse,
ApiTags, ApiTags,
getSchemaPath, getSchemaPath,
@@ -13,10 +14,12 @@ import {
Headers, Headers,
HttpCode, HttpCode,
Param, Param,
ParseBoolPipe,
ParseIntPipe, ParseIntPipe,
Post, Post,
Put, Put,
Query, Query,
DefaultValuePipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { PaymentReceivesApplication } from './PaymentReceived.application'; import { PaymentReceivesApplication } from './PaymentReceived.application';
import { import {
@@ -171,13 +174,25 @@ export class PaymentReceivesController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple payments received.' }) @ApiOperation({ summary: 'Deletes multiple payments received.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable payments will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Payments received deleted successfully.', description: 'Payments received deleted successfully.',
}) })
public bulkDeletePaymentsReceived(@Body() bulkDeleteDto: BulkDeleteDto) { public bulkDeletePaymentsReceived(
@Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
) {
return this.paymentReceivesApplication.bulkDeletePaymentReceives( return this.paymentReceivesApplication.bulkDeletePaymentReceives(
bulkDeleteDto.ids, bulkDeleteDto.ids,
{ skipUndeletable },
); );
} }

View File

@@ -10,17 +10,25 @@ export class BulkDeleteSaleEstimatesService {
async bulkDeleteSaleEstimates( async bulkDeleteSaleEstimates(
saleEstimateIds: number | Array<number>, saleEstimateIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const estimatesIds = uniq(castArray(saleEstimateIds)); const estimatesIds = uniq(castArray(saleEstimateIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(estimatesIds) .for(estimatesIds)
.process(async (saleEstimateId: number) => { .process(async (saleEstimateId: number) => {
await this.deleteSaleEstimateService.deleteEstimate(saleEstimateId); try {
await this.deleteSaleEstimateService.deleteEstimate(saleEstimateId);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -39,7 +39,7 @@ export class SaleEstimatesApplication {
private readonly getSaleEstimateMailStateService: GetSaleEstimateMailStateService, private readonly getSaleEstimateMailStateService: GetSaleEstimateMailStateService,
private readonly bulkDeleteSaleEstimatesService: BulkDeleteSaleEstimatesService, private readonly bulkDeleteSaleEstimatesService: BulkDeleteSaleEstimatesService,
private readonly validateBulkDeleteSaleEstimatesService: ValidateBulkDeleteSaleEstimatesService, private readonly validateBulkDeleteSaleEstimatesService: ValidateBulkDeleteSaleEstimatesService,
) {} ) { }
/** /**
* Create a sale estimate. * Create a sale estimate.
@@ -77,9 +77,13 @@ export class SaleEstimatesApplication {
* @param {number[]} saleEstimateIds * @param {number[]} saleEstimateIds
* @return {Promise<void>} * @return {Promise<void>}
*/ */
public bulkDeleteSaleEstimates(saleEstimateIds: number[]) { public bulkDeleteSaleEstimates(
saleEstimateIds: number[],
options?: { skipUndeletable?: boolean },
) {
return this.bulkDeleteSaleEstimatesService.bulkDeleteSaleEstimates( return this.bulkDeleteSaleEstimatesService.bulkDeleteSaleEstimates(
saleEstimateIds, saleEstimateIds,
options,
); );
} }

View File

@@ -2,6 +2,7 @@ import {
ApiExtraModels, ApiExtraModels,
ApiOperation, ApiOperation,
ApiParam, ApiParam,
ApiQuery,
ApiResponse, ApiResponse,
ApiTags, ApiTags,
getSchemaPath, getSchemaPath,
@@ -11,9 +12,11 @@ import {
Controller, Controller,
Delete, Delete,
Get, Get,
DefaultValuePipe,
Headers, Headers,
HttpCode, HttpCode,
Param, Param,
ParseBoolPipe,
ParseIntPipe, ParseIntPipe,
Post, Post,
Put, Put,
@@ -72,15 +75,25 @@ export class SaleEstimatesController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple sale estimates.' }) @ApiOperation({ summary: 'Deletes multiple sale estimates.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable estimates will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Sale estimates deleted successfully', description: 'Sale estimates deleted successfully',
}) })
public bulkDeleteSaleEstimates( public bulkDeleteSaleEstimates(
@Body() bulkDeleteDto: BulkDeleteDto, @Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
): Promise<void> { ): Promise<void> {
return this.saleEstimatesApplication.bulkDeleteSaleEstimates( return this.saleEstimatesApplication.bulkDeleteSaleEstimates(
bulkDeleteDto.ids, bulkDeleteDto.ids,
{ skipUndeletable },
); );
} }

View File

@@ -10,19 +10,25 @@ export class BulkDeleteSaleInvoicesService {
async bulkDeleteSaleInvoices( async bulkDeleteSaleInvoices(
saleInvoiceIds: number | Array<number>, saleInvoiceIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const invoicesIds = uniq(castArray(saleInvoiceIds)); const invoicesIds = uniq(castArray(saleInvoiceIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(invoicesIds) .for(invoicesIds)
.process(async (saleInvoiceId: number) => { .process(async (saleInvoiceId: number) => {
await this.deleteSaleInvoiceService.deleteSaleInvoice(saleInvoiceId); try {
await this.deleteSaleInvoiceService.deleteSaleInvoice(saleInvoiceId);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (!skipUndeletable && results.errors && results.errors.length > 0) {
if (results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }
} }

View File

@@ -45,7 +45,7 @@ export class SaleInvoiceApplication {
private generateShareLinkService: GenerateShareLink, private generateShareLinkService: GenerateShareLink,
private bulkDeleteSaleInvoicesService: BulkDeleteSaleInvoicesService, private bulkDeleteSaleInvoicesService: BulkDeleteSaleInvoicesService,
private validateBulkDeleteSaleInvoicesService: ValidateBulkDeleteSaleInvoicesService, private validateBulkDeleteSaleInvoicesService: ValidateBulkDeleteSaleInvoicesService,
) {} ) { }
/** /**
* Creates a new sale invoice with associated GL entries. * Creates a new sale invoice with associated GL entries.
@@ -87,9 +87,13 @@ export class SaleInvoiceApplication {
* @param {number[]} saleInvoiceIds * @param {number[]} saleInvoiceIds
* @return {Promise<void>} * @return {Promise<void>}
*/ */
public bulkDeleteSaleInvoices(saleInvoiceIds: number[]) { public bulkDeleteSaleInvoices(
saleInvoiceIds: number[],
options?: { skipUndeletable?: boolean },
) {
return this.bulkDeleteSaleInvoicesService.bulkDeleteSaleInvoices( return this.bulkDeleteSaleInvoicesService.bulkDeleteSaleInvoices(
saleInvoiceIds, saleInvoiceIds,
options,
); );
} }

View File

@@ -2,11 +2,13 @@ import { Response } from 'express';
import { import {
Body, Body,
Controller, Controller,
DefaultValuePipe,
Delete, Delete,
Get, Get,
Headers, Headers,
HttpCode, HttpCode,
Param, Param,
ParseBoolPipe,
ParseIntPipe, ParseIntPipe,
Post, Post,
Put, Put,
@@ -78,13 +80,25 @@ export class SaleInvoicesController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple sale invoices.' }) @ApiOperation({ summary: 'Deletes multiple sale invoices.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable invoices will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Sale invoices deleted successfully', description: 'Sale invoices deleted successfully',
}) })
bulkDeleteSaleInvoices(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> { bulkDeleteSaleInvoices(
@Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
): Promise<void> {
return this.saleInvoiceApplication.bulkDeleteSaleInvoices( return this.saleInvoiceApplication.bulkDeleteSaleInvoices(
bulkDeleteDto.ids, bulkDeleteDto.ids,
{ skipUndeletable },
); );
} }

View File

@@ -12,17 +12,25 @@ export class BulkDeleteSaleReceiptsService {
async bulkDeleteSaleReceipts( async bulkDeleteSaleReceipts(
saleReceiptIds: number | number[], saleReceiptIds: number | number[],
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const receiptIds = uniq(castArray(saleReceiptIds)); const receiptIds = uniq(castArray(saleReceiptIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(receiptIds) .for(receiptIds)
.process(async (saleReceiptId: number) => { .process(async (saleReceiptId: number) => {
await this.deleteSaleReceiptService.deleteSaleReceipt(saleReceiptId); try {
await this.deleteSaleReceiptService.deleteSaleReceipt(saleReceiptId);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -93,9 +93,13 @@ export class SaleReceiptApplication {
* Deletes multiple sale receipts. * Deletes multiple sale receipts.
* @param {number[]} saleReceiptIds * @param {number[]} saleReceiptIds
*/ */
public async bulkDeleteSaleReceipts(saleReceiptIds: number[]) { public async bulkDeleteSaleReceipts(
saleReceiptIds: number[],
options?: { skipUndeletable?: boolean },
) {
return this.bulkDeleteSaleReceiptsService.bulkDeleteSaleReceipts( return this.bulkDeleteSaleReceiptsService.bulkDeleteSaleReceipts(
saleReceiptIds, saleReceiptIds,
options,
); );
} }

View File

@@ -6,6 +6,8 @@ import {
Headers, Headers,
HttpCode, HttpCode,
Param, Param,
ParseBoolPipe,
DefaultValuePipe,
ParseIntPipe, ParseIntPipe,
Post, Post,
Put, Put,
@@ -17,6 +19,7 @@ import {
ApiExtraModels, ApiExtraModels,
ApiOperation, ApiOperation,
ApiParam, ApiParam,
ApiQuery,
ApiResponse, ApiResponse,
ApiTags, ApiTags,
getSchemaPath, getSchemaPath,
@@ -70,13 +73,25 @@ export class SaleReceiptsController {
@Post('bulk-delete') @Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple sale receipts.' }) @ApiOperation({ summary: 'Deletes multiple sale receipts.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable receipts will be skipped and only deletable ones will be removed.',
})
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Sale receipts deleted successfully', description: 'Sale receipts deleted successfully',
}) })
bulkDeleteSaleReceipts(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> { bulkDeleteSaleReceipts(
@Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
): Promise<void> {
return this.saleReceiptApplication.bulkDeleteSaleReceipts( return this.saleReceiptApplication.bulkDeleteSaleReceipts(
bulkDeleteDto.ids, bulkDeleteDto.ids,
{ skipUndeletable },
); );
} }

View File

@@ -8,21 +8,31 @@ import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service
export class BulkDeleteVendorCreditsService { export class BulkDeleteVendorCreditsService {
constructor( constructor(
private readonly deleteVendorCreditService: DeleteVendorCreditService, private readonly deleteVendorCreditService: DeleteVendorCreditService,
) {} ) { }
async bulkDeleteVendorCredits( async bulkDeleteVendorCredits(
vendorCreditIds: number | Array<number>, vendorCreditIds: number | Array<number>,
options?: { skipUndeletable?: boolean },
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<void> { ): Promise<void> {
const { skipUndeletable = false } = options ?? {};
const creditsIds = uniq(castArray(vendorCreditIds)); const creditsIds = uniq(castArray(vendorCreditIds));
const results = await PromisePool.withConcurrency(1) const results = await PromisePool.withConcurrency(1)
.for(creditsIds) .for(creditsIds)
.process(async (vendorCreditId: number) => { .process(async (vendorCreditId: number) => {
await this.deleteVendorCreditService.deleteVendorCredit(vendorCreditId); try {
await this.deleteVendorCreditService.deleteVendorCredit(
vendorCreditId,
);
} catch (error) {
if (!skipUndeletable) {
throw error;
}
}
}); });
if (results.errors && results.errors.length > 0) { if (!skipUndeletable && results.errors && results.errors.length > 0) {
throw results.errors[0].raw; throw results.errors[0].raw;
} }
} }

View File

@@ -7,23 +7,82 @@ import {
Post, Post,
Put, Put,
Query, Query,
DefaultValuePipe,
ParseBoolPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { VendorCreditsApplicationService } from './VendorCreditsApplication.service'; import { VendorCreditsApplicationService } from './VendorCreditsApplication.service';
import { IVendorCreditsQueryDTO } from './types/VendorCredit.types'; import { IVendorCreditsQueryDTO } from './types/VendorCredit.types';
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import {
ApiExtraModels,
ApiOperation,
ApiQuery,
ApiResponse,
ApiTags,
getSchemaPath,
} from '@nestjs/swagger';
import { import {
CreateVendorCreditDto, CreateVendorCreditDto,
EditVendorCreditDto, EditVendorCreditDto,
} from './dtos/VendorCredit.dto'; } from './dtos/VendorCredit.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import {
BulkDeleteDto,
ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto';
@Controller('vendor-credits') @Controller('vendor-credits')
@ApiTags('Vendor Credits') @ApiTags('Vendor Credits')
@ApiCommonHeaders() @ApiCommonHeaders()
@ApiExtraModels(ValidateBulkDeleteResponseDto)
export class VendorCreditsController { export class VendorCreditsController {
constructor( constructor(
private readonly vendorCreditsApplication: VendorCreditsApplicationService, private readonly vendorCreditsApplication: VendorCreditsApplicationService,
) {} ) { }
@Post('validate-bulk-delete')
@ApiOperation({
summary:
'Validates which vendor credits can be deleted and returns the results.',
})
@ApiResponse({
status: 200,
description:
'Validation completed with counts and IDs of deletable and non-deletable vendor credits.',
schema: {
$ref: getSchemaPath(ValidateBulkDeleteResponseDto),
},
})
async validateBulkDeleteVendorCredits(
@Body() bulkDeleteDto: BulkDeleteDto,
): Promise<ValidateBulkDeleteResponseDto> {
return this.vendorCreditsApplication.validateBulkDeleteVendorCredits(
bulkDeleteDto.ids,
);
}
@Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple vendor credits.' })
@ApiQuery({
name: 'skip_undeletable',
required: false,
type: Boolean,
description:
'When true, undeletable vendor credits will be skipped and only deletable ones will be removed.',
})
@ApiResponse({
status: 200,
description: 'Vendor credits deleted successfully',
})
async bulkDeleteVendorCredits(
@Body() bulkDeleteDto: BulkDeleteDto,
@Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe)
skipUndeletable: boolean,
): Promise<void> {
return this.vendorCreditsApplication.bulkDeleteVendorCredits(
bulkDeleteDto.ids,
{ skipUndeletable },
);
}
@Post() @Post()
@ApiOperation({ summary: 'Create a new vendor credit.' }) @ApiOperation({ summary: 'Create a new vendor credit.' })

View File

@@ -28,6 +28,8 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module'; import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
import { VendorCreditsExportable } from './commands/VendorCreditsExportable'; import { VendorCreditsExportable } from './commands/VendorCreditsExportable';
import { VendorCreditsImportable } from './commands/VendorCreditsImportable'; import { VendorCreditsImportable } from './commands/VendorCreditsImportable';
import { BulkDeleteVendorCreditsService } from './BulkDeleteVendorCredits.service';
import { ValidateBulkDeleteVendorCreditsService } from './ValidateBulkDeleteVendorCredits.service';
@Module({ @Module({
imports: [ imports: [
@@ -61,6 +63,8 @@ import { VendorCreditsImportable } from './commands/VendorCreditsImportable';
VendorCreditAutoSerialSubscriber, VendorCreditAutoSerialSubscriber,
VendorCreditsExportable, VendorCreditsExportable,
VendorCreditsImportable, VendorCreditsImportable,
BulkDeleteVendorCreditsService,
ValidateBulkDeleteVendorCreditsService,
], ],
exports: [ exports: [
CreateVendorCreditService, CreateVendorCreditService,
@@ -74,6 +78,8 @@ import { VendorCreditsImportable } from './commands/VendorCreditsImportable';
OpenVendorCreditService, OpenVendorCreditService,
VendorCreditsExportable, VendorCreditsExportable,
VendorCreditsImportable, VendorCreditsImportable,
BulkDeleteVendorCreditsService,
ValidateBulkDeleteVendorCreditsService,
], ],
controllers: [VendorCreditsController], controllers: [VendorCreditsController],
}) })

View File

@@ -3,12 +3,21 @@ import { CreateVendorCreditService } from './commands/CreateVendorCredit.service
import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service'; import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service';
import { EditVendorCreditService } from './commands/EditVendorCredit.service'; import { EditVendorCreditService } from './commands/EditVendorCredit.service';
import { GetVendorCreditService } from './queries/GetVendorCredit.service'; import { GetVendorCreditService } from './queries/GetVendorCredit.service';
import { IVendorCreditEditDTO, IVendorCreditsQueryDTO } from './types/VendorCredit.types'; import {
IVendorCreditEditDTO,
IVendorCreditsQueryDTO,
} from './types/VendorCredit.types';
import { IVendorCreditCreateDTO } from './types/VendorCredit.types'; import { IVendorCreditCreateDTO } from './types/VendorCredit.types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { OpenVendorCreditService } from './commands/OpenVendorCredit.service'; import { OpenVendorCreditService } from './commands/OpenVendorCredit.service';
import { GetVendorCreditsService } from './queries/GetVendorCredits.service'; import { GetVendorCreditsService } from './queries/GetVendorCredits.service';
import { CreateVendorCreditDto, EditVendorCreditDto } from './dtos/VendorCredit.dto'; import {
CreateVendorCreditDto,
EditVendorCreditDto,
} from './dtos/VendorCredit.dto';
import { BulkDeleteVendorCreditsService } from './BulkDeleteVendorCredits.service';
import { ValidateBulkDeleteVendorCreditsService } from './ValidateBulkDeleteVendorCredits.service';
import { ValidateBulkDeleteResponseDto } from '@/common/dtos/BulkDelete.dto';
@Injectable() @Injectable()
export class VendorCreditsApplicationService { export class VendorCreditsApplicationService {
@@ -25,7 +34,9 @@ export class VendorCreditsApplicationService {
private readonly getVendorCreditService: GetVendorCreditService, private readonly getVendorCreditService: GetVendorCreditService,
private readonly openVendorCreditService: OpenVendorCreditService, private readonly openVendorCreditService: OpenVendorCreditService,
private readonly getVendorCreditsService: GetVendorCreditsService, private readonly getVendorCreditsService: GetVendorCreditsService,
) {} private readonly bulkDeleteVendorCreditsService: BulkDeleteVendorCreditsService,
private readonly validateBulkDeleteVendorCreditsService: ValidateBulkDeleteVendorCreditsService,
) { }
/** /**
* Creates a new vendor credit. * Creates a new vendor credit.
@@ -90,4 +101,22 @@ export class VendorCreditsApplicationService {
getVendorCredits(query: IVendorCreditsQueryDTO) { getVendorCredits(query: IVendorCreditsQueryDTO) {
return this.getVendorCreditsService.getVendorCredits(query); return this.getVendorCreditsService.getVendorCredits(query);
} }
bulkDeleteVendorCredits(
vendorCreditIds: number[],
options?: { skipUndeletable?: boolean },
) {
return this.bulkDeleteVendorCreditsService.bulkDeleteVendorCredits(
vendorCreditIds,
options,
);
}
validateBulkDeleteVendorCredits(
vendorCreditIds: number[],
): Promise<ValidateBulkDeleteResponseDto> {
return this.validateBulkDeleteVendorCreditsService.validateBulkDeleteVendorCredits(
vendorCreditIds,
);
}
} }