import axios from 'axios'; import { API_CONFIG } from '../config/api'; import { FournisseurPhase, ApiResponse } from '../types/btp-extended'; class FournisseurPhaseService { private readonly basePath = '/fournisseurs-phases'; 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 les fournisseurs d'une phase */ async getByPhase(phaseId: string): Promise { if (!phaseId || phaseId === 'undefined' || phaseId === 'null' || phaseId === 'NaN') { console.warn(`ID de phase invalide: ${phaseId}`); return []; } try { const response = await this.api.get(`${this.basePath}/phase/${phaseId}`); return response.data; } catch (error) { console.warn(`Endpoint ${this.basePath}/phase/${phaseId} non disponible:`, error); return []; } } /** * Récupérer un fournisseur de phase par ID */ async getById(id: number): Promise { const response = await this.api.get(`${this.basePath}/${id}`); return response.data; } /** * Créer un nouveau fournisseur de phase */ async create(fournisseurPhase: Omit): Promise { console.log('Creating fournisseur phase with data:', fournisseurPhase); const response = await this.api.post(this.basePath, fournisseurPhase); return response.data; } /** * Modifier un fournisseur de phase existant */ async update(id: number, fournisseurPhase: Partial): Promise { const response = await this.api.put(`${this.basePath}/${id}`, fournisseurPhase); return response.data; } /** * Supprimer un fournisseur de phase */ async delete(id: number): Promise { await this.api.delete(`${this.basePath}/${id}`); } /** * Obtenir les fournisseurs par type de contribution */ async getByTypeContribution(phaseId: string, type: string): Promise { try { const fournisseurs = await this.getByPhase(phaseId); return fournisseurs.filter(f => f.typeContribution === type); } catch (error) { console.error('Erreur lors de la récupération par type:', error); return []; } } /** * Calculer les économies réalisées avec les négociations */ async calculerEconomies(phaseId: string): Promise { try { const fournisseurs = await this.getByPhase(phaseId); return fournisseurs.reduce((total, fournisseur) => { const prixCatalogue = fournisseur.prixCatalogue || 0; const prixNegocie = fournisseur.prixNegocie || prixCatalogue; return total + (prixCatalogue - prixNegocie); }, 0); } catch (error) { console.error('Erreur lors du calcul des économies:', error); return 0; } } /** * Obtenir le fournisseur principal d'une phase */ async getFournisseurPrincipal(phaseId: string): Promise { try { const fournisseurs = await this.getByPhase(phaseId); return fournisseurs.find(f => f.priorite === 1) || null; } catch (error) { console.error('Erreur lors de la récupération du fournisseur principal:', error); return null; } } /** * Lancer un appel d'offres pour une phase */ async lancerAppelOffres(phaseId: string, fournisseurIds: number[]): Promise { try { const response = await this.api.post(`${this.basePath}/appel-offres`, { phaseId, fournisseurIds }); return response.data; } catch (error) { console.warn('Appel d\'offres non disponible:', error); return []; } } /** * Comparer les offres de fournisseurs */ async comparerOffres(phaseId: string): Promise { try { const fournisseurs = await this.getByPhase(phaseId); // Tri par prix négocié croissant return fournisseurs.sort((a, b) => { const prixA = a.prixNegocie || a.prixCatalogue || Infinity; const prixB = b.prixNegocie || b.prixCatalogue || Infinity; return prixA - prixB; }); } catch (error) { console.error('Erreur lors de la comparaison des offres:', error); return []; } } /** * Valider une négociation */ async validerNegociation(id: number, validePar: string): Promise { const response = await this.api.post(`${this.basePath}/${id}/valider`, { validePar, dateValidation: new Date() }); return response.data; } /** * Calculer le score d'un fournisseur (prix, délai, qualité) */ calculerScoreFournisseur(fournisseur: FournisseurPhase): number { let score = 0; // Score prix (40% du total) const prixFinal = fournisseur.prixNegocie || fournisseur.prixCatalogue || 0; const remise = fournisseur.remise || 0; const scoreRemise = Math.min(remise / 100, 0.3); // Max 30% de remise score += scoreRemise * 40; // Score délai (30% du total) const delai = fournisseur.delaiLivraison || 30; const scoreDelai = Math.max(0, (30 - delai) / 30); // Meilleur si délai < 30 jours score += scoreDelai * 30; // Score priorité/historique (30% du total) const priorite = fournisseur.priorite || 5; const scorePriorite = Math.max(0, (6 - priorite) / 5); // Meilleur si priorité = 1 score += scorePriorite * 30; return Math.round(score); } } export default new FournisseurPhaseService();