Files
btpxpress-frontend/services/fournisseurPhaseService.ts
2025-10-13 05:29:32 +02:00

239 lines
8.5 KiB
TypeScript

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<FournisseurPhase[]> {
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<FournisseurPhase> {
const response = await this.api.get(`${this.basePath}/${id}`);
return response.data;
}
/**
* Créer un nouveau fournisseur de phase
*/
async create(fournisseurPhase: Omit<FournisseurPhase, 'id'>): Promise<FournisseurPhase> {
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<FournisseurPhase>): Promise<FournisseurPhase> {
const response = await this.api.put(`${this.basePath}/${id}`, fournisseurPhase);
return response.data;
}
/**
* Supprimer un fournisseur de phase
*/
async delete(id: number): Promise<void> {
await this.api.delete(`${this.basePath}/${id}`);
}
/**
* Obtenir les fournisseurs par type de contribution
*/
async getByTypeContribution(phaseId: string, type: string): Promise<FournisseurPhase[]> {
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<number> {
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<FournisseurPhase | null> {
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<FournisseurPhase[]> {
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<FournisseurPhase[]> {
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<FournisseurPhase> {
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();