Initial commit
This commit is contained in:
428
services/phaseService.ts
Normal file
428
services/phaseService.ts
Normal file
@@ -0,0 +1,428 @@
|
||||
import axios from 'axios';
|
||||
import { API_CONFIG } from '../config/api';
|
||||
import {
|
||||
PhaseChantier,
|
||||
PhaseChantierFormData,
|
||||
PhaseFilters,
|
||||
JalonPhase,
|
||||
PointagePhase,
|
||||
StatutPhase,
|
||||
ApiResponse
|
||||
} from '../types/btp-extended';
|
||||
import { MaterielBTPService, MaterielBTP, CategorieMateriel } from './materielBTPService';
|
||||
import { CalculsTechniquesService, ParametresCalculBriques, ResultatCalculBriques } from './calculsTechniquesService';
|
||||
import { ZoneClimatiqueService, ZoneClimatique } from './zoneClimatiqueService';
|
||||
|
||||
class PhaseService {
|
||||
private readonly basePath = '/api/v1/phases-chantier';
|
||||
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 = '/auth/login';
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les phases d'un chantier
|
||||
*/
|
||||
async getByChantier(chantierId: string): Promise<PhaseChantier[]> {
|
||||
// Vérifier si l'ID est valide
|
||||
if (!chantierId || chantierId === 'undefined' || chantierId === 'null' || chantierId === 'NaN') {
|
||||
console.warn(`ID de chantier invalide: ${chantierId}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`🔍 Recherche des phases pour chantier: ${chantierId}`);
|
||||
// Ajouter un timestamp pour éviter le cache
|
||||
const response = await this.api.get(`${this.basePath}/chantier/${chantierId}`, {
|
||||
params: {
|
||||
_t: Date.now() // Forcer le rafraîchissement
|
||||
}
|
||||
});
|
||||
console.log(`✅ ${response.data.length} phases récupérées pour chantier ${chantierId}:`, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// Pour l'instant, retourner des données vides si l'endpoint n'existe pas
|
||||
console.warn(`❌ Endpoint ${this.basePath}/chantier/${chantierId} non disponible:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer toutes les phases
|
||||
*/
|
||||
async getAll(): Promise<PhaseChantier[]> {
|
||||
try {
|
||||
const response = await this.api.get(this.basePath);
|
||||
console.log('Toutes les phases récupérées:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.warn('Erreur lors de la récupération de toutes les phases:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer une phase par ID
|
||||
*/
|
||||
async getById(id: string): Promise<PhaseChantier> {
|
||||
const response = await this.api.get(`${this.basePath}/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer une nouvelle phase
|
||||
*/
|
||||
async create(phase: PhaseChantierFormData): Promise<PhaseChantier> {
|
||||
// D'abord récupérer les phases existantes pour calculer l'ordre d'exécution
|
||||
const chantierId = phase.chantier?.id || phase.chantierId;
|
||||
if (!chantierId) {
|
||||
throw new Error('ID du chantier requis');
|
||||
}
|
||||
|
||||
// Récupérer les phases existantes pour calculer le prochain ordre
|
||||
const existingPhases = await this.getByChantier(chantierId);
|
||||
const nextOrder = existingPhases.length > 0
|
||||
? Math.max(...existingPhases.map(p => p.ordreExecution || 0)) + 1
|
||||
: 1;
|
||||
|
||||
// S'assurer que chantierId est inclus dans la requête
|
||||
// et mapper phaseParent vers phaseParentId pour le backend
|
||||
const requestData = {
|
||||
...phase,
|
||||
chantierId: chantierId,
|
||||
ordreExecution: phase.ordreExecution || nextOrder,
|
||||
phaseParentId: phase.phaseParent || phase.phaseParentId
|
||||
};
|
||||
// Supprimer phaseParent si il existe pour éviter la confusion
|
||||
delete requestData.phaseParent;
|
||||
|
||||
console.log('🚀 Création de phase avec données:', requestData);
|
||||
const response = await this.api.post(this.basePath, requestData);
|
||||
console.log('✅ Phase créée avec succès:', response.data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier une phase existante
|
||||
*/
|
||||
async update(id: string, phase: PhaseChantierFormData): Promise<PhaseChantier> {
|
||||
const response = await this.api.put(`${this.basePath}/${id}`, phase);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprimer une phase
|
||||
*/
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.api.delete(`${this.basePath}/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Générer des phases à partir d'un template
|
||||
*/
|
||||
async generateFromTemplate(
|
||||
chantierId: number,
|
||||
templateId: string,
|
||||
configuration: {
|
||||
phasesSelectionnees: any[];
|
||||
configurationsPersonnalisees?: any;
|
||||
optionsAvancees?: any;
|
||||
dateDebutSouhaitee?: string;
|
||||
dureeGlobale?: number;
|
||||
}
|
||||
): Promise<PhaseChantier[]> {
|
||||
try {
|
||||
console.log('Génération de phases depuis template:', { chantierId, templateId, configuration });
|
||||
|
||||
// Pour l'instant, on utilise la génération locale basée sur les templates
|
||||
// Cette méthode peut être remplacée par un appel API backend plus tard
|
||||
const phasesGenerees: PhaseChantier[] = [];
|
||||
|
||||
// Récupérer le template et générer les phases
|
||||
const typeChantierService = await import('./typeChantierService');
|
||||
const templates = await typeChantierService.default.getAllTemplates();
|
||||
const template = templates.find(t => t.id === templateId);
|
||||
|
||||
if (!template) {
|
||||
throw new Error(`Template ${templateId} non trouvé`);
|
||||
}
|
||||
|
||||
// Générer les phases sélectionnées depuis la configuration (avec données utilisateur)
|
||||
for (let i = 0; i < configuration.phasesSelectionnees.length; i++) {
|
||||
const phaseSelectionnee = configuration.phasesSelectionnees[i];
|
||||
|
||||
// Utiliser les données personnalisées (saisies par l'utilisateur)
|
||||
const phaseData: any = {
|
||||
nom: phaseSelectionnee.nom,
|
||||
description: phaseSelectionnee.description,
|
||||
chantierId: chantierId.toString(),
|
||||
statut: 'PLANIFIEE' as StatutPhase,
|
||||
ordreExecution: phaseSelectionnee.ordre,
|
||||
dureeEstimeeHeures: (phaseSelectionnee.dureeEstimee || 1) * 8, // Convertir jours en heures (défaut 1 jour)
|
||||
budgetPrevu: phaseSelectionnee.budgetEstime || 0,
|
||||
coutReel: 0,
|
||||
priorite: phaseSelectionnee.categorieMetier === 'GROS_OEUVRE' ? 'CRITIQUE' : 'MOYENNE',
|
||||
critique: phaseSelectionnee.obligatoire,
|
||||
dateDebutPrevue: configuration.dateDebutSouhaitee,
|
||||
competencesRequises: phaseSelectionnee.competencesRequises || [],
|
||||
materielsNecessaires: [],
|
||||
fournisseursRecommandes: []
|
||||
};
|
||||
|
||||
// Créer la phase via l'API
|
||||
const phaseCreee = await this.create(phaseData);
|
||||
phasesGenerees.push(phaseCreee);
|
||||
|
||||
// Créer les sous-phases si elles existent
|
||||
if (phaseSelectionnee.sousPhases && phaseSelectionnee.sousPhases.length > 0) {
|
||||
for (let j = 0; j < phaseSelectionnee.sousPhases.length; j++) {
|
||||
const sousPhaseTemplate = phaseSelectionnee.sousPhases[j];
|
||||
|
||||
const sousPhaseData: any = {
|
||||
nom: sousPhaseTemplate.nom,
|
||||
description: sousPhaseTemplate.description,
|
||||
chantierId: chantierId.toString(),
|
||||
statut: 'PLANIFIEE' as StatutPhase,
|
||||
phaseParentId: phaseCreee.id,
|
||||
ordreExecution: sousPhaseTemplate.ordre,
|
||||
dureeEstimeeHeures: (sousPhaseTemplate.dureeEstimee || 1) * 8,
|
||||
budgetPrevu: sousPhaseTemplate.budgetEstime || 0,
|
||||
coutReel: 0,
|
||||
priorite: 'MOYENNE',
|
||||
critique: sousPhaseTemplate.obligatoire,
|
||||
competencesRequises: sousPhaseTemplate.competencesRequises || [],
|
||||
materielsNecessaires: [],
|
||||
fournisseursRecommandes: []
|
||||
};
|
||||
|
||||
const sousPhaseCreee = await this.create(sousPhaseData);
|
||||
phasesGenerees.push(sousPhaseCreee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Phases générées avec succès:', phasesGenerees);
|
||||
return phasesGenerees;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la génération depuis template:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarrer une phase
|
||||
*/
|
||||
async start(id: string): Promise<void> {
|
||||
await this.api.post(`${this.basePath}/${id}/demarrer`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminer une phase
|
||||
*/
|
||||
async complete(id: string): Promise<void> {
|
||||
await this.api.post(`${this.basePath}/${id}/terminer`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspendre une phase
|
||||
*/
|
||||
async suspend(id: string): Promise<void> {
|
||||
await this.api.post(`${this.basePath}/${id}/suspendre`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reprendre une phase suspendue
|
||||
*/
|
||||
async resume(id: string): Promise<void> {
|
||||
await this.api.post(`${this.basePath}/${id}/reprendre`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mettre à jour l'avancement d'une phase
|
||||
*/
|
||||
async updateProgress(id: string, pourcentage: number): Promise<void> {
|
||||
await this.api.put(`${this.basePath}/${id}/avancement?pourcentage=${pourcentage}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias français pour start()
|
||||
*/
|
||||
async demarrer(id: string): Promise<void> {
|
||||
return this.start(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias français pour complete()
|
||||
*/
|
||||
async terminer(id: string): Promise<void> {
|
||||
return this.complete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias français pour suspend()
|
||||
*/
|
||||
async suspendre(id: string, motif?: string): Promise<void> {
|
||||
return this.suspend(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias français pour resume()
|
||||
*/
|
||||
async reprendre(id: string): Promise<void> {
|
||||
return this.resume(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias français pour updateProgress()
|
||||
*/
|
||||
async updateAvancement(id: string, pourcentage: number): Promise<void> {
|
||||
return this.updateProgress(id, pourcentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les statuts disponibles
|
||||
*/
|
||||
async getStatuts(): Promise<StatutPhase[]> {
|
||||
const response = await this.api.get(`${this.basePath}/statuts`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les phases en retard
|
||||
*/
|
||||
async getEnRetard(): Promise<PhaseChantier[]> {
|
||||
const response = await this.api.get(`${this.basePath}/en-retard`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les phases d'un responsable
|
||||
*/
|
||||
async getByResponsable(employeId: number): Promise<PhaseChantier[]> {
|
||||
const response = await this.api.get(`${this.basePath}/responsable/${employeId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les jalons d'une phase
|
||||
*/
|
||||
async getJalons(phaseId: number): Promise<JalonPhase[]> {
|
||||
const response = await this.api.get(`${this.basePath}/${phaseId}/jalons`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupérer les pointages d'une phase
|
||||
*/
|
||||
async getPointages(phaseId: number): Promise<PointagePhase[]> {
|
||||
const response = await this.api.get(`${this.basePath}/${phaseId}/pointages`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le libellé d'un statut
|
||||
*/
|
||||
getStatutLabel(statut: StatutPhase): string {
|
||||
const labels: Record<StatutPhase, string> = {
|
||||
PLANIFIEE: 'Planifiée',
|
||||
EN_ATTENTE: 'En attente',
|
||||
EN_COURS: 'En cours',
|
||||
EN_PAUSE: 'En pause',
|
||||
TERMINEE: 'Terminée',
|
||||
ANNULEE: 'Annulée',
|
||||
EN_RETARD: 'En retard'
|
||||
};
|
||||
return labels[statut] || statut;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir la couleur d'un statut
|
||||
*/
|
||||
getStatutColor(statut: StatutPhase): string {
|
||||
const colors: Record<StatutPhase, string> = {
|
||||
PLANIFIEE: '#6c757d',
|
||||
EN_ATTENTE: '#ffc107',
|
||||
EN_COURS: '#0d6efd',
|
||||
EN_PAUSE: '#fd7e14',
|
||||
TERMINEE: '#198754',
|
||||
ANNULEE: '#dc3545',
|
||||
EN_RETARD: '#dc3545'
|
||||
};
|
||||
return colors[statut] || '#6c757d';
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si une phase est en retard
|
||||
*/
|
||||
isEnRetard(phase: PhaseChantier): boolean {
|
||||
if (phase.statut === 'TERMINEE') return false;
|
||||
if (!phase.dateFinPrevue) return false;
|
||||
|
||||
return new Date() > new Date(phase.dateFinPrevue);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default new PhaseService();
|
||||
Reference in New Issue
Block a user