From 2b384b2f6f6e21c4647de28f98eb673a7645e65a Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 19 Nov 2025 22:59:21 +0200 Subject: [PATCH] wip --- .../modules/Accounts/Accounts.controller.ts | 15 ++++- .../Accounts/AccountsApplication.service.ts | 12 +++- .../Accounts/BulkDeleteAccounts.service.ts | 12 +++- .../src/modules/Bills/Bills.application.ts | 7 ++- .../src/modules/Bills/Bills.controller.ts | 20 +++++- .../modules/Bills/BulkDeleteBills.service.ts | 12 +++- .../BulkDeleteCreditNotes.service.ts | 12 +++- .../CreditNoteApplication.service.ts | 6 +- .../CreditNotes/CreditNotes.controller.ts | 19 +++++- .../Expenses/BulkDeleteExpenses.service.ts | 14 ++++- .../modules/Expenses/Expenses.controller.ts | 20 +++++- .../Expenses/ExpensesApplication.service.ts | 12 +++- .../BulkDeleteManualJournals.service.ts | 18 ++++-- .../ManualJournals.controller.ts | 13 ++++ .../ManualJournalsApplication.service.ts | 8 ++- .../BulkDeletePaymentReceived.service.ts | 18 ++++-- .../PaymentReceived.application.ts | 6 +- .../PaymentsReceived.controller.ts | 17 ++++- .../BulkDeleteSaleEstimates.service.ts | 12 +++- .../SaleEstimates.application.ts | 8 ++- .../SaleEstimates/SaleEstimates.controller.ts | 13 ++++ .../BulkDeleteSaleInvoices.service.ts | 14 +++-- .../SaleInvoices/SaleInvoices.application.ts | 8 ++- .../SaleInvoices/SaleInvoices.controller.ts | 16 ++++- .../BulkDeleteSaleReceipts.service.ts | 12 +++- .../SaleReceiptApplication.service.ts | 6 +- .../SaleReceipts/SaleReceipts.controller.ts | 17 ++++- .../BulkDeleteVendorCredits.service.ts | 16 ++++- .../VendorCredit/VendorCredits.controller.ts | 63 ++++++++++++++++++- .../VendorCredit/VendorCredits.module.ts | 6 ++ .../VendorCreditsApplication.service.ts | 35 ++++++++++- 31 files changed, 405 insertions(+), 62 deletions(-) diff --git a/packages/server/src/modules/Accounts/Accounts.controller.ts b/packages/server/src/modules/Accounts/Accounts.controller.ts index 656d2d80a..a58fa95af 100644 --- a/packages/server/src/modules/Accounts/Accounts.controller.ts +++ b/packages/server/src/modules/Accounts/Accounts.controller.ts @@ -7,6 +7,8 @@ import { Get, Query, ParseIntPipe, + DefaultValuePipe, + ParseBoolPipe, } from '@nestjs/common'; import { AccountsApplication } from './AccountsApplication.service'; import { CreateAccountDTO } from './CreateAccount.dto'; @@ -64,14 +66,25 @@ export class AccountsController { @Post('bulk-delete') @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({ status: 200, description: 'The accounts have been successfully deleted.', }) async bulkDeleteAccounts( @Body() bulkDeleteDto: BulkDeleteDto, + @Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe) + skipUndeletable: boolean, ): Promise { - return this.accountsApplication.bulkDeleteAccounts(bulkDeleteDto.ids); + return this.accountsApplication.bulkDeleteAccounts(bulkDeleteDto.ids, { + skipUndeletable, + }); } @Post() diff --git a/packages/server/src/modules/Accounts/AccountsApplication.service.ts b/packages/server/src/modules/Accounts/AccountsApplication.service.ts index b1a8b60e8..7eab22bfc 100644 --- a/packages/server/src/modules/Accounts/AccountsApplication.service.ts +++ b/packages/server/src/modules/Accounts/AccountsApplication.service.ts @@ -42,7 +42,7 @@ export class AccountsApplication { private readonly getAccountsService: GetAccountsService, private readonly bulkDeleteAccountsService: BulkDeleteAccountsService, private readonly validateBulkDeleteAccountsService: ValidateBulkDeleteAccountsService, - ) {} + ) { } /** * Creates a new account. @@ -148,7 +148,13 @@ export class AccountsApplication { /** * Deletes multiple accounts in bulk. */ - public bulkDeleteAccounts = (accountIds: number[]): Promise => { - return this.bulkDeleteAccountsService.bulkDeleteAccounts(accountIds); + public bulkDeleteAccounts = ( + accountIds: number[], + options?: { skipUndeletable?: boolean }, + ): Promise => { + return this.bulkDeleteAccountsService.bulkDeleteAccounts( + accountIds, + options, + ); }; } diff --git a/packages/server/src/modules/Accounts/BulkDeleteAccounts.service.ts b/packages/server/src/modules/Accounts/BulkDeleteAccounts.service.ts index cf65de70f..7f40c16f4 100644 --- a/packages/server/src/modules/Accounts/BulkDeleteAccounts.service.ts +++ b/packages/server/src/modules/Accounts/BulkDeleteAccounts.service.ts @@ -15,17 +15,25 @@ export class BulkDeleteAccountsService { */ async bulkDeleteAccounts( accountIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const accountsIds = uniq(castArray(accountIds)); const results = await PromisePool.withConcurrency(1) .for(accountsIds) .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; } } diff --git a/packages/server/src/modules/Bills/Bills.application.ts b/packages/server/src/modules/Bills/Bills.application.ts index a33f0adfd..207b400ee 100644 --- a/packages/server/src/modules/Bills/Bills.application.ts +++ b/packages/server/src/modules/Bills/Bills.application.ts @@ -61,8 +61,11 @@ export class BillsApplication { * Deletes multiple bills. * @param {number[]} billIds */ - public bulkDeleteBills(billIds: number[]) { - return this.bulkDeleteBillsService.bulkDeleteBills(billIds); + public bulkDeleteBills( + billIds: number[], + options?: { skipUndeletable?: boolean }, + ) { + return this.bulkDeleteBillsService.bulkDeleteBills(billIds, options); } /** diff --git a/packages/server/src/modules/Bills/Bills.controller.ts b/packages/server/src/modules/Bills/Bills.controller.ts index 3bb401f35..287b747b2 100644 --- a/packages/server/src/modules/Bills/Bills.controller.ts +++ b/packages/server/src/modules/Bills/Bills.controller.ts @@ -2,6 +2,7 @@ import { ApiExtraModels, ApiOperation, ApiParam, + ApiQuery, ApiResponse, ApiTags, getSchemaPath, @@ -15,6 +16,8 @@ import { Delete, Get, Query, + DefaultValuePipe, + ParseBoolPipe, } from '@nestjs/common'; import { BillsApplication } from './Bills.application'; import { IBillsFilter } from './Bills.types'; @@ -56,12 +59,25 @@ export class BillsController { @Post('bulk-delete') @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({ status: 200, description: 'Bills deleted successfully', }) - bulkDeleteBills(@Body() bulkDeleteDto: BulkDeleteDto): Promise { - return this.billsApplication.bulkDeleteBills(bulkDeleteDto.ids); + bulkDeleteBills( + @Body() bulkDeleteDto: BulkDeleteDto, + @Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe) + skipUndeletable: boolean, + ): Promise { + return this.billsApplication.bulkDeleteBills(bulkDeleteDto.ids, { + skipUndeletable, + }); } @Post() diff --git a/packages/server/src/modules/Bills/BulkDeleteBills.service.ts b/packages/server/src/modules/Bills/BulkDeleteBills.service.ts index dfc71dc2a..338c740ce 100644 --- a/packages/server/src/modules/Bills/BulkDeleteBills.service.ts +++ b/packages/server/src/modules/Bills/BulkDeleteBills.service.ts @@ -10,17 +10,25 @@ export class BulkDeleteBillsService { async bulkDeleteBills( billIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const billsIds = uniq(castArray(billIds)); const results = await PromisePool.withConcurrency(1) .for(billsIds) .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; } } diff --git a/packages/server/src/modules/CreditNotes/BulkDeleteCreditNotes.service.ts b/packages/server/src/modules/CreditNotes/BulkDeleteCreditNotes.service.ts index ec226d27d..a5ea6a717 100644 --- a/packages/server/src/modules/CreditNotes/BulkDeleteCreditNotes.service.ts +++ b/packages/server/src/modules/CreditNotes/BulkDeleteCreditNotes.service.ts @@ -12,17 +12,25 @@ export class BulkDeleteCreditNotesService { async bulkDeleteCreditNotes( creditNoteIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const notesIds = uniq(castArray(creditNoteIds)); const results = await PromisePool.withConcurrency(1) .for(notesIds) .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; } } diff --git a/packages/server/src/modules/CreditNotes/CreditNoteApplication.service.ts b/packages/server/src/modules/CreditNotes/CreditNoteApplication.service.ts index 5eb4009b1..472508f92 100644 --- a/packages/server/src/modules/CreditNotes/CreditNoteApplication.service.ts +++ b/packages/server/src/modules/CreditNotes/CreditNoteApplication.service.ts @@ -107,9 +107,13 @@ export class CreditNoteApplication { * @param {number[]} creditNoteIds * @returns {Promise} */ - bulkDeleteCreditNotes(creditNoteIds: number[]) { + bulkDeleteCreditNotes( + creditNoteIds: number[], + options?: { skipUndeletable?: boolean }, + ) { return this.bulkDeleteCreditNotesService.bulkDeleteCreditNotes( creditNoteIds, + options, ); } diff --git a/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts b/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts index 3e4835334..603709042 100644 --- a/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts +++ b/packages/server/src/modules/CreditNotes/CreditNotes.controller.ts @@ -3,6 +3,7 @@ import { ApiOperation, ApiResponse, ApiParam, + ApiQuery, ApiExtraModels, getSchemaPath, } from '@nestjs/swagger'; @@ -11,7 +12,9 @@ import { Controller, Delete, Get, + DefaultValuePipe, Param, + ParseBoolPipe, Post, Put, Query, @@ -37,7 +40,7 @@ export class CreditNotesController { /** * @param {CreditNoteApplication} creditNoteApplication - The credit note application service. */ - constructor(private creditNoteApplication: CreditNoteApplication) {} + constructor(private creditNoteApplication: CreditNoteApplication) { } @Post() @ApiOperation({ summary: 'Create a new credit note' }) @@ -140,13 +143,25 @@ export class CreditNotesController { @Post('bulk-delete') @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({ status: 200, description: 'Credit notes deleted successfully', }) - bulkDeleteCreditNotes(@Body() bulkDeleteDto: BulkDeleteDto): Promise { + bulkDeleteCreditNotes( + @Body() bulkDeleteDto: BulkDeleteDto, + @Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe) + skipUndeletable: boolean, + ): Promise { return this.creditNoteApplication.bulkDeleteCreditNotes( bulkDeleteDto.ids, + { skipUndeletable }, ); } diff --git a/packages/server/src/modules/Expenses/BulkDeleteExpenses.service.ts b/packages/server/src/modules/Expenses/BulkDeleteExpenses.service.ts index 59dca523b..7e62444c4 100644 --- a/packages/server/src/modules/Expenses/BulkDeleteExpenses.service.ts +++ b/packages/server/src/modules/Expenses/BulkDeleteExpenses.service.ts @@ -6,21 +6,29 @@ import { DeleteExpense } from './commands/DeleteExpense.service'; @Injectable() export class BulkDeleteExpensesService { - constructor(private readonly deleteExpenseService: DeleteExpense) {} + constructor(private readonly deleteExpenseService: DeleteExpense) { } async bulkDeleteExpenses( expenseIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const expensesIds = uniq(castArray(expenseIds)); const results = await PromisePool.withConcurrency(1) .for(expensesIds) .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; } } diff --git a/packages/server/src/modules/Expenses/Expenses.controller.ts b/packages/server/src/modules/Expenses/Expenses.controller.ts index 8ee22ffb3..3e55bafc3 100644 --- a/packages/server/src/modules/Expenses/Expenses.controller.ts +++ b/packages/server/src/modules/Expenses/Expenses.controller.ts @@ -7,12 +7,15 @@ import { Post, Put, Query, + DefaultValuePipe, + ParseBoolPipe, } from '@nestjs/common'; import { ExpensesApplication } from './ExpensesApplication.service'; import { IExpensesFilter } from './Expenses.types'; import { ApiExtraModels, ApiOperation, + ApiQuery, ApiResponse, ApiTags, getSchemaPath, @@ -59,12 +62,25 @@ export class ExpensesController { @Post('bulk-delete') @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({ status: 200, description: 'Expenses deleted successfully', }) - public bulkDeleteExpenses(@Body() bulkDeleteDto: BulkDeleteDto) { - return this.expensesApplication.bulkDeleteExpenses(bulkDeleteDto.ids); + public bulkDeleteExpenses( + @Body() bulkDeleteDto: BulkDeleteDto, + @Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe) + skipUndeletable: boolean, + ) { + return this.expensesApplication.bulkDeleteExpenses(bulkDeleteDto.ids, { + skipUndeletable, + }); } /** diff --git a/packages/server/src/modules/Expenses/ExpensesApplication.service.ts b/packages/server/src/modules/Expenses/ExpensesApplication.service.ts index a0b08ce45..aa89c99a5 100644 --- a/packages/server/src/modules/Expenses/ExpensesApplication.service.ts +++ b/packages/server/src/modules/Expenses/ExpensesApplication.service.ts @@ -21,7 +21,7 @@ export class ExpensesApplication { private readonly getExpensesService: GetExpensesService, private readonly bulkDeleteExpensesService: BulkDeleteExpensesService, private readonly validateBulkDeleteExpensesService: ValidateBulkDeleteExpensesService, - ) {} + ) { } /** * Create a new expense transaction. @@ -55,8 +55,14 @@ export class ExpensesApplication { * Deletes expenses in bulk. * @param {number[]} expenseIds - Expense ids. */ - public bulkDeleteExpenses(expenseIds: number[]) { - return this.bulkDeleteExpensesService.bulkDeleteExpenses(expenseIds); + public bulkDeleteExpenses( + expenseIds: number[], + options?: { skipUndeletable?: boolean }, + ) { + return this.bulkDeleteExpensesService.bulkDeleteExpenses( + expenseIds, + options, + ); } /** diff --git a/packages/server/src/modules/ManualJournals/BulkDeleteManualJournals.service.ts b/packages/server/src/modules/ManualJournals/BulkDeleteManualJournals.service.ts index f4a49ea90..4684aea65 100644 --- a/packages/server/src/modules/ManualJournals/BulkDeleteManualJournals.service.ts +++ b/packages/server/src/modules/ManualJournals/BulkDeleteManualJournals.service.ts @@ -8,23 +8,31 @@ import { DeleteManualJournalService } from './commands/DeleteManualJournal.servi export class BulkDeleteManualJournalsService { constructor( private readonly deleteManualJournalService: DeleteManualJournalService, - ) {} + ) { } async bulkDeleteManualJournals( manualJournalIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const journalsIds = uniq(castArray(manualJournalIds)); const results = await PromisePool.withConcurrency(1) .for(journalsIds) .process(async (manualJournalId: number) => { - await this.deleteManualJournalService.deleteManualJournal( - manualJournalId, - ); + try { + 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; } } diff --git a/packages/server/src/modules/ManualJournals/ManualJournals.controller.ts b/packages/server/src/modules/ManualJournals/ManualJournals.controller.ts index b2baf7ee2..ebdabee2a 100644 --- a/packages/server/src/modules/ManualJournals/ManualJournals.controller.ts +++ b/packages/server/src/modules/ManualJournals/ManualJournals.controller.ts @@ -8,12 +8,15 @@ import { Post, Put, Query, + DefaultValuePipe, + ParseBoolPipe, } from '@nestjs/common'; import { ManualJournalsApplication } from './ManualJournalsApplication.service'; import { ApiExtraModels, ApiOperation, ApiParam, + ApiQuery, ApiResponse, ApiTags, getSchemaPath, @@ -61,15 +64,25 @@ export class ManualJournalsController { @Post('bulk-delete') @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({ status: 200, description: 'Manual journals deleted successfully', }) public bulkDeleteManualJournals( @Body() bulkDeleteDto: BulkDeleteDto, + @Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe) + skipUndeletable: boolean, ): Promise { return this.manualJournalsApplication.bulkDeleteManualJournals( bulkDeleteDto.ids, + { skipUndeletable }, ); } diff --git a/packages/server/src/modules/ManualJournals/ManualJournalsApplication.service.ts b/packages/server/src/modules/ManualJournals/ManualJournalsApplication.service.ts index 5f956af79..164bfd943 100644 --- a/packages/server/src/modules/ManualJournals/ManualJournalsApplication.service.ts +++ b/packages/server/src/modules/ManualJournals/ManualJournalsApplication.service.ts @@ -25,7 +25,7 @@ export class ManualJournalsApplication { private getManualJournalsService: GetManualJournals, private bulkDeleteManualJournalsService: BulkDeleteManualJournalsService, private validateBulkDeleteManualJournalsService: ValidateBulkDeleteManualJournalsService, - ) {} + ) { } /** * Make journal entries. @@ -65,9 +65,13 @@ export class ManualJournalsApplication { * Bulk deletes manual journals. * @param {number[]} manualJournalIds */ - public bulkDeleteManualJournals = (manualJournalIds: number[]) => { + public bulkDeleteManualJournals = ( + manualJournalIds: number[], + options?: { skipUndeletable?: boolean }, + ) => { return this.bulkDeleteManualJournalsService.bulkDeleteManualJournals( manualJournalIds, + options, ); }; diff --git a/packages/server/src/modules/PaymentReceived/BulkDeletePaymentReceived.service.ts b/packages/server/src/modules/PaymentReceived/BulkDeletePaymentReceived.service.ts index cb0f69348..c36f41a00 100644 --- a/packages/server/src/modules/PaymentReceived/BulkDeletePaymentReceived.service.ts +++ b/packages/server/src/modules/PaymentReceived/BulkDeletePaymentReceived.service.ts @@ -8,23 +8,31 @@ import { DeletePaymentReceivedService } from './commands/DeletePaymentReceived.s export class BulkDeletePaymentReceivedService { constructor( private readonly deletePaymentReceivedService: DeletePaymentReceivedService, - ) {} + ) { } async bulkDeletePaymentReceived( paymentReceiveIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const paymentsIds = uniq(castArray(paymentReceiveIds)); const results = await PromisePool.withConcurrency(1) .for(paymentsIds) .process(async (paymentReceiveId: number) => { - await this.deletePaymentReceivedService.deletePaymentReceive( - paymentReceiveId, - ); + try { + 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; } } diff --git a/packages/server/src/modules/PaymentReceived/PaymentReceived.application.ts b/packages/server/src/modules/PaymentReceived/PaymentReceived.application.ts index 61ea93f12..32780174d 100644 --- a/packages/server/src/modules/PaymentReceived/PaymentReceived.application.ts +++ b/packages/server/src/modules/PaymentReceived/PaymentReceived.application.ts @@ -81,9 +81,13 @@ export class PaymentReceivesApplication { * Deletes multiple payment receives. * @param {number[]} paymentReceiveIds */ - public bulkDeletePaymentReceives(paymentReceiveIds: number[]) { + public bulkDeletePaymentReceives( + paymentReceiveIds: number[], + options?: { skipUndeletable?: boolean }, + ) { return this.bulkDeletePaymentReceivedService.bulkDeletePaymentReceived( paymentReceiveIds, + options, ); } diff --git a/packages/server/src/modules/PaymentReceived/PaymentsReceived.controller.ts b/packages/server/src/modules/PaymentReceived/PaymentsReceived.controller.ts index 31fb9c87b..3ec616ad4 100644 --- a/packages/server/src/modules/PaymentReceived/PaymentsReceived.controller.ts +++ b/packages/server/src/modules/PaymentReceived/PaymentsReceived.controller.ts @@ -1,6 +1,7 @@ import { ApiExtraModels, ApiOperation, + ApiQuery, ApiResponse, ApiTags, getSchemaPath, @@ -13,10 +14,12 @@ import { Headers, HttpCode, Param, + ParseBoolPipe, ParseIntPipe, Post, Put, Query, + DefaultValuePipe, } from '@nestjs/common'; import { PaymentReceivesApplication } from './PaymentReceived.application'; import { @@ -171,13 +174,25 @@ export class PaymentReceivesController { @Post('bulk-delete') @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({ status: 200, 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( bulkDeleteDto.ids, + { skipUndeletable }, ); } diff --git a/packages/server/src/modules/SaleEstimates/BulkDeleteSaleEstimates.service.ts b/packages/server/src/modules/SaleEstimates/BulkDeleteSaleEstimates.service.ts index a122c69ff..e5dc864e5 100644 --- a/packages/server/src/modules/SaleEstimates/BulkDeleteSaleEstimates.service.ts +++ b/packages/server/src/modules/SaleEstimates/BulkDeleteSaleEstimates.service.ts @@ -10,17 +10,25 @@ export class BulkDeleteSaleEstimatesService { async bulkDeleteSaleEstimates( saleEstimateIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const estimatesIds = uniq(castArray(saleEstimateIds)); const results = await PromisePool.withConcurrency(1) .for(estimatesIds) .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; } } diff --git a/packages/server/src/modules/SaleEstimates/SaleEstimates.application.ts b/packages/server/src/modules/SaleEstimates/SaleEstimates.application.ts index 818e53e3b..f81042a8d 100644 --- a/packages/server/src/modules/SaleEstimates/SaleEstimates.application.ts +++ b/packages/server/src/modules/SaleEstimates/SaleEstimates.application.ts @@ -39,7 +39,7 @@ export class SaleEstimatesApplication { private readonly getSaleEstimateMailStateService: GetSaleEstimateMailStateService, private readonly bulkDeleteSaleEstimatesService: BulkDeleteSaleEstimatesService, private readonly validateBulkDeleteSaleEstimatesService: ValidateBulkDeleteSaleEstimatesService, - ) {} + ) { } /** * Create a sale estimate. @@ -77,9 +77,13 @@ export class SaleEstimatesApplication { * @param {number[]} saleEstimateIds * @return {Promise} */ - public bulkDeleteSaleEstimates(saleEstimateIds: number[]) { + public bulkDeleteSaleEstimates( + saleEstimateIds: number[], + options?: { skipUndeletable?: boolean }, + ) { return this.bulkDeleteSaleEstimatesService.bulkDeleteSaleEstimates( saleEstimateIds, + options, ); } diff --git a/packages/server/src/modules/SaleEstimates/SaleEstimates.controller.ts b/packages/server/src/modules/SaleEstimates/SaleEstimates.controller.ts index c1d3ac74e..7565de7e3 100644 --- a/packages/server/src/modules/SaleEstimates/SaleEstimates.controller.ts +++ b/packages/server/src/modules/SaleEstimates/SaleEstimates.controller.ts @@ -2,6 +2,7 @@ import { ApiExtraModels, ApiOperation, ApiParam, + ApiQuery, ApiResponse, ApiTags, getSchemaPath, @@ -11,9 +12,11 @@ import { Controller, Delete, Get, + DefaultValuePipe, Headers, HttpCode, Param, + ParseBoolPipe, ParseIntPipe, Post, Put, @@ -72,15 +75,25 @@ export class SaleEstimatesController { @Post('bulk-delete') @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({ status: 200, description: 'Sale estimates deleted successfully', }) public bulkDeleteSaleEstimates( @Body() bulkDeleteDto: BulkDeleteDto, + @Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe) + skipUndeletable: boolean, ): Promise { return this.saleEstimatesApplication.bulkDeleteSaleEstimates( bulkDeleteDto.ids, + { skipUndeletable }, ); } diff --git a/packages/server/src/modules/SaleInvoices/BulkDeleteSaleInvoices.service.ts b/packages/server/src/modules/SaleInvoices/BulkDeleteSaleInvoices.service.ts index cb29c222a..142634787 100644 --- a/packages/server/src/modules/SaleInvoices/BulkDeleteSaleInvoices.service.ts +++ b/packages/server/src/modules/SaleInvoices/BulkDeleteSaleInvoices.service.ts @@ -10,19 +10,25 @@ export class BulkDeleteSaleInvoicesService { async bulkDeleteSaleInvoices( saleInvoiceIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const invoicesIds = uniq(castArray(saleInvoiceIds)); const results = await PromisePool.withConcurrency(1) .for(invoicesIds) .process(async (saleInvoiceId: number) => { - await this.deleteSaleInvoiceService.deleteSaleInvoice(saleInvoiceId); + try { + await this.deleteSaleInvoiceService.deleteSaleInvoice(saleInvoiceId); + } 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; } } } - diff --git a/packages/server/src/modules/SaleInvoices/SaleInvoices.application.ts b/packages/server/src/modules/SaleInvoices/SaleInvoices.application.ts index eee2da24f..0cd0535ab 100644 --- a/packages/server/src/modules/SaleInvoices/SaleInvoices.application.ts +++ b/packages/server/src/modules/SaleInvoices/SaleInvoices.application.ts @@ -45,7 +45,7 @@ export class SaleInvoiceApplication { private generateShareLinkService: GenerateShareLink, private bulkDeleteSaleInvoicesService: BulkDeleteSaleInvoicesService, private validateBulkDeleteSaleInvoicesService: ValidateBulkDeleteSaleInvoicesService, - ) {} + ) { } /** * Creates a new sale invoice with associated GL entries. @@ -87,9 +87,13 @@ export class SaleInvoiceApplication { * @param {number[]} saleInvoiceIds * @return {Promise} */ - public bulkDeleteSaleInvoices(saleInvoiceIds: number[]) { + public bulkDeleteSaleInvoices( + saleInvoiceIds: number[], + options?: { skipUndeletable?: boolean }, + ) { return this.bulkDeleteSaleInvoicesService.bulkDeleteSaleInvoices( saleInvoiceIds, + options, ); } diff --git a/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts b/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts index ce40a3ef5..c38d8caed 100644 --- a/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts +++ b/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts @@ -2,11 +2,13 @@ import { Response } from 'express'; import { Body, Controller, + DefaultValuePipe, Delete, Get, Headers, HttpCode, Param, + ParseBoolPipe, ParseIntPipe, Post, Put, @@ -78,13 +80,25 @@ export class SaleInvoicesController { @Post('bulk-delete') @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({ status: 200, description: 'Sale invoices deleted successfully', }) - bulkDeleteSaleInvoices(@Body() bulkDeleteDto: BulkDeleteDto): Promise { + bulkDeleteSaleInvoices( + @Body() bulkDeleteDto: BulkDeleteDto, + @Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe) + skipUndeletable: boolean, + ): Promise { return this.saleInvoiceApplication.bulkDeleteSaleInvoices( bulkDeleteDto.ids, + { skipUndeletable }, ); } diff --git a/packages/server/src/modules/SaleReceipts/BulkDeleteSaleReceipts.service.ts b/packages/server/src/modules/SaleReceipts/BulkDeleteSaleReceipts.service.ts index 6417e5b72..04c605608 100644 --- a/packages/server/src/modules/SaleReceipts/BulkDeleteSaleReceipts.service.ts +++ b/packages/server/src/modules/SaleReceipts/BulkDeleteSaleReceipts.service.ts @@ -12,17 +12,25 @@ export class BulkDeleteSaleReceiptsService { async bulkDeleteSaleReceipts( saleReceiptIds: number | number[], + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const receiptIds = uniq(castArray(saleReceiptIds)); const results = await PromisePool.withConcurrency(1) .for(receiptIds) .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; } } diff --git a/packages/server/src/modules/SaleReceipts/SaleReceiptApplication.service.ts b/packages/server/src/modules/SaleReceipts/SaleReceiptApplication.service.ts index 5782ce896..9b97def9a 100644 --- a/packages/server/src/modules/SaleReceipts/SaleReceiptApplication.service.ts +++ b/packages/server/src/modules/SaleReceipts/SaleReceiptApplication.service.ts @@ -93,9 +93,13 @@ export class SaleReceiptApplication { * Deletes multiple sale receipts. * @param {number[]} saleReceiptIds */ - public async bulkDeleteSaleReceipts(saleReceiptIds: number[]) { + public async bulkDeleteSaleReceipts( + saleReceiptIds: number[], + options?: { skipUndeletable?: boolean }, + ) { return this.bulkDeleteSaleReceiptsService.bulkDeleteSaleReceipts( saleReceiptIds, + options, ); } diff --git a/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts b/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts index 90398c23a..b671ee4c3 100644 --- a/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts +++ b/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts @@ -6,6 +6,8 @@ import { Headers, HttpCode, Param, + ParseBoolPipe, + DefaultValuePipe, ParseIntPipe, Post, Put, @@ -17,6 +19,7 @@ import { ApiExtraModels, ApiOperation, ApiParam, + ApiQuery, ApiResponse, ApiTags, getSchemaPath, @@ -70,13 +73,25 @@ export class SaleReceiptsController { @Post('bulk-delete') @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({ status: 200, description: 'Sale receipts deleted successfully', }) - bulkDeleteSaleReceipts(@Body() bulkDeleteDto: BulkDeleteDto): Promise { + bulkDeleteSaleReceipts( + @Body() bulkDeleteDto: BulkDeleteDto, + @Query('skip_undeletable', new DefaultValuePipe(false), ParseBoolPipe) + skipUndeletable: boolean, + ): Promise { return this.saleReceiptApplication.bulkDeleteSaleReceipts( bulkDeleteDto.ids, + { skipUndeletable }, ); } diff --git a/packages/server/src/modules/VendorCredit/BulkDeleteVendorCredits.service.ts b/packages/server/src/modules/VendorCredit/BulkDeleteVendorCredits.service.ts index 987905af3..a77e6b818 100644 --- a/packages/server/src/modules/VendorCredit/BulkDeleteVendorCredits.service.ts +++ b/packages/server/src/modules/VendorCredit/BulkDeleteVendorCredits.service.ts @@ -8,21 +8,31 @@ import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service export class BulkDeleteVendorCreditsService { constructor( private readonly deleteVendorCreditService: DeleteVendorCreditService, - ) {} + ) { } async bulkDeleteVendorCredits( vendorCreditIds: number | Array, + options?: { skipUndeletable?: boolean }, trx?: Knex.Transaction, ): Promise { + const { skipUndeletable = false } = options ?? {}; const creditsIds = uniq(castArray(vendorCreditIds)); const results = await PromisePool.withConcurrency(1) .for(creditsIds) .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; } } diff --git a/packages/server/src/modules/VendorCredit/VendorCredits.controller.ts b/packages/server/src/modules/VendorCredit/VendorCredits.controller.ts index 2f0c85235..cd03119e1 100644 --- a/packages/server/src/modules/VendorCredit/VendorCredits.controller.ts +++ b/packages/server/src/modules/VendorCredit/VendorCredits.controller.ts @@ -7,23 +7,82 @@ import { Post, Put, Query, + DefaultValuePipe, + ParseBoolPipe, } from '@nestjs/common'; import { VendorCreditsApplicationService } from './VendorCreditsApplication.service'; import { IVendorCreditsQueryDTO } from './types/VendorCredit.types'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { + ApiExtraModels, + ApiOperation, + ApiQuery, + ApiResponse, + ApiTags, + getSchemaPath, +} from '@nestjs/swagger'; import { CreateVendorCreditDto, EditVendorCreditDto, } from './dtos/VendorCredit.dto'; import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; +import { + BulkDeleteDto, + ValidateBulkDeleteResponseDto, +} from '@/common/dtos/BulkDelete.dto'; @Controller('vendor-credits') @ApiTags('Vendor Credits') @ApiCommonHeaders() +@ApiExtraModels(ValidateBulkDeleteResponseDto) export class VendorCreditsController { constructor( 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 { + 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 { + return this.vendorCreditsApplication.bulkDeleteVendorCredits( + bulkDeleteDto.ids, + { skipUndeletable }, + ); + } @Post() @ApiOperation({ summary: 'Create a new vendor credit.' }) diff --git a/packages/server/src/modules/VendorCredit/VendorCredits.module.ts b/packages/server/src/modules/VendorCredit/VendorCredits.module.ts index 58338b8d8..ff9e9365a 100644 --- a/packages/server/src/modules/VendorCredit/VendorCredits.module.ts +++ b/packages/server/src/modules/VendorCredit/VendorCredits.module.ts @@ -28,6 +28,8 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { InventoryCostModule } from '../InventoryCost/InventoryCost.module'; import { VendorCreditsExportable } from './commands/VendorCreditsExportable'; import { VendorCreditsImportable } from './commands/VendorCreditsImportable'; +import { BulkDeleteVendorCreditsService } from './BulkDeleteVendorCredits.service'; +import { ValidateBulkDeleteVendorCreditsService } from './ValidateBulkDeleteVendorCredits.service'; @Module({ imports: [ @@ -61,6 +63,8 @@ import { VendorCreditsImportable } from './commands/VendorCreditsImportable'; VendorCreditAutoSerialSubscriber, VendorCreditsExportable, VendorCreditsImportable, + BulkDeleteVendorCreditsService, + ValidateBulkDeleteVendorCreditsService, ], exports: [ CreateVendorCreditService, @@ -74,6 +78,8 @@ import { VendorCreditsImportable } from './commands/VendorCreditsImportable'; OpenVendorCreditService, VendorCreditsExportable, VendorCreditsImportable, + BulkDeleteVendorCreditsService, + ValidateBulkDeleteVendorCreditsService, ], controllers: [VendorCreditsController], }) diff --git a/packages/server/src/modules/VendorCredit/VendorCreditsApplication.service.ts b/packages/server/src/modules/VendorCredit/VendorCreditsApplication.service.ts index 47b709f2e..6d0a1003d 100644 --- a/packages/server/src/modules/VendorCredit/VendorCreditsApplication.service.ts +++ b/packages/server/src/modules/VendorCredit/VendorCreditsApplication.service.ts @@ -3,12 +3,21 @@ import { CreateVendorCreditService } from './commands/CreateVendorCredit.service import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service'; import { EditVendorCreditService } from './commands/EditVendorCredit.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 { Injectable } from '@nestjs/common'; import { OpenVendorCreditService } from './commands/OpenVendorCredit.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() export class VendorCreditsApplicationService { @@ -25,7 +34,9 @@ export class VendorCreditsApplicationService { private readonly getVendorCreditService: GetVendorCreditService, private readonly openVendorCreditService: OpenVendorCreditService, private readonly getVendorCreditsService: GetVendorCreditsService, - ) {} + private readonly bulkDeleteVendorCreditsService: BulkDeleteVendorCreditsService, + private readonly validateBulkDeleteVendorCreditsService: ValidateBulkDeleteVendorCreditsService, + ) { } /** * Creates a new vendor credit. @@ -90,4 +101,22 @@ export class VendorCreditsApplicationService { getVendorCredits(query: IVendorCreditsQueryDTO) { return this.getVendorCreditsService.getVendorCredits(query); } + + bulkDeleteVendorCredits( + vendorCreditIds: number[], + options?: { skipUndeletable?: boolean }, + ) { + return this.bulkDeleteVendorCreditsService.bulkDeleteVendorCredits( + vendorCreditIds, + options, + ); + } + + validateBulkDeleteVendorCredits( + vendorCreditIds: number[], + ): Promise { + return this.validateBulkDeleteVendorCreditsService.validateBulkDeleteVendorCredits( + vendorCreditIds, + ); + } }