import { chantierService as apiChantierService, apiService } from './api'; import { Chantier } from '../types/btp'; import { ChantierFormData } from '../types/chantier-form'; class ChantierService { /** * Récupérer tous les chantiers */ async getAll(): Promise { return await apiChantierService.getAll(); } /** * Récupérer un chantier par ID */ async getById(id: string): Promise { return await apiChantierService.getById(id); } /** * Créer un nouveau chantier */ async create(chantier: ChantierFormData): Promise { return await apiChantierService.create(chantier as any); } /** * Modifier un chantier existant */ async update(id: string, chantier: ChantierFormData): Promise { return await apiChantierService.update(id, chantier as any); } /** * 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 { await apiChantierService.delete(id, permanent); } /** * Récupérer les chantiers d'un client */ async getByClient(clientId: string): Promise { 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.client as any)?.id === clientId || (chantier as any).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 { return await apiChantierService.getRecents() as any; } /** * Récupérer les chantiers par statut */ async getByStatut(statut: string): Promise { const allChantiers = await this.getAll(); return allChantiers.filter(c => c.statut === statut); } /** * Récupérer les chantiers en retard */ async getEnRetard(): Promise { const allChantiers = await this.getAll(); return allChantiers.filter(c => this.isEnRetard(c)); } /** * Récupérer les chantiers actifs */ async getActifs(): Promise { return await this.getByStatut('EN_COURS'); } /** * Actions sur les chantiers (nécessitent des endpoints spécifiques) */ async demarrer(id: string): Promise { // 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 { throw new Error('Méthode non implémentée côté API'); } async suspendre(id: string): Promise { throw new Error('Méthode non implémentée côté API'); } async reprendre(id: string): Promise { throw new Error('Méthode non implémentée côté API'); } async annuler(id: string): Promise { 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 = { 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 = { 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 { 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();