203 lines
7.0 KiB
TypeScript
Executable File
203 lines
7.0 KiB
TypeScript
Executable File
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(); |