From de50b89e5c20b80e32f80a6fd749b5409f694e39 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 19 Oct 2024 13:16:06 +0200 Subject: [PATCH] feat: Invoice number in downloaded pdf document --- .../src/api/controllers/Sales/CreditNotes.ts | 3 +- .../api/controllers/Sales/PaymentReceives.ts | 5 +-- .../api/controllers/Sales/SalesEstimates.ts | 10 +++--- .../api/controllers/Sales/SalesInvoices.ts | 10 +++--- .../api/controllers/Sales/SalesReceipts.ts | 12 ++++--- .../services/CreditNotes/GetCreditNotePdf.ts | 31 +++++++++++++++++-- .../Sales/Estimates/SaleEstimatesPdf.ts | 29 +++++++++++++++-- .../services/Sales/Invoices/SaleInvoicePdf.ts | 28 +++++++++++++++-- .../PaymentReceived/GetPaymentReceivedPdf.ts | 29 +++++++++++++++-- .../Sales/Receipts/SaleReceiptsPdfService.ts | 25 ++++++++++++++- .../CreditNotePdfPreviewDialogContent.tsx | 6 ++-- .../EstimatePdfPreviewDialogContent.tsx | 4 +-- .../InvoicePdfPreviewDialogContent.tsx | 4 +-- .../PaymentReceivePdfPreviewContent.tsx | 4 +-- .../ReceiptPdfPreviewDialogContent.tsx | 4 +-- packages/webapp/src/hooks/useRequestPdf.tsx | 13 ++++++++ 16 files changed, 181 insertions(+), 36 deletions(-) diff --git a/packages/server/src/api/controllers/Sales/CreditNotes.ts b/packages/server/src/api/controllers/Sales/CreditNotes.ts index 3f35412c2d..3037d33f14 100644 --- a/packages/server/src/api/controllers/Sales/CreditNotes.ts +++ b/packages/server/src/api/controllers/Sales/CreditNotes.ts @@ -471,13 +471,14 @@ export default class PaymentReceivesController extends BaseController { ACCEPT_TYPE.APPLICATION_PDF, ]); if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { - const pdfContent = await this.creditNotePdf.getCreditNotePdf( + const [pdfContent, filename] = await this.creditNotePdf.getCreditNotePdf( tenantId, creditNoteId ); res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdfContent.length, + 'Content-Disposition': `attachment; filename="${filename}"`, }); res.send(pdfContent); } else { diff --git a/packages/server/src/api/controllers/Sales/PaymentReceives.ts b/packages/server/src/api/controllers/Sales/PaymentReceives.ts index f54bf5f151..b0f17bfac1 100644 --- a/packages/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/packages/server/src/api/controllers/Sales/PaymentReceives.ts @@ -408,7 +408,7 @@ export default class PaymentReceivesController extends BaseController { res: Response, next: NextFunction ) { - const { tenantId } = req; + const { tenantId } = req; try { const data = await this.paymentReceiveApplication.getPaymentReceivedState( @@ -473,7 +473,7 @@ export default class PaymentReceivesController extends BaseController { ]); // Response in pdf format. if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { - const pdfContent = + const [pdfContent, filename] = await this.paymentReceiveApplication.getPaymentReceivePdf( tenantId, paymentReceiveId @@ -481,6 +481,7 @@ export default class PaymentReceivesController extends BaseController { res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdfContent.length, + 'Content-Disposition': `attachment; filename="${filename}"`, }); res.send(pdfContent); // Response in json format. diff --git a/packages/server/src/api/controllers/Sales/SalesEstimates.ts b/packages/server/src/api/controllers/Sales/SalesEstimates.ts index 7b8109bdd9..556e96fd66 100644 --- a/packages/server/src/api/controllers/Sales/SalesEstimates.ts +++ b/packages/server/src/api/controllers/Sales/SalesEstimates.ts @@ -398,13 +398,15 @@ export default class SalesEstimatesController extends BaseController { ]); // Retrieves estimate in pdf format. if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) { - const pdfContent = await this.saleEstimatesApplication.getSaleEstimatePdf( - tenantId, - estimateId - ); + const [pdfContent, filename] = + await this.saleEstimatesApplication.getSaleEstimatePdf( + tenantId, + estimateId + ); res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdfContent.length, + 'Content-Disposition': `attachment; filename="${filename}"`, }); res.send(pdfContent); // Retrieves estimates in json format. diff --git a/packages/server/src/api/controllers/Sales/SalesInvoices.ts b/packages/server/src/api/controllers/Sales/SalesInvoices.ts index a8b2fb5a7f..ba900b786c 100644 --- a/packages/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/packages/server/src/api/controllers/Sales/SalesInvoices.ts @@ -441,13 +441,15 @@ export default class SaleInvoicesController extends BaseController { ]); // Retrieves invoice in pdf format. if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) { - const pdfContent = await this.saleInvoiceApplication.saleInvoicePdf( - tenantId, - saleInvoiceId - ); + const [pdfContent, filename] = + await this.saleInvoiceApplication.saleInvoicePdf( + tenantId, + saleInvoiceId + ); res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdfContent.length, + 'Content-Disposition': `attachment; filename="${filename}"`, }); res.send(pdfContent); // Retrieves invoice in json format. diff --git a/packages/server/src/api/controllers/Sales/SalesReceipts.ts b/packages/server/src/api/controllers/Sales/SalesReceipts.ts index a21392bb14..caf93d02ac 100644 --- a/packages/server/src/api/controllers/Sales/SalesReceipts.ts +++ b/packages/server/src/api/controllers/Sales/SalesReceipts.ts @@ -113,7 +113,7 @@ export default class SalesReceiptsController extends BaseController { CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt), asyncMiddleware(this.getSaleReceiptState.bind(this)), this.handleServiceErrors - ); + ); router.get( '/:id', CheckPolicies(SaleReceiptAction.View, AbilitySubject.SaleReceipt), @@ -356,13 +356,15 @@ export default class SalesReceiptsController extends BaseController { ]); // Retrieves receipt in pdf format. if (ACCEPT_TYPE.APPLICATION_PDF == acceptType) { - const pdfContent = await this.saleReceiptsApplication.getSaleReceiptPdf( - tenantId, - saleReceiptId - ); + const [pdfContent, filename] = + await this.saleReceiptsApplication.getSaleReceiptPdf( + tenantId, + saleReceiptId + ); res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdfContent.length, + 'Content-Disposition': `attachment; filename="${filename}"`, }); res.send(pdfContent); // Retrieves receipt in json format. diff --git a/packages/server/src/services/CreditNotes/GetCreditNotePdf.ts b/packages/server/src/services/CreditNotes/GetCreditNotePdf.ts index 4205fbfaa6..c4f22c92bf 100644 --- a/packages/server/src/services/CreditNotes/GetCreditNotePdf.ts +++ b/packages/server/src/services/CreditNotes/GetCreditNotePdf.ts @@ -28,8 +28,12 @@ export default class GetCreditNotePdf { * Retrieves sale invoice pdf content. * @param {number} tenantId - Tenant id. * @param {number} creditNoteId - Credit note id. + * @returns {Promise<[Buffer, string]>} */ - public async getCreditNotePdf(tenantId: number, creditNoteId: number) { + public async getCreditNotePdf( + tenantId: number, + creditNoteId: number + ): Promise<[Buffer, string]> { const brandingAttributes = await this.getCreditNoteBrandingAttributes( tenantId, creditNoteId @@ -39,7 +43,30 @@ export default class GetCreditNotePdf { 'modules/credit-note-standard', brandingAttributes ); - return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent); + const filename = await this.getCreditNoteFilename(tenantId, creditNoteId); + + const document = await this.chromiumlyTenancy.convertHtmlContent( + tenantId, + htmlContent + ); + return [document, filename]; + } + + /** + * Retrieves the filename pdf document of the given credit note. + * @param {number} tenantId + * @param {number} creditNoteId + * @returns {Promise} + */ + public async getCreditNoteFilename( + tenantId: number, + creditNoteId: number + ): Promise { + const { CreditNote } = this.tenancy.models(tenantId); + + const creditNote = await CreditNote.query().findById(creditNoteId); + + return `Credit-${creditNote.creditNoteNumber}`; } /** diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts b/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts index 36e4bf8f0f..f1a91162d0 100644 --- a/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts +++ b/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts @@ -29,7 +29,14 @@ export class SaleEstimatesPdf { * @param {number} tenantId - * @param {ISaleInvoice} saleInvoice - */ - public async getSaleEstimatePdf(tenantId: number, saleEstimateId: number) { + public async getSaleEstimatePdf( + tenantId: number, + saleEstimateId: number + ): Promise<[Buffer, string]> { + const filename = await this.getSaleEstimateFilename( + tenantId, + saleEstimateId + ); const brandingAttributes = await this.getEstimateBrandingAttributes( tenantId, saleEstimateId @@ -39,7 +46,25 @@ export class SaleEstimatesPdf { 'modules/estimate-regular', brandingAttributes ); - return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent); + const content = await this.chromiumlyTenancy.convertHtmlContent( + tenantId, + htmlContent + ); + return [content, filename]; + } + + /** + * Retrieves the filename file document of the given estimate. + * @param {number} tenantId + * @param {number} estimateId + * @returns {Promise} + */ + private async getSaleEstimateFilename(tenantId: number, estimateId: number) { + const { SaleEstimate } = this.tenancy.models(tenantId); + + const estimate = await SaleEstimate.query().findById(estimateId); + + return `Estimate-${estimate.estimateNumber}`; } /** diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts index 0146478b3a..54e8738ef8 100644 --- a/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts @@ -33,7 +33,9 @@ export class SaleInvoicePdf { public async saleInvoicePdf( tenantId: number, invoiceId: number - ): Promise { + ): Promise<[Buffer, string]> { + const filename = await this.getInvoicePdfFilename(tenantId, invoiceId); + const brandingAttributes = await this.getInvoiceBrandingAttributes( tenantId, invoiceId @@ -44,7 +46,29 @@ export class SaleInvoicePdf { brandingAttributes ); // Converts the given html content to pdf document. - return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent); + const buffer = await this.chromiumlyTenancy.convertHtmlContent( + tenantId, + htmlContent + ); + + return [buffer, filename]; + } + + /** + * Retrieves the filename pdf document of the given invoice. + * @param {number} tenantId + * @param {number} invoiceId + * @returns {Promise} + */ + private async getInvoicePdfFilename( + tenantId: number, + invoiceId: number + ): Promise { + const { SaleInvoice } = this.tenancy.models(tenantId); + + const invoice = await SaleInvoice.query().findById(invoiceId); + + return `Invoice-${invoice.invoiceNo}`; } /** diff --git a/packages/server/src/services/Sales/PaymentReceived/GetPaymentReceivedPdf.ts b/packages/server/src/services/Sales/PaymentReceived/GetPaymentReceivedPdf.ts index afae67359a..5ee761f2f4 100644 --- a/packages/server/src/services/Sales/PaymentReceived/GetPaymentReceivedPdf.ts +++ b/packages/server/src/services/Sales/PaymentReceived/GetPaymentReceivedPdf.ts @@ -33,7 +33,7 @@ export default class GetPaymentReceivedPdf { async getPaymentReceivePdf( tenantId: number, paymentReceiveId: number - ): Promise { + ): Promise<[Buffer, string]> { const brandingAttributes = await this.getPaymentBrandingAttributes( tenantId, paymentReceiveId @@ -43,8 +43,33 @@ export default class GetPaymentReceivedPdf { 'modules/payment-receive-standard', brandingAttributes ); + const filename = await this.getPaymentReceivedFilename( + tenantId, + paymentReceiveId + ); // Converts the given html content to pdf document. - return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent); + const content = await this.chromiumlyTenancy.convertHtmlContent( + tenantId, + htmlContent + ); + return [content, filename]; + } + + /** + * Retrieves the filename of the given payment. + * @param {number} tenantId + * @param {number} paymentReceivedId + * @returns {Promise} + */ + private async getPaymentReceivedFilename( + tenantId: number, + paymentReceivedId: number + ): Promise { + const { PaymentReceive } = this.tenancy.models(tenantId); + + const payment = await PaymentReceive.query().findById(paymentReceivedId); + + return `Payment-${payment.paymentReceiveNo}`; } /** diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts index 7b056c1c52..dface41667 100644 --- a/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts @@ -31,6 +31,8 @@ export class SaleReceiptsPdf { * @returns {Promise} */ public async saleReceiptPdf(tenantId: number, saleReceiptId: number) { + const filename = await this.getSaleReceiptFilename(tenantId, saleReceiptId); + const brandingAttributes = await this.getReceiptBrandingAttributes( tenantId, saleReceiptId @@ -42,7 +44,28 @@ export class SaleReceiptsPdf { brandingAttributes ); // Renders the html content to pdf document. - return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent); + const content = await this.chromiumlyTenancy.convertHtmlContent( + tenantId, + htmlContent + ); + return [content, filename]; + } + + /** + * Retrieves the filename file document of the given sale receipt. + * @param {number} tenantId + * @param {number} receiptId + * @returns {Promise} + */ + public async getSaleReceiptFilename( + tenantId: number, + receiptId: number + ): Promise { + const { SaleReceipt } = this.tenancy.models(tenantId); + + const receipt = await SaleReceipt.query().findById(receiptId); + + return `Receipt-${receipt.receiptNumber}`; } /** diff --git a/packages/webapp/src/containers/Dialogs/CreditNotePdfPreviewDialog/CreditNotePdfPreviewDialogContent.tsx b/packages/webapp/src/containers/Dialogs/CreditNotePdfPreviewDialog/CreditNotePdfPreviewDialogContent.tsx index 067853aeb7..bb44f858ba 100644 --- a/packages/webapp/src/containers/Dialogs/CreditNotePdfPreviewDialog/CreditNotePdfPreviewDialogContent.tsx +++ b/packages/webapp/src/containers/Dialogs/CreditNotePdfPreviewDialog/CreditNotePdfPreviewDialogContent.tsx @@ -11,8 +11,8 @@ import { compose } from '@/utils'; function CreditNotePdfPreviewDialogContent({ subscriptionForm: { creditNoteId }, }) { - const { isLoading, pdfUrl } = usePdfCreditNote(creditNoteId); - + const { isLoading, pdfUrl, filename } = usePdfCreditNote(creditNoteId); + return (
@@ -27,7 +27,7 @@ function CreditNotePdfPreviewDialogContent({ diff --git a/packages/webapp/src/containers/Dialogs/EstimatePdfPreviewDialog/EstimatePdfPreviewDialogContent.tsx b/packages/webapp/src/containers/Dialogs/EstimatePdfPreviewDialog/EstimatePdfPreviewDialogContent.tsx index 50aa219dde..7d9546e57d 100644 --- a/packages/webapp/src/containers/Dialogs/EstimatePdfPreviewDialog/EstimatePdfPreviewDialogContent.tsx +++ b/packages/webapp/src/containers/Dialogs/EstimatePdfPreviewDialog/EstimatePdfPreviewDialogContent.tsx @@ -14,7 +14,7 @@ function EstimatePdfPreviewDialogContent({ // #withDialogActions closeDialog, }) { - const { isLoading, pdfUrl } = usePdfEstimate(estimateId); + const { isLoading, pdfUrl, filename } = usePdfEstimate(estimateId); return ( @@ -30,7 +30,7 @@ function EstimatePdfPreviewDialogContent({ diff --git a/packages/webapp/src/containers/Dialogs/InvoicePdfPreviewDialog/InvoicePdfPreviewDialogContent.tsx b/packages/webapp/src/containers/Dialogs/InvoicePdfPreviewDialog/InvoicePdfPreviewDialogContent.tsx index c31b643b0d..7a56e7fa0a 100644 --- a/packages/webapp/src/containers/Dialogs/InvoicePdfPreviewDialog/InvoicePdfPreviewDialogContent.tsx +++ b/packages/webapp/src/containers/Dialogs/InvoicePdfPreviewDialog/InvoicePdfPreviewDialogContent.tsx @@ -13,7 +13,7 @@ function InvoicePdfPreviewDialogContent({ // #withDialog closeDialog, }) { - const { isLoading, pdfUrl } = usePdfInvoice(invoiceId); + const { isLoading, pdfUrl, filename } = usePdfInvoice(invoiceId); return ( @@ -29,7 +29,7 @@ function InvoicePdfPreviewDialogContent({ diff --git a/packages/webapp/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/PaymentReceivePdfPreviewContent.tsx b/packages/webapp/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/PaymentReceivePdfPreviewContent.tsx index b76b9d6587..21f5f7d4cf 100644 --- a/packages/webapp/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/PaymentReceivePdfPreviewContent.tsx +++ b/packages/webapp/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/PaymentReceivePdfPreviewContent.tsx @@ -11,7 +11,7 @@ import { compose } from '@/utils'; function PaymentReceivePdfPreviewDialogContent({ subscriptionForm: { paymentReceiveId }, }) { - const { isLoading, pdfUrl } = usePdfPaymentReceive(paymentReceiveId); + const { isLoading, pdfUrl, filename } = usePdfPaymentReceive(paymentReceiveId); return ( @@ -27,7 +27,7 @@ function PaymentReceivePdfPreviewDialogContent({ diff --git a/packages/webapp/src/containers/Dialogs/ReceiptPdfPreviewDialog/ReceiptPdfPreviewDialogContent.tsx b/packages/webapp/src/containers/Dialogs/ReceiptPdfPreviewDialog/ReceiptPdfPreviewDialogContent.tsx index fea9394ffb..37bb75eff0 100644 --- a/packages/webapp/src/containers/Dialogs/ReceiptPdfPreviewDialog/ReceiptPdfPreviewDialogContent.tsx +++ b/packages/webapp/src/containers/Dialogs/ReceiptPdfPreviewDialog/ReceiptPdfPreviewDialogContent.tsx @@ -13,7 +13,7 @@ function ReceiptPdfPreviewDialogContent({ // #withDialogActions closeDialog, }) { - const { isLoading, pdfUrl } = usePdfReceipt(receiptId); + const { isLoading, pdfUrl, filename } = usePdfReceipt(receiptId); return ( @@ -29,7 +29,7 @@ function ReceiptPdfPreviewDialogContent({ diff --git a/packages/webapp/src/hooks/useRequestPdf.tsx b/packages/webapp/src/hooks/useRequestPdf.tsx index 55d797b2fb..48fbbf6874 100644 --- a/packages/webapp/src/hooks/useRequestPdf.tsx +++ b/packages/webapp/src/hooks/useRequestPdf.tsx @@ -8,6 +8,7 @@ export const useRequestPdf = (httpProps) => { const [isLoaded, setIsLoaded] = React.useState(false); const [pdfUrl, setPdfUrl] = React.useState(''); const [response, setResponse] = React.useState(null); + const [filename, setFilename] = React.useState(''); React.useEffect(() => { setIsLoading(true); @@ -25,10 +26,21 @@ export const useRequestPdf = (httpProps) => { // Build a URL from the file const fileURL = URL.createObjectURL(file); + // Extract the filename from the Content-Disposition header + const contentDisposition = response.headers.get('Content-Disposition'); + let _filename = 'default.pdf'; // Default filename if not provided by server + + if (contentDisposition && contentDisposition.includes('filename=')) { + const matches = contentDisposition.match(/filename="(.+)"/); + if (matches && matches[1]) { + _filename = matches[1]; + } + } setPdfUrl(fileURL); setIsLoading(false); setIsLoaded(true); setResponse(response); + setFilename(_filename); }); }, []); @@ -37,5 +49,6 @@ export const useRequestPdf = (httpProps) => { isLoaded, pdfUrl, response, + filename }; };