Initial commit
This commit is contained in:
464
services/exportBTPService.ts
Normal file
464
services/exportBTPService.ts
Normal file
@@ -0,0 +1,464 @@
|
||||
import ApiService from './ApiService';
|
||||
import { MaterielBTP, RechercheMaterielParams } from './materielBTPService';
|
||||
import { ZoneClimatique } from './zoneClimatiqueService';
|
||||
import { ResultatCalculBriques, ResultatCalculBetonArme } from './calculsTechniquesService';
|
||||
|
||||
/**
|
||||
* Service d'export pour les données BTP ultra-détaillées
|
||||
* Permet l'export en CSV, Excel et PDF des matériaux, calculs et zones climatiques
|
||||
*/
|
||||
|
||||
export type FormatExport = 'CSV' | 'EXCEL' | 'PDF';
|
||||
|
||||
export interface OptionsExport {
|
||||
format: FormatExport;
|
||||
includeImages?: boolean;
|
||||
includeCharts?: boolean;
|
||||
filtres?: any;
|
||||
colonnesPersonnalisees?: string[];
|
||||
template?: 'STANDARD' | 'DETAILLE' | 'RESUME';
|
||||
}
|
||||
|
||||
export interface ResultatExport {
|
||||
filename: string;
|
||||
blob: Blob;
|
||||
size: number;
|
||||
format: FormatExport;
|
||||
nbLignes: number;
|
||||
dateGeneration: string;
|
||||
}
|
||||
|
||||
export class ExportBTPService {
|
||||
|
||||
/**
|
||||
* Export des matériaux BTP avec filtres
|
||||
*/
|
||||
static async exporterMateriaux(options: OptionsExport & {
|
||||
filtres?: RechercheMaterielParams;
|
||||
}): Promise<ResultatExport> {
|
||||
try {
|
||||
const blob = await ApiService.post<Blob>(
|
||||
'/calculs-techniques/materiaux/export',
|
||||
{
|
||||
format: options.format,
|
||||
filtres: options.filtres,
|
||||
template: options.template || 'STANDARD',
|
||||
colonnesPersonnalisees: options.colonnesPersonnalisees
|
||||
},
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
const filename = this.genererNomFichier('materiaux', options.format);
|
||||
|
||||
return {
|
||||
filename,
|
||||
blob,
|
||||
size: blob.size,
|
||||
format: options.format,
|
||||
nbLignes: 0, // Sera calculé côté serveur
|
||||
dateGeneration: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur export matériaux:', error);
|
||||
throw new Error('Impossible d\'exporter les matériaux');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export des zones climatiques
|
||||
*/
|
||||
static async exporterZonesClimatiques(options: OptionsExport): Promise<ResultatExport> {
|
||||
try {
|
||||
const blob = await ApiService.post<Blob>(
|
||||
'/calculs-techniques/zones-climatiques/export',
|
||||
{
|
||||
format: options.format,
|
||||
template: options.template || 'STANDARD'
|
||||
},
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
const filename = this.genererNomFichier('zones-climatiques', options.format);
|
||||
|
||||
return {
|
||||
filename,
|
||||
blob,
|
||||
size: blob.size,
|
||||
format: options.format,
|
||||
nbLignes: 0,
|
||||
dateGeneration: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur export zones climatiques:', error);
|
||||
throw new Error('Impossible d\'exporter les zones climatiques');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export des résultats de calculs techniques
|
||||
*/
|
||||
static async exporterCalculs(
|
||||
calculs: Array<{
|
||||
type: 'BRIQUES' | 'BETON' | 'MORTIER';
|
||||
resultat: ResultatCalculBriques | ResultatCalculBetonArme | any;
|
||||
parametres: any;
|
||||
}>,
|
||||
options: OptionsExport
|
||||
): Promise<ResultatExport> {
|
||||
try {
|
||||
const data = {
|
||||
calculs: calculs,
|
||||
format: options.format,
|
||||
template: options.template || 'DETAILLE',
|
||||
includeCharts: options.includeCharts || false
|
||||
};
|
||||
|
||||
const blob = await ApiService.post<Blob>(
|
||||
'/calculs-techniques/export-calculs',
|
||||
data,
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
const filename = this.genererNomFichier('calculs-techniques', options.format);
|
||||
|
||||
return {
|
||||
filename,
|
||||
blob,
|
||||
size: blob.size,
|
||||
format: options.format,
|
||||
nbLignes: calculs.length,
|
||||
dateGeneration: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
// Fallback : export côté client si serveur indisponible
|
||||
console.warn('Export serveur indisponible, génération côté client');
|
||||
return this.exporterCalculsCoteClient(calculs, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export complet du projet BTP (matériaux + zones + calculs)
|
||||
*/
|
||||
static async exporterProjetComplet(
|
||||
chantierId: string,
|
||||
options: OptionsExport & {
|
||||
includeMateriaux?: boolean;
|
||||
includeZones?: boolean;
|
||||
includeCalculs?: boolean;
|
||||
includePhases?: boolean;
|
||||
}
|
||||
): Promise<ResultatExport> {
|
||||
try {
|
||||
const data = {
|
||||
chantierId,
|
||||
format: options.format,
|
||||
template: options.template || 'DETAILLE',
|
||||
sections: {
|
||||
materiaux: options.includeMateriaux !== false,
|
||||
zones: options.includeZones !== false,
|
||||
calculs: options.includeCalculs !== false,
|
||||
phases: options.includePhases !== false
|
||||
},
|
||||
includeCharts: options.includeCharts || false,
|
||||
includeImages: options.includeImages || false
|
||||
};
|
||||
|
||||
const blob = await ApiService.post<Blob>(
|
||||
'/calculs-techniques/export-projet-complet',
|
||||
data,
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
const filename = this.genererNomFichier(`projet-${chantierId}`, options.format);
|
||||
|
||||
return {
|
||||
filename,
|
||||
blob,
|
||||
size: blob.size,
|
||||
format: options.format,
|
||||
nbLignes: 0,
|
||||
dateGeneration: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur export projet complet:', error);
|
||||
throw new Error('Impossible d\'exporter le projet complet');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génération de devis BTP détaillé avec matériaux et calculs
|
||||
*/
|
||||
static async genererDevisBTP(
|
||||
devisData: {
|
||||
chantierId: string;
|
||||
phases: Array<{
|
||||
nom: string;
|
||||
materiaux: Array<{
|
||||
code: string;
|
||||
quantite: number;
|
||||
prixUnitaire?: number;
|
||||
}>;
|
||||
calculs?: any;
|
||||
}>;
|
||||
client: {
|
||||
nom: string;
|
||||
adresse: string;
|
||||
telephone?: string;
|
||||
email?: string;
|
||||
};
|
||||
options: {
|
||||
margeCommerciale: number;
|
||||
tva: number;
|
||||
delaiExecution: number;
|
||||
validiteDevis: number; // jours
|
||||
conditionsPaiement: string;
|
||||
};
|
||||
},
|
||||
format: FormatExport = 'PDF'
|
||||
): Promise<ResultatExport> {
|
||||
try {
|
||||
const blob = await ApiService.post<Blob>(
|
||||
'/calculs-techniques/generer-devis',
|
||||
{
|
||||
...devisData,
|
||||
format,
|
||||
template: 'DEVIS_PROFESSIONNEL'
|
||||
},
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
const filename = this.genererNomFichier(`devis-${devisData.chantierId}`, format);
|
||||
|
||||
return {
|
||||
filename,
|
||||
blob,
|
||||
size: blob.size,
|
||||
format,
|
||||
nbLignes: devisData.phases.length,
|
||||
dateGeneration: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur génération devis:', error);
|
||||
throw new Error('Impossible de générer le devis');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export des matériaux avec QR codes pour traçabilité
|
||||
*/
|
||||
static async exporterMateriauxAvecQRCodes(
|
||||
materiaux: MaterielBTP[],
|
||||
options: OptionsExport
|
||||
): Promise<ResultatExport> {
|
||||
try {
|
||||
const data = {
|
||||
materiaux: materiaux.map(m => ({
|
||||
id: m.id,
|
||||
code: m.code,
|
||||
nom: m.nom,
|
||||
categorie: m.categorie,
|
||||
specifications: {
|
||||
resistance: m.resistanceCompression,
|
||||
densite: m.densite,
|
||||
norme: m.normePrincipale
|
||||
}
|
||||
})),
|
||||
format: options.format,
|
||||
includeQRCodes: true,
|
||||
template: 'TRACABILITE'
|
||||
};
|
||||
|
||||
const blob = await ApiService.post<Blob>(
|
||||
'/calculs-techniques/export-tracabilite',
|
||||
data,
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
const filename = this.genererNomFichier('tracabilite-materiaux', options.format);
|
||||
|
||||
return {
|
||||
filename,
|
||||
blob,
|
||||
size: blob.size,
|
||||
format: options.format,
|
||||
nbLignes: materiaux.length,
|
||||
dateGeneration: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur export traçabilité:', error);
|
||||
throw new Error('Impossible d\'exporter la traçabilité');
|
||||
}
|
||||
}
|
||||
|
||||
// =================== MÉTHODES UTILITAIRES ===================
|
||||
|
||||
/**
|
||||
* Télécharge automatiquement le fichier exporté
|
||||
*/
|
||||
static telechargerFichier(resultat: ResultatExport): void {
|
||||
const url = window.URL.createObjectURL(resultat.blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = resultat.filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prévisualise le contenu avant export (pour PDF)
|
||||
*/
|
||||
static async previsualiserExport(
|
||||
type: 'MATERIAUX' | 'ZONES' | 'CALCULS' | 'DEVIS',
|
||||
donnees: any,
|
||||
options: OptionsExport
|
||||
): Promise<string> {
|
||||
try {
|
||||
const response = await ApiService.post<{ previewUrl: string }>(
|
||||
'/calculs-techniques/previsualiser-export',
|
||||
{
|
||||
type,
|
||||
donnees,
|
||||
options
|
||||
}
|
||||
);
|
||||
|
||||
return response.previewUrl;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur prévisualisation:', error);
|
||||
throw new Error('Impossible de générer la prévisualisation');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les templates d'export disponibles
|
||||
*/
|
||||
static async getTemplatesDisponibles(): Promise<Array<{
|
||||
id: string;
|
||||
nom: string;
|
||||
description: string;
|
||||
formatsSupportes: FormatExport[];
|
||||
sections: string[];
|
||||
}>> {
|
||||
try {
|
||||
const response = await ApiService.get<Array<{
|
||||
id: string;
|
||||
nom: string;
|
||||
description: string;
|
||||
formatsSupportes: FormatExport[];
|
||||
sections: string[];
|
||||
}>>('/calculs-techniques/templates-export');
|
||||
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
// Templates par défaut
|
||||
return [
|
||||
{
|
||||
id: 'STANDARD',
|
||||
nom: 'Standard',
|
||||
description: 'Export standard avec informations essentielles',
|
||||
formatsSupportes: ['CSV', 'EXCEL', 'PDF'],
|
||||
sections: ['donnees_base']
|
||||
},
|
||||
{
|
||||
id: 'DETAILLE',
|
||||
nom: 'Détaillé',
|
||||
description: 'Export complet avec toutes les spécifications techniques',
|
||||
formatsSupportes: ['EXCEL', 'PDF'],
|
||||
sections: ['donnees_base', 'specifications', 'calculs', 'normes']
|
||||
},
|
||||
{
|
||||
id: 'RESUME',
|
||||
nom: 'Résumé',
|
||||
description: 'Export synthétique pour présentation',
|
||||
formatsSupportes: ['PDF'],
|
||||
sections: ['resume', 'graphiques']
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation des options d'export
|
||||
*/
|
||||
static validerOptionsExport(options: OptionsExport): {
|
||||
valide: boolean;
|
||||
erreurs: string[];
|
||||
} {
|
||||
const erreurs: string[] = [];
|
||||
|
||||
if (!options.format) {
|
||||
erreurs.push('Format d\'export requis');
|
||||
}
|
||||
|
||||
if (options.format === 'PDF' && options.colonnesPersonnalisees?.length > 20) {
|
||||
erreurs.push('Maximum 20 colonnes pour export PDF');
|
||||
}
|
||||
|
||||
if (options.includeImages && options.format === 'CSV') {
|
||||
erreurs.push('Images non supportées en format CSV');
|
||||
}
|
||||
|
||||
return {
|
||||
valide: erreurs.length === 0,
|
||||
erreurs
|
||||
};
|
||||
}
|
||||
|
||||
// =================== MÉTHODES PRIVÉES ===================
|
||||
|
||||
/**
|
||||
* Génère un nom de fichier unique
|
||||
*/
|
||||
private static genererNomFichier(prefix: string, format: FormatExport): string {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
||||
const extension = format.toLowerCase();
|
||||
return `${prefix}_${timestamp}.${extension}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export côté client en cas de problème serveur
|
||||
*/
|
||||
private static async exporterCalculsCoteClient(
|
||||
calculs: any[],
|
||||
options: OptionsExport
|
||||
): Promise<ResultatExport> {
|
||||
let contenu = '';
|
||||
|
||||
if (options.format === 'CSV') {
|
||||
// Génération CSV simple
|
||||
contenu = 'Type,Résultat,Date\n';
|
||||
calculs.forEach(calcul => {
|
||||
contenu += `${calcul.type},"${JSON.stringify(calcul.resultat)}",${new Date().toISOString()}\n`;
|
||||
});
|
||||
} else {
|
||||
// Format texte pour autres formats
|
||||
contenu = 'Export des calculs techniques BTP\n\n';
|
||||
calculs.forEach(calcul => {
|
||||
contenu += `Type: ${calcul.type}\n`;
|
||||
contenu += `Résultat: ${JSON.stringify(calcul.resultat, null, 2)}\n\n`;
|
||||
});
|
||||
}
|
||||
|
||||
const blob = new Blob([contenu], { type: 'text/plain;charset=utf-8' });
|
||||
const filename = this.genererNomFichier('calculs-fallback', 'CSV');
|
||||
|
||||
return {
|
||||
filename,
|
||||
blob,
|
||||
size: blob.size,
|
||||
format: 'CSV',
|
||||
nbLignes: calculs.length,
|
||||
dateGeneration: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user