- Tous les clients repliés par défaut à l'ouverture du modal - Boutons 'Tout replier' / 'Tout déplier' pour gérer tous les clients - Section factures repliable avec bouton Afficher/Masquer - Affichage résumé facture sélectionnée quand section repliée - Nouveau client déplié automatiquement pour faciliter la saisie - Améliore la lisibilité pour NAA avec nombreux clients
125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
|
|
const REGION = process.env.AWS_REGION || 'eu-west-3';
|
|
const BUCKET = (process.env.AWS_S3_BUCKET || 'odentas-docs').trim();
|
|
|
|
const s3Client = new S3Client({
|
|
region: REGION,
|
|
credentials: {
|
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
},
|
|
});
|
|
|
|
export interface UploadPdfOptions {
|
|
/** Buffer du PDF à uploader */
|
|
pdfBuffer: Buffer;
|
|
/** Clé S3 (chemin + nom du fichier), ex: 'contrats/2025/contrat-123.pdf' */
|
|
key: string;
|
|
/** Type de contenu (par défaut: application/pdf) */
|
|
contentType?: string;
|
|
/** Métadonnées supplémentaires */
|
|
metadata?: Record<string, string>;
|
|
}
|
|
|
|
/**
|
|
* Upload un PDF généré sur S3
|
|
*
|
|
* @param options - Options d'upload
|
|
* @returns La clé S3 du fichier uploadé
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const pdfBuffer = await generateContratPdf(data);
|
|
* const s3Key = await uploadPdfToS3({
|
|
* pdfBuffer,
|
|
* key: `contrats/${organizationId}/${contractId}.pdf`,
|
|
* metadata: {
|
|
* contractId: contractId,
|
|
* organizationId: organizationId,
|
|
* generatedAt: new Date().toISOString(),
|
|
* }
|
|
* });
|
|
* ```
|
|
*/
|
|
export async function uploadPdfToS3(options: UploadPdfOptions): Promise<string> {
|
|
const { pdfBuffer, key, contentType = 'application/pdf', metadata = {} } = options;
|
|
|
|
console.log('📤 [S3 Upload] Début de l\'upload du PDF:', {
|
|
key,
|
|
bucket: BUCKET,
|
|
region: REGION,
|
|
size: pdfBuffer.byteLength,
|
|
metadata,
|
|
});
|
|
|
|
try {
|
|
const command = new PutObjectCommand({
|
|
Bucket: BUCKET,
|
|
Key: key,
|
|
Body: pdfBuffer,
|
|
ContentType: contentType,
|
|
Metadata: metadata,
|
|
});
|
|
|
|
await s3Client.send(command);
|
|
|
|
console.log('✅ [S3 Upload] PDF uploadé avec succès:', {
|
|
key,
|
|
bucket: BUCKET,
|
|
size: pdfBuffer.byteLength,
|
|
});
|
|
|
|
return key;
|
|
} catch (error) {
|
|
console.error('❌ [S3 Upload] Erreur lors de l\'upload du PDF:', error);
|
|
throw new Error(`Échec de l'upload du PDF sur S3: ${error instanceof Error ? error.message : String(error)}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Génère une clé S3 pour un contrat CDDU
|
|
*
|
|
* @param organizationId - ID de l'organisation
|
|
* @param contractId - ID du contrat
|
|
* @param year - Année du contrat (par défaut: année courante)
|
|
* @returns Clé S3 formatée
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const key = generateContractS3Key('org-123', 'contract-456', 2025);
|
|
* // Retourne: 'contrats/org-123/2025/contract-456.pdf'
|
|
* ```
|
|
*/
|
|
export function generateContractS3Key(
|
|
organizationId: string,
|
|
contractId: string,
|
|
year: number = new Date().getFullYear()
|
|
): string {
|
|
return `contrats/${organizationId}/${year}/${contractId}.pdf`;
|
|
}
|
|
|
|
/**
|
|
* Génère une clé S3 pour une fiche de paie
|
|
*
|
|
* @param organizationId - ID de l'organisation
|
|
* @param payslipId - ID de la fiche de paie
|
|
* @param year - Année de la fiche de paie
|
|
* @param month - Mois de la fiche de paie (1-12)
|
|
* @returns Clé S3 formatée
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const key = generatePayslipS3Key('org-123', 'payslip-456', 2025, 10);
|
|
* // Retourne: 'fiches-paie/org-123/2025/10/payslip-456.pdf'
|
|
* ```
|
|
*/
|
|
export function generatePayslipS3Key(
|
|
organizationId: string,
|
|
payslipId: string,
|
|
year: number,
|
|
month: number
|
|
): string {
|
|
const monthPadded = String(month).padStart(2, '0');
|
|
return `fiches-paie/${organizationId}/${year}/${monthPadded}/${payslipId}.pdf`;
|
|
}
|