import axios from 'axios'; import { API_CONFIG } from '../config/api'; import { Fournisseur, FournisseurFormData, FournisseurFilters, CommandeFournisseur, CatalogueItem, TypeFournisseur, ApiResponse, PaginatedResponse } from '../types/btp-extended'; class FournisseurService { private readonly basePath = '/api/v1/fournisseurs'; private api = axios.create({ baseURL: API_CONFIG.baseURL, timeout: API_CONFIG.timeout, headers: API_CONFIG.headers, }); constructor() { // Interceptor pour ajouter le token Keycloak this.api.interceptors.request.use( async (config) => { // Vérifier si Keycloak est initialisé et l'utilisateur authentifié if (typeof window !== 'undefined') { const { keycloak, KEYCLOAK_TIMEOUTS } = await import('../config/keycloak'); if (keycloak.authenticated) { try { // Rafraîchir le token si nécessaire await keycloak.updateToken(KEYCLOAK_TIMEOUTS.TOKEN_REFRESH_BEFORE_EXPIRY); // Ajouter le token Bearer à l'en-tête Authorization if (keycloak.token) { config.headers['Authorization'] = `Bearer ${keycloak.token}`; } } catch (error) { console.error('Erreur lors de la mise à jour du token Keycloak:', error); keycloak.login(); throw error; } } else { // Fallback vers l'ancien système pour la rétrocompatibilité let token = null; try { const authTokenItem = sessionStorage.getItem('auth_token') || localStorage.getItem('auth_token'); if (authTokenItem) { const parsed = JSON.parse(authTokenItem); token = parsed.value; } } catch (e) { token = localStorage.getItem('token'); } if (token) { config.headers['Authorization'] = `Bearer ${token}`; } } } return config; }, (error) => Promise.reject(error) ); // Interceptor pour les réponses this.api.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { localStorage.removeItem('token'); localStorage.removeItem('user'); window.location.href = '/api/auth/login'; } return Promise.reject(error); } ); } /** * Récupérer tous les fournisseurs */ async getAll(filters?: FournisseurFilters): Promise { const params = new URLSearchParams(); if (filters?.actif !== undefined) { params.append('actifs', filters.actif.toString()); } if (filters?.type) { params.append('type', filters.type); } const response = await this.api.get(`${this.basePath}?${params}`); return response.data; } /** * Récupérer un fournisseur par ID */ async getById(id: number): Promise { const response = await this.api.get(`${this.basePath}/${id}`); return response.data; } /** * Créer un nouveau fournisseur */ async create(fournisseur: FournisseurFormData): Promise { const response = await this.api.post(this.basePath, fournisseur); return response.data; } /** * Modifier un fournisseur existant */ async update(id: number, fournisseur: FournisseurFormData): Promise { const response = await this.api.put(`${this.basePath}/${id}`, fournisseur); return response.data; } /** * Supprimer un fournisseur */ async delete(id: number): Promise { await this.api.delete(`${this.basePath}/${id}`); } /** * Désactiver un fournisseur */ async deactivate(id: number): Promise { await this.api.post(`${this.basePath}/${id}/desactiver`); } /** * Activer un fournisseur */ async activate(id: number): Promise { await this.api.post(`${this.basePath}/${id}/activer`); } /** * Rechercher des fournisseurs */ async search(terme: string): Promise { const response = await this.api.get(`${this.basePath}/recherche?q=${encodeURIComponent(terme)}`); return response.data; } /** * Récupérer les types de fournisseurs */ async getTypes(): Promise { const response = await this.api.get(`${this.basePath}/types`); return response.data; } /** * Récupérer les commandes d'un fournisseur */ async getCommandes(id: number): Promise { const response = await this.api.get(`${this.basePath}/${id}/commandes`); return response.data; } /** * Récupérer le catalogue d'un fournisseur */ async getCatalogue(id: number): Promise { const response = await this.api.get(`${this.basePath}/${id}/catalogue`); return response.data; } /** * Récupérer les fournisseurs actifs uniquement */ async getActifs(): Promise { return this.getAll({ actif: true }); } /** * Récupérer les fournisseurs par type */ async getByType(type: TypeFournisseur): Promise { return this.getAll({ type }); } /** * Valider les données d'un fournisseur */ validateFournisseur(fournisseur: FournisseurFormData): string[] { const errors: string[] = []; if (!fournisseur.nom || fournisseur.nom.trim().length === 0) { errors.push('Le nom du fournisseur est obligatoire'); } if (fournisseur.nom && fournisseur.nom.length > 100) { errors.push('Le nom ne peut pas dépasser 100 caractères'); } if (fournisseur.email && !this.isValidEmail(fournisseur.email)) { errors.push('L\'adresse email n\'est pas valide'); } if (fournisseur.siret && fournisseur.siret.length > 20) { errors.push('Le numéro SIRET ne peut pas dépasser 20 caractères'); } if (fournisseur.telephone && fournisseur.telephone.length > 20) { errors.push('Le numéro de téléphone ne peut pas dépasser 20 caractères'); } return errors; } /** * Valider une adresse email */ private isValidEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * Formater l'adresse complète d'un fournisseur */ formatAdresseComplete(fournisseur: Fournisseur): string { const parties: string[] = []; if (fournisseur.adresse) { parties.push(fournisseur.adresse); } if (fournisseur.codePostal || fournisseur.ville) { const ligneVille = [fournisseur.codePostal, fournisseur.ville] .filter(Boolean) .join(' '); if (ligneVille) { parties.push(ligneVille); } } if (fournisseur.pays && fournisseur.pays !== 'France') { parties.push(fournisseur.pays); } return parties.join(', '); } /** * Obtenir le libellé d'un type de fournisseur */ getTypeLabel(type: TypeFournisseur): string { const labels: Record = { MATERIEL: 'Matériel', SERVICE: 'Service', SOUS_TRAITANT: 'Sous-traitant', LOCATION: 'Location', TRANSPORT: 'Transport', CONSOMMABLE: 'Consommable' }; return labels[type] || type; } /** * Exporter la liste des fournisseurs au format CSV */ async exportToCsv(filters?: FournisseurFilters): Promise { const fournisseurs = await this.getAll(filters); const headers = [ 'ID', 'Nom', 'Type', 'SIRET', 'Email', 'Téléphone', 'Adresse', 'Code Postal', 'Ville', 'Pays', 'Actif' ]; const csvContent = [ headers.join(';'), ...fournisseurs.map(f => [ f.id || '', f.nom || '', this.getTypeLabel(f.type), f.siret || '', f.email || '', f.telephone || '', f.adresse || '', f.codePostal || '', f.ville || '', f.pays || '', f.actif ? 'Oui' : 'Non' ].join(';')) ].join('\n'); return new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); } /** * Importer des fournisseurs depuis un fichier CSV */ async importFromCsv(file: File): Promise<{ success: number; errors: string[] }> { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = async (e) => { try { const csv = e.target?.result as string; const lines = csv.split('\n'); const headers = lines[0].split(';'); let successCount = 0; const errors: string[] = []; for (let i = 1; i < lines.length; i++) { if (lines[i].trim()) { try { const values = lines[i].split(';'); const fournisseur: FournisseurFormData = { nom: values[1] || '', type: (values[2] as TypeFournisseur) || 'MATERIEL', siret: values[3] || undefined, email: values[4] || undefined, telephone: values[5] || undefined, adresse: values[6] || undefined, codePostal: values[7] || undefined, ville: values[8] || undefined, pays: values[9] || 'France', actif: values[10] === 'Oui' }; const validationErrors = this.validateFournisseur(fournisseur); if (validationErrors.length === 0) { await this.create(fournisseur); successCount++; } else { errors.push(`Ligne ${i + 1}: ${validationErrors.join(', ')}`); } } catch (error) { errors.push(`Ligne ${i + 1}: Erreur lors de la création`); } } } resolve({ success: successCount, errors }); } catch (error) { reject(error); } }; reader.readAsText(file); }); } } export default new FournisseurService();