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

203 lines
7.0 KiB
TypeScript

import axios from 'axios';
import { API_CONFIG } from '../config/api';
import {
MaterielPhase,
FournisseurPhase,
AnalysePrixPhase,
ApiResponse
} from '../types/btp-extended';
class MaterielPhaseService {
private readonly basePath = '/materiels-phases';
private api = axios.create({
baseURL: API_CONFIG.baseURL,
timeout: API_CONFIG.timeout,
headers: API_CONFIG.headers,
});
constructor() {
// Interceptor pour ajouter le token JWT
this.api.interceptors.request.use(
(config) => {
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 matériels d'une phase
*/
async getByPhase(phaseId: string): Promise<MaterielPhase[]> {
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 matériel de phase par ID
*/
async getById(id: number): Promise<MaterielPhase> {
const response = await this.api.get(`${this.basePath}/${id}`);
return response.data;
}
/**
* Créer un nouveau matériel de phase
*/
async create(materielPhase: Omit<MaterielPhase, 'id'>): Promise<MaterielPhase> {
console.log('Creating materiel phase with data:', materielPhase);
const response = await this.api.post(this.basePath, materielPhase);
return response.data;
}
/**
* Modifier un matériel de phase existant
*/
async update(id: number, materielPhase: Partial<MaterielPhase>): Promise<MaterielPhase> {
const response = await this.api.put(`${this.basePath}/${id}`, materielPhase);
return response.data;
}
/**
* Supprimer un matériel de phase
*/
async delete(id: number): Promise<void> {
await this.api.delete(`${this.basePath}/${id}`);
}
/**
* Calculer le coût total des matériels d'une phase
*/
async calculerCoutTotal(phaseId: string): Promise<number> {
try {
const materiels = await this.getByPhase(phaseId);
return materiels.reduce((total, materiel) => {
const prix = materiel.prixUnitaireNegocie || materiel.prixUnitaireCatalogue || 0;
const quantite = materiel.quantiteUtilisee || materiel.quantitePrevue || 0;
return total + (prix * quantite);
}, 0);
} catch (error) {
console.error('Erreur lors du calcul du coût total:', error);
return 0;
}
}
/**
* Obtenir les matériels en rupture de stock pour une phase
*/
async getMaterielsEnRupture(phaseId: string): Promise<MaterielPhase[]> {
try {
const materiels = await this.getByPhase(phaseId);
return materiels.filter(materiel =>
materiel.enStock === false ||
(materiel.quantiteStock || 0) < (materiel.quantitePrevue || 0)
);
} catch (error) {
console.error('Erreur lors de la vérification du stock:', error);
return [];
}
}
/**
* Obtenir les alternatives de fournisseurs pour un matériel
*/
async getFournisseursAlternatifs(materielPhaseId: number): Promise<FournisseurPhase[]> {
try {
const response = await this.api.get(`${this.basePath}/${materielPhaseId}/fournisseurs-alternatifs`);
return response.data;
} catch (error) {
console.warn('Fournisseurs alternatifs non disponibles:', error);
return [];
}
}
/**
* Négocier un prix avec un fournisseur
*/
async negocierPrix(materielPhaseId: number, fournisseurId: number, prixNegocie: number): Promise<MaterielPhase> {
const response = await this.api.post(`${this.basePath}/${materielPhaseId}/negocier-prix`, {
fournisseurId,
prixNegocie
});
return response.data;
}
/**
* Valider la sélection d'un fournisseur
*/
async validerFournisseur(materielPhaseId: number, fournisseurPhaseId: number): Promise<MaterielPhase> {
const response = await this.api.post(`${this.basePath}/${materielPhaseId}/valider-fournisseur`, {
fournisseurPhaseId
});
return response.data;
}
/**
* Calculer l'analyse de prix pour une phase
*/
async calculerAnalysePrix(phaseId: string): Promise<AnalysePrixPhase> {
try {
const response = await this.api.post(`/analyses-prix/calculer/${phaseId}`);
return response.data;
} catch (error) {
// Calcul côté client si l'endpoint n'existe pas
const materiels = await this.getByPhase(phaseId);
const coutMateriauxTotal = materiels.reduce((total, materiel) => {
const prix = materiel.prixUnitaireNegocie || materiel.prixUnitaireCatalogue || 0;
const quantite = materiel.quantiteUtilisee || materiel.quantitePrevue || 0;
return total + (prix * quantite);
}, 0);
return {
phase: { id: parseInt(phaseId) } as any,
coutMateriauxTotal,
coutMainOeuvreTotal: 0,
coutSousTraitanceTotal: 0,
coutAutresTotal: 0,
coutTotalDirect: coutMateriauxTotal,
coutTotalAvecFrais: coutMateriauxTotal,
prixVenteCalcule: coutMateriauxTotal * 1.2, // Marge de 20% par défaut
dateAnalyse: new Date()
};
}
}
}
export default new MaterielPhaseService();