Files
btpxpress-frontend/services/chantierService.ts

348 lines
11 KiB
TypeScript
Executable File

import { chantierService as apiChantierService, apiService } from './api';
import { Chantier, ChantierFormData } from '../types/btp';
class ChantierService {
/**
* Récupérer tous les chantiers
*/
async getAll(): Promise<Chantier[]> {
return await apiChantierService.getAll();
}
/**
* Récupérer un chantier par ID
*/
async getById(id: string): Promise<Chantier> {
return await apiChantierService.getById(id);
}
/**
* Créer un nouveau chantier
*/
async create(chantier: ChantierFormData): Promise<Chantier> {
return await apiChantierService.create(chantier);
}
/**
* Modifier un chantier existant
*/
async update(id: string, chantier: ChantierFormData): Promise<Chantier> {
return await apiChantierService.update(id, chantier);
}
/**
* Supprimer un chantier
* @param id - ID du chantier
* @param permanent - Si true, suppression physique définitive. Si false, suppression logique (défaut)
*/
async delete(id: string, permanent: boolean = false): Promise<void> {
await apiChantierService.delete(id, permanent);
}
/**
* Récupérer les chantiers d'un client
*/
async getByClient(clientId: string): Promise<Chantier[]> {
try {
return await apiChantierService.getByClient(clientId);
} catch (error: any) {
// Si l'endpoint spécifique n'existe pas (404), essayer de filtrer tous les chantiers
if (error.status === 404 || error.response?.status === 404) {
try {
const tousLesChantiers = await this.getAll();
return tousLesChantiers.filter(chantier => chantier.clientId === clientId);
} catch (fallbackError) {
console.debug('Fallback sur filtrage côté client également impossible, retour liste vide');
// Retourner une liste vide plutôt qu'une erreur si pas de chantiers
return [];
}
}
// Pour toute autre erreur, la remonter
throw error;
}
}
/**
* Récupérer les chantiers récents
*/
async getRecents(): Promise<Chantier[]> {
return await apiChantierService.getRecents();
}
/**
* Récupérer les chantiers par statut
*/
async getByStatut(statut: string): Promise<Chantier[]> {
const allChantiers = await this.getAll();
return allChantiers.filter(c => c.statut === statut);
}
/**
* Récupérer les chantiers en retard
*/
async getEnRetard(): Promise<Chantier[]> {
const allChantiers = await this.getAll();
return allChantiers.filter(c => this.isEnRetard(c));
}
/**
* Récupérer les chantiers actifs
*/
async getActifs(): Promise<Chantier[]> {
return await this.getByStatut('EN_COURS');
}
/**
* Actions sur les chantiers (nécessitent des endpoints spécifiques)
*/
async demarrer(id: string): Promise<void> {
// Ces méthodes nécessiteraient des endpoints spécifiques dans l'API
throw new Error('Méthode non implémentée côté API');
}
async terminer(id: string): Promise<void> {
throw new Error('Méthode non implémentée côté API');
}
async suspendre(id: string): Promise<void> {
throw new Error('Méthode non implémentée côté API');
}
async reprendre(id: string): Promise<void> {
throw new Error('Méthode non implémentée côté API');
}
async annuler(id: string): Promise<void> {
throw new Error('Méthode non implémentée côté API');
}
/**
* Valider les données d'un chantier
*/
validateChantier(chantier: ChantierFormData): string[] {
const errors: string[] = [];
if (!chantier.nom || chantier.nom.trim().length === 0) {
errors.push('Le nom du chantier est obligatoire');
}
if (!chantier.adresse || chantier.adresse.trim().length === 0) {
errors.push('L\'adresse est obligatoire');
}
if (!chantier.clientId) {
errors.push('Le client est obligatoire');
}
if (!chantier.dateDebut) {
errors.push('La date de début est obligatoire');
}
if (!chantier.dateFinPrevue) {
errors.push('La date de fin prévue est obligatoire');
}
if (chantier.dateDebut && chantier.dateFinPrevue) {
const debut = new Date(chantier.dateDebut);
const fin = new Date(chantier.dateFinPrevue);
if (debut >= fin) {
errors.push('La date de fin doit être postérieure à la date de début');
}
}
if (chantier.montantPrevu !== undefined && chantier.montantPrevu < 0) {
errors.push('Le montant prévu doit être positif');
}
return errors;
}
/**
* Calculer la durée prévue du chantier en jours
*/
calculateDureePrevue(chantier: Chantier): number {
if (!chantier.dateDebut || !chantier.dateFinPrevue) return 0;
const debut = new Date(chantier.dateDebut);
const fin = new Date(chantier.dateFinPrevue);
const diffTime = Math.abs(fin.getTime() - debut.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
/**
* Calculer la durée réelle du chantier en jours
*/
calculateDureeReelle(chantier: Chantier): number {
if (!chantier.dateDebut) return 0;
const debut = new Date(chantier.dateDebut);
const fin = chantier.dateFinReelle ? new Date(chantier.dateFinReelle) : new Date();
const diffTime = Math.abs(fin.getTime() - debut.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
/**
* Vérifier si un chantier est en retard
*/
isEnRetard(chantier: Chantier): boolean {
if (chantier.statut === 'TERMINE') return false;
if (!chantier.dateFinPrevue) return false;
return new Date() > new Date(chantier.dateFinPrevue);
}
/**
* Calculer le retard en jours
*/
calculateRetard(chantier: Chantier): number {
if (!this.isEnRetard(chantier)) return 0;
const dateFinPrevue = new Date(chantier.dateFinPrevue!);
const maintenant = new Date();
const diffTime = maintenant.getTime() - dateFinPrevue.getTime();
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
/**
* Calculer l'avancement du chantier en pourcentage
*/
calculateAvancement(chantier: Chantier): number {
if (chantier.statut === 'TERMINE') return 100;
if (chantier.statut === 'ANNULE') return 0;
if (!chantier.dateDebut || !chantier.dateFinPrevue) return 0;
const now = new Date();
const start = new Date(chantier.dateDebut);
const end = new Date(chantier.dateFinPrevue);
if (now < start) return 0;
if (now > end) return 100;
const totalDays = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
const elapsedDays = (now.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
return Math.min(Math.max((elapsedDays / totalDays) * 100, 0), 100);
}
/**
* Obtenir le libellé d'un statut
*/
getStatutLabel(statut: string): string {
const labels: Record<string, string> = {
PLANIFIE: 'Planifié',
EN_COURS: 'En cours',
TERMINE: 'Terminé',
ANNULE: 'Annulé',
SUSPENDU: 'Suspendu'
};
return labels[statut] || statut;
}
/**
* Obtenir la couleur d'un statut
*/
getStatutColor(statut: string): string {
const colors: Record<string, string> = {
PLANIFIE: '#6c757d',
EN_COURS: '#0d6efd',
TERMINE: '#198754',
ANNULE: '#dc3545',
SUSPENDU: '#fd7e14'
};
return colors[statut] || '#6c757d';
}
/**
* Calculer les statistiques des chantiers
*/
calculateStatistiques(chantiers: Chantier[]): {
total: number;
planifies: number;
enCours: number;
termines: number;
annules: number;
suspendus: number;
enRetard: number;
montantTotal: number;
coutTotal: number;
} {
const stats = {
total: chantiers.length,
planifies: 0,
enCours: 0,
termines: 0,
annules: 0,
suspendus: 0,
enRetard: 0,
montantTotal: 0,
coutTotal: 0
};
chantiers.forEach(chantier => {
// Compter par statut
switch (chantier.statut) {
case 'PLANIFIE':
stats.planifies++;
break;
case 'EN_COURS':
stats.enCours++;
break;
case 'TERMINE':
stats.termines++;
break;
case 'ANNULE':
stats.annules++;
break;
case 'SUSPENDU':
stats.suspendus++;
break;
}
// Vérifier les retards
if (this.isEnRetard(chantier)) {
stats.enRetard++;
}
// Calculer montants
stats.montantTotal += chantier.montantPrevu || 0;
stats.coutTotal += chantier.montantReel || 0;
});
return stats;
}
/**
* Exporter les chantiers au format CSV
*/
async exportToCsv(): Promise<Blob> {
const chantiers = await this.getAll();
const headers = [
'ID', 'Nom', 'Description', 'Adresse', 'Client', 'Statut',
'Date Début', 'Date Fin Prévue', 'Date Fin Réelle',
'Montant Prévu', 'Montant Réel', 'Actif'
];
const csvContent = [
headers.join(';'),
...chantiers.map(c => [
c.id || '',
c.nom || '',
c.description || '',
c.adresse || '',
c.client ? `${c.client.prenom} ${c.client.nom}` : '',
this.getStatutLabel(c.statut),
c.dateDebut ? new Date(c.dateDebut).toLocaleDateString('fr-FR') : '',
c.dateFinPrevue ? new Date(c.dateFinPrevue).toLocaleDateString('fr-FR') : '',
c.dateFinReelle ? new Date(c.dateFinReelle).toLocaleDateString('fr-FR') : '',
c.montantPrevu || 0,
c.montantReel || 0,
c.actif ? 'Oui' : 'Non'
].join(';'))
].join('\n');
return new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
}
}
export default new ChantierService();