This commit is contained in:
Ahmed Bouhuolia
2025-11-17 17:04:25 +02:00
parent 2383091b6e
commit 2c64e1b8ab
41 changed files with 709 additions and 87 deletions

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { PromisePool } from '@supercharge/promise-pool';
import { castArray, uniq } from 'lodash';
import { DeleteSaleReceipt } from './commands/DeleteSaleReceipt.service';
@Injectable()
export class BulkDeleteSaleReceiptsService {
constructor(
private readonly deleteSaleReceiptService: DeleteSaleReceipt,
) { }
async bulkDeleteSaleReceipts(
saleReceiptIds: number | number[],
trx?: Knex.Transaction,
): Promise<void> {
const receiptIds = uniq(castArray(saleReceiptIds));
const results = await PromisePool.withConcurrency(1)
.for(receiptIds)
.process(async (saleReceiptId: number) => {
await this.deleteSaleReceiptService.deleteSaleReceipt(saleReceiptId);
});
if (results.errors && results.errors.length > 0) {
throw results.errors[0].raw;
}
}
}

View File

@@ -22,6 +22,8 @@ import {
EditSaleReceiptDto,
} from './dtos/SaleReceipt.dto';
import { GetSaleReceiptMailStateService } from './queries/GetSaleReceiptMailState.service';
import { BulkDeleteSaleReceiptsService } from './BulkDeleteSaleReceipts.service';
import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleReceipts.service';
@Injectable()
export class SaleReceiptApplication {
@@ -36,7 +38,9 @@ export class SaleReceiptApplication {
private getSaleReceiptStateService: GetSaleReceiptState,
private saleReceiptNotifyByMailService: SaleReceiptMailNotification,
private getSaleReceiptMailStateService: GetSaleReceiptMailStateService,
) {}
private bulkDeleteSaleReceiptsService: BulkDeleteSaleReceiptsService,
private validateBulkDeleteSaleReceiptsService: ValidateBulkDeleteSaleReceiptsService,
) { }
/**
* Creates a new sale receipt with associated entries.
@@ -85,6 +89,26 @@ export class SaleReceiptApplication {
return this.deleteSaleReceiptService.deleteSaleReceipt(saleReceiptId);
}
/**
* Deletes multiple sale receipts.
* @param {number[]} saleReceiptIds
*/
public async bulkDeleteSaleReceipts(saleReceiptIds: number[]) {
return this.bulkDeleteSaleReceiptsService.bulkDeleteSaleReceipts(
saleReceiptIds,
);
}
/**
* Validates which sale receipts can be deleted.
* @param {number[]} saleReceiptIds
*/
public async validateBulkDeleteSaleReceipts(saleReceiptIds: number[]) {
return this.validateBulkDeleteSaleReceiptsService.validateBulkDeleteSaleReceipts(
saleReceiptIds,
);
}
/**
* Retrieve sales receipts paginated and filterable list.
* @param {ISalesReceiptsFilter} filterDTO

View File

@@ -32,6 +32,10 @@ import { SaleReceiptResponseDto } from './dtos/SaleReceiptResponse.dto';
import { PaginatedResponseDto } from '@/common/dtos/PaginatedResults.dto';
import { SaleReceiptStateResponseDto } from './dtos/SaleReceiptState.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import {
BulkDeleteDto,
ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto';
@Controller('sale-receipts')
@ApiTags('Sale Receipts')
@@ -39,8 +43,42 @@ import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
@ApiExtraModels(PaginatedResponseDto)
@ApiExtraModels(SaleReceiptStateResponseDto)
@ApiCommonHeaders()
@ApiExtraModels(ValidateBulkDeleteResponseDto)
export class SaleReceiptsController {
constructor(private saleReceiptApplication: SaleReceiptApplication) {}
constructor(private saleReceiptApplication: SaleReceiptApplication) { }
@Post('validate-bulk-delete')
@ApiOperation({
summary:
'Validates which sale receipts can be deleted and returns the results.',
})
@ApiResponse({
status: 200,
description:
'Validation completed with counts and IDs of deletable and non-deletable sale receipts.',
schema: {
$ref: getSchemaPath(ValidateBulkDeleteResponseDto),
},
})
validateBulkDeleteSaleReceipts(
@Body() bulkDeleteDto: BulkDeleteDto,
): Promise<ValidateBulkDeleteResponseDto> {
return this.saleReceiptApplication.validateBulkDeleteSaleReceipts(
bulkDeleteDto.ids,
);
}
@Post('bulk-delete')
@ApiOperation({ summary: 'Deletes multiple sale receipts.' })
@ApiResponse({
status: 200,
description: 'Sale receipts deleted successfully',
})
bulkDeleteSaleReceipts(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> {
return this.saleReceiptApplication.bulkDeleteSaleReceipts(
bulkDeleteDto.ids,
);
}
@Post()
@ApiOperation({ summary: 'Create a new sale receipt.' })

View File

@@ -40,6 +40,8 @@ import { SaleReceiptsImportable } from './commands/SaleReceiptsImportable';
import { GetSaleReceiptMailStateService } from './queries/GetSaleReceiptMailState.service';
import { GetSaleReceiptMailTemplateService } from './queries/GetSaleReceiptMailTemplate.service';
import { SaleReceiptAutoIncrementSubscriber } from './subscribers/SaleReceiptAutoIncrementSubscriber';
import { BulkDeleteSaleReceiptsService } from './BulkDeleteSaleReceipts.service';
import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleReceipts.service';
@Module({
controllers: [SaleReceiptsController],
@@ -85,6 +87,8 @@ import { SaleReceiptAutoIncrementSubscriber } from './subscribers/SaleReceiptAut
GetSaleReceiptMailStateService,
GetSaleReceiptMailTemplateService,
SaleReceiptAutoIncrementSubscriber,
BulkDeleteSaleReceiptsService,
ValidateBulkDeleteSaleReceiptsService,
],
})
export class SaleReceiptsModule { }

View File

@@ -0,0 +1,54 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
import { DeleteSaleReceipt } from './commands/DeleteSaleReceipt.service';
@Injectable()
export class ValidateBulkDeleteSaleReceiptsService {
constructor(
private readonly deleteSaleReceiptService: DeleteSaleReceipt,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex,
) {}
public async validateBulkDeleteSaleReceipts(
saleReceiptIds: 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 saleReceiptId of saleReceiptIds) {
try {
await this.deleteSaleReceiptService.deleteSaleReceipt(saleReceiptId);
deletableIds.push(saleReceiptId);
} catch (error) {
nonDeletableIds.push(saleReceiptId);
}
}
await trx.rollback();
return {
deletableCount: deletableIds.length,
nonDeletableCount: nonDeletableIds.length,
deletableIds,
nonDeletableIds,
};
} catch (error) {
await trx.rollback();
throw error;
}
}
}