fix: printing sale receipts

This commit is contained in:
Ahmed Bouhuolia
2025-11-25 23:46:41 +02:00
parent 234b1804b3
commit cd4816aa3b
6 changed files with 48 additions and 23 deletions

View File

@@ -1,3 +1,4 @@
import { Response } from 'express';
import { import {
ApiTags, ApiTags,
ApiOperation, ApiOperation,
@@ -11,10 +12,12 @@ import {
Controller, Controller,
Delete, Delete,
Get, Get,
Headers,
Param, Param,
Post, Post,
Put, Put,
Query, Query,
Res,
} from '@nestjs/common'; } from '@nestjs/common';
import { CreditNoteApplication } from './CreditNoteApplication.service'; import { CreditNoteApplication } from './CreditNoteApplication.service';
import { ICreditNotesQueryDTO } from './types/CreditNotes.types'; import { ICreditNotesQueryDTO } from './types/CreditNotes.types';
@@ -26,6 +29,7 @@ import {
BulkDeleteDto, BulkDeleteDto,
ValidateBulkDeleteResponseDto, ValidateBulkDeleteResponseDto,
} from '@/common/dtos/BulkDelete.dto'; } from '@/common/dtos/BulkDelete.dto';
import { AcceptType } from '@/constants/accept-type';
@Controller('credit-notes') @Controller('credit-notes')
@ApiTags('Credit Notes') @ApiTags('Credit Notes')
@@ -65,8 +69,26 @@ export class CreditNotesController {
}, },
}) })
@ApiResponse({ status: 404, description: 'Credit note not found' }) @ApiResponse({ status: 404, description: 'Credit note not found' })
getCreditNote(@Param('id') creditNoteId: number) { async getCreditNote(
return this.creditNoteApplication.getCreditNote(creditNoteId); @Param('id') creditNoteId: number,
@Headers('accept') acceptHeader: string,
@Res({ passthrough: true }) res: Response,
) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
const [pdfContent, filename] =
await this.creditNoteApplication.getCreditNotePdf(creditNoteId);
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});
res.status(200).send(pdfContent);
} else {
const creditNote =
await this.creditNoteApplication.getCreditNote(creditNoteId);
return creditNote;
}
} }
@Get() @Get()
@@ -144,13 +166,10 @@ export class CreditNotesController {
status: 200, status: 200,
description: 'Credit notes deleted successfully', description: 'Credit notes deleted successfully',
}) })
bulkDeleteCreditNotes( bulkDeleteCreditNotes(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> {
@Body() bulkDeleteDto: BulkDeleteDto, return this.creditNoteApplication.bulkDeleteCreditNotes(bulkDeleteDto.ids, {
): Promise<void> { skipUndeletable: bulkDeleteDto.skipUndeletable ?? false,
return this.creditNoteApplication.bulkDeleteCreditNotes( });
bulkDeleteDto.ids,
{ skipUndeletable: bulkDeleteDto.skipUndeletable ?? false },
);
} }
@Delete(':id') @Delete(':id')

View File

@@ -1,3 +1,4 @@
import { Response } from 'express';
import { import {
ApiExtraModels, ApiExtraModels,
ApiOperation, ApiOperation,
@@ -17,6 +18,7 @@ import {
Post, Post,
Put, Put,
Query, Query,
Res,
} from '@nestjs/common'; } from '@nestjs/common';
import { PaymentReceivesApplication } from './PaymentReceived.application'; import { PaymentReceivesApplication } from './PaymentReceived.application';
import { import {
@@ -225,11 +227,18 @@ export class PaymentReceivesController {
public async getPaymentReceive( public async getPaymentReceive(
@Param('id', ParseIntPipe) paymentReceiveId: number, @Param('id', ParseIntPipe) paymentReceiveId: number,
@Headers('accept') acceptHeader: string, @Headers('accept') acceptHeader: string,
@Res({ passthrough: true }) res: Response,
) { ) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) { if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
return this.paymentReceivesApplication.getPaymentReceivePdf( const [pdfContent, filename] = await this.paymentReceivesApplication.getPaymentReceivePdf(
paymentReceiveId, paymentReceiveId,
); );
res.set({
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});
res.send(pdfContent);
} else if (acceptHeader.includes(AcceptType.ApplicationTextHtml)) { } else if (acceptHeader.includes(AcceptType.ApplicationTextHtml)) {
const htmlContent = const htmlContent =
await this.paymentReceivesApplication.getPaymentReceivedHtml( await this.paymentReceivesApplication.getPaymentReceivedHtml(

View File

@@ -318,7 +318,7 @@ export class SaleEstimatesController {
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
) { ) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) { if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
const pdfContent = const [pdfContent] =
await this.saleEstimatesApplication.getSaleEstimatePdf(estimateId); await this.saleEstimatesApplication.getSaleEstimatePdf(estimateId);
res.set({ res.set({

View File

@@ -26,7 +26,7 @@ export class GetSaleEstimatePdf {
@Inject(SaleEstimate.name) @Inject(SaleEstimate.name)
private readonly saleEstimateModel: TenantModelProxy<typeof SaleEstimate>, private readonly saleEstimateModel: TenantModelProxy<typeof SaleEstimate>,
) {} ) { }
/** /**
* Retrieve sale estimate html content. * Retrieve sale estimate html content.
@@ -50,9 +50,9 @@ export class GetSaleEstimatePdf {
// Retrieves the sale estimate html. // Retrieves the sale estimate html.
const htmlContent = await this.saleEstimateHtml(saleEstimateId); const htmlContent = await this.saleEstimateHtml(saleEstimateId);
const buffer =
const content =
await this.chromiumlyTenancy.convertHtmlContent(htmlContent); await this.chromiumlyTenancy.convertHtmlContent(htmlContent);
const eventPayload = { saleEstimateId }; const eventPayload = { saleEstimateId };
// Triggers the `onSaleEstimatePdfViewed` event. // Triggers the `onSaleEstimatePdfViewed` event.
@@ -60,7 +60,7 @@ export class GetSaleEstimatePdf {
events.saleEstimate.onPdfViewed, events.saleEstimate.onPdfViewed,
eventPayload, eventPayload,
); );
return [content, filename]; return [buffer, filename];
} }
/** /**

View File

@@ -82,9 +82,7 @@ export class SaleInvoicesController {
status: 200, status: 200,
description: 'Sale invoices deleted successfully', description: 'Sale invoices deleted successfully',
}) })
bulkDeleteSaleInvoices( bulkDeleteSaleInvoices(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> {
@Body() bulkDeleteDto: BulkDeleteDto,
): Promise<void> {
return this.saleInvoiceApplication.bulkDeleteSaleInvoices( return this.saleInvoiceApplication.bulkDeleteSaleInvoices(
bulkDeleteDto.ids, bulkDeleteDto.ids,
{ skipUndeletable: bulkDeleteDto.skipUndeletable ?? false }, { skipUndeletable: bulkDeleteDto.skipUndeletable ?? false },
@@ -212,7 +210,8 @@ export class SaleInvoicesController {
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
) { ) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) { if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf(id); const [pdfContent, filename] =
await this.saleInvoiceApplication.saleInvoicePdf(id);
res.set({ res.set({
'Content-Type': 'application/pdf', 'Content-Type': 'application/pdf',

View File

@@ -74,9 +74,7 @@ export class SaleReceiptsController {
status: 200, status: 200,
description: 'Sale receipts deleted successfully', description: 'Sale receipts deleted successfully',
}) })
bulkDeleteSaleReceipts( bulkDeleteSaleReceipts(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> {
@Body() bulkDeleteDto: BulkDeleteDto,
): Promise<void> {
return this.saleReceiptApplication.bulkDeleteSaleReceipts( return this.saleReceiptApplication.bulkDeleteSaleReceipts(
bulkDeleteDto.ids, bulkDeleteDto.ids,
{ skipUndeletable: bulkDeleteDto.skipUndeletable ?? false }, { skipUndeletable: bulkDeleteDto.skipUndeletable ?? false },
@@ -165,7 +163,7 @@ export class SaleReceiptsController {
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
) { ) {
if (acceptHeader.includes(AcceptType.ApplicationPdf)) { if (acceptHeader.includes(AcceptType.ApplicationPdf)) {
const pdfContent = const [pdfContent] =
await this.saleReceiptApplication.getSaleReceiptPdf(id); await this.saleReceiptApplication.getSaleReceiptPdf(id);
res.set({ res.set({