diff --git a/src/front/Components/Layouts/Folder/FolderInformation/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/index.tsx
index 40092017..b18eab68 100644
--- a/src/front/Components/Layouts/Folder/FolderInformation/index.tsx
+++ b/src/front/Components/Layouts/Folder/FolderInformation/index.tsx
@@ -21,6 +21,13 @@ import NoClientView from "./NoClientView";
import AnchoringProcessingInfo from "./elements/AnchoringProcessingInfo";
import FolderService from "src/common/Api/LeCoffreApi/sdk/FolderService";
+import DocumentService from "src/common/Api/LeCoffreApi/sdk/DocumentService";
+import FileService from "src/common/Api/LeCoffreApi/sdk/FileService";
+import LoaderService from "src/common/Api/LeCoffreApi/sdk/Loader/LoaderService";
+import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
+import PdfService from "@Front/Services/PdfService";
+import MessageBus from "src/sdk/MessageBus";
+import { saveAs } from "file-saver";
import EFolderStatus from "le-coffre-resources/dist/Customer/EFolderStatus";
export enum AnchorStatus {
@@ -145,6 +152,121 @@ export default function FolderInformation(props: IProps) {
archiveModal.open();
}, [anchorStatus, archiveModal, requireAnchoringModal]);
+ const onDownloadAllCertificates = useCallback(async () => {
+ if (!folder?.uid) return;
+
+ try {
+ LoaderService.getInstance().show();
+
+ // Get all documents for this folder
+ const allDocuments = await DocumentService.getDocuments();
+ const folderDocuments = allDocuments
+ .map((process: any) => process.processData)
+ .filter((doc: any) =>
+ doc.folder?.uid === folder.uid &&
+ doc.document_status === EDocumentStatus.VALIDATED
+ );
+
+ if (folderDocuments.length === 0) {
+ ToasterService.getInstance().warning({
+ title: "Aucun certificat",
+ description: "Aucun document validé trouvé pour ce dossier."
+ });
+ LoaderService.getInstance().hide();
+ return;
+ }
+
+ // Generate certificates for all validated documents
+ const certificates: any[] = [];
+
+ for (const doc of folderDocuments) {
+ try {
+ // Get customer info
+ const customer = doc.depositor || doc.customer;
+
+ const certificateData: any = {
+ customer: {
+ firstName: customer?.first_name || customer?.firstName || "N/A",
+ lastName: customer?.last_name || customer?.lastName || "N/A",
+ postalAddress: customer?.postal_address || customer?.address || customer?.postalAddress || "N/A",
+ email: customer?.email || "N/A"
+ },
+ notary: {
+ name: "N/A"
+ },
+ folderUid: folder.uid,
+ documentHash: "N/A",
+ metadata: {
+ fileName: "N/A",
+ isDeleted: false,
+ updatedAt: new Date(),
+ commitmentId: "N/A",
+ createdAt: new Date(),
+ documentUid: "N/A",
+ documentType: "N/A",
+ merkleProof: "N/A"
+ }
+ };
+
+ // Process files for this document
+ if (doc.files && doc.files.length > 0) {
+ for (const file of doc.files) {
+ const fileProcess = await FileService.getFileByUid(file.uid);
+
+ if (fileProcess.lastUpdatedFileState?.pcd_commitment?.file_blob) {
+ const hash = fileProcess.lastUpdatedFileState.pcd_commitment.file_blob;
+ certificateData.documentHash = hash;
+
+ const proof = await MessageBus.getInstance().generateMerkleProof(
+ fileProcess.lastUpdatedFileState,
+ 'file_blob'
+ );
+
+ const metadata: any = {
+ fileName: fileProcess.processData.file_name,
+ isDeleted: false,
+ updatedAt: new Date(fileProcess.processData.updated_at),
+ commitmentId: fileProcess.lastUpdatedFileState.commited_in,
+ createdAt: new Date(fileProcess.processData.created_at),
+ documentUid: doc.uid,
+ documentType: doc.document_type?.name || "N/A",
+ merkleProof: proof
+ };
+ certificateData.metadata = metadata;
+ break; // Use first file for now
+ }
+ }
+ }
+
+ certificates.push(certificateData);
+ } catch (error) {
+ console.error(`Error processing document ${doc.uid}:`, error);
+ }
+ }
+
+ // Generate combined PDF
+ const combinedPdf = await PdfService.getInstance().generateCombinedCertificates(certificates, folder);
+
+ // Download the combined PDF
+ const filename = `certificats_${folder.folder_number || folder.uid}_${new Date().toISOString().split('T')[0]}.pdf`;
+ saveAs(combinedPdf, filename);
+
+ ToasterService.getInstance().success({
+ title: "Succès !",
+ description: `${certificates.length} certificat(s) téléchargé(s) avec succès.`
+ });
+
+ } catch (error) {
+ console.error('Error downloading all certificates:', error);
+ ToasterService.getInstance().error({
+ title: "Erreur",
+ description: "Une erreur est survenue lors du téléchargement des certificats."
+ });
+ } finally {
+ LoaderService.getInstance().hide();
+ }
+ }, [folder?.uid]);
+
return (
{!isLoading && (
@@ -155,6 +277,7 @@ export default function FolderInformation(props: IProps) {
onArchive={onArchive}
anchorStatus={anchorStatus}
isArchived={isArchived}
+ onDownloadAllCertificates={onDownloadAllCertificates}
/>
{progress === 100 && /*anchorStatus === AnchorStatus.NOT_ANCHORED*/ folder.status !== EFolderStatus.ARCHIVED && (
diff --git a/src/front/Services/PdfService/index.ts b/src/front/Services/PdfService/index.ts
index fcfa0eb3..927e455f 100644
--- a/src/front/Services/PdfService/index.ts
+++ b/src/front/Services/PdfService/index.ts
@@ -335,4 +335,193 @@ export default class PdfService {
fileReader.readAsArrayBuffer(certificateFile);
});
}
+
+ /**
+ * Generate a combined PDF with multiple certificates
+ * @param certificates - Array of certificate data
+ * @param folder - Folder information
+ * @returns Promise - Combined PDF as blob
+ */
+ public async generateCombinedCertificates(certificates: CertificateData[], folder: any): Promise {
+ try {
+ // Import pdf-lib dynamically to avoid SSR issues
+ const { PDFDocument, rgb, StandardFonts } = await import('pdf-lib');
+
+ // Create a new PDF document
+ const pdfDoc = await PDFDocument.create();
+ const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
+ const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
+
+ // Add a cover page
+ const coverPage = pdfDoc.addPage([595.28, 841.89]); // A4 size
+ const { width, height } = coverPage.getSize();
+
+ // Cover page title
+ coverPage.drawText('Certificats de Validation', {
+ x: 50,
+ y: height - 100,
+ size: 24,
+ font: helveticaBold,
+ color: rgb(0, 0, 0),
+ });
+
+ // Folder information
+ coverPage.drawText(`Dossier: ${folder.folder_number || folder.uid}`, {
+ x: 50,
+ y: height - 150,
+ size: 16,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ coverPage.drawText(`Nom: ${folder.name || 'N/A'}`, {
+ x: 50,
+ y: height - 180,
+ size: 16,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ coverPage.drawText(`Date de génération: ${new Date().toLocaleDateString('fr-FR')}`, {
+ x: 50,
+ y: height - 210,
+ size: 16,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ coverPage.drawText(`Nombre de certificats: ${certificates.length}`, {
+ x: 50,
+ y: height - 240,
+ size: 16,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ // Add each certificate as a separate page
+ for (let i = 0; i < certificates.length; i++) {
+ const certificate = certificates[i];
+ if (!certificate) continue;
+
+ const page = pdfDoc.addPage([595.28, 841.89]); // A4 size
+ const { width: pageWidth, height: pageHeight } = page.getSize();
+
+ // Certificate title
+ page.drawText(`Certificat ${i + 1}`, {
+ x: 50,
+ y: pageHeight - 50,
+ size: 20,
+ font: helveticaBold,
+ color: rgb(0, 0, 0),
+ });
+
+ // Customer information
+ page.drawText('Informations Client:', {
+ x: 50,
+ y: pageHeight - 100,
+ size: 16,
+ font: helveticaBold,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Nom: ${certificate.customer.firstName} ${certificate.customer.lastName}`, {
+ x: 50,
+ y: pageHeight - 130,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Adresse: ${certificate.customer.postalAddress}`, {
+ x: 50,
+ y: pageHeight - 150,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Email: ${certificate.customer.email}`, {
+ x: 50,
+ y: pageHeight - 170,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ // Document information
+ page.drawText('Informations Document:', {
+ x: 50,
+ y: pageHeight - 200,
+ size: 16,
+ font: helveticaBold,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Type: ${certificate.metadata.documentType}`, {
+ x: 50,
+ y: pageHeight - 230,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Fichier: ${certificate.metadata.fileName}`, {
+ x: 50,
+ y: pageHeight - 250,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Hash: ${certificate.documentHash}`, {
+ x: 50,
+ y: pageHeight - 270,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Commitment ID: ${certificate.metadata.commitmentId}`, {
+ x: 50,
+ y: pageHeight - 290,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Date de création: ${certificate.metadata.createdAt.toLocaleDateString('fr-FR')}`, {
+ x: 50,
+ y: pageHeight - 310,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ page.drawText(`Date de mise à jour: ${certificate.metadata.updatedAt.toLocaleDateString('fr-FR')}`, {
+ x: 50,
+ y: pageHeight - 330,
+ size: 12,
+ font: helveticaFont,
+ color: rgb(0, 0, 0),
+ });
+
+ // Add page number
+ page.drawText(`Page ${i + 2}`, {
+ x: pageWidth - 80,
+ y: 30,
+ size: 10,
+ font: helveticaFont,
+ color: rgb(0.5, 0.5, 0.5),
+ });
+ }
+
+ // Save the PDF
+ const pdfBytes = await pdfDoc.save();
+ return new Blob([pdfBytes], { type: 'application/pdf' });
+
+ } catch (error) {
+ console.error('Error generating combined certificates:', error);
+ throw error;
+ }
+ }
}