Initial commit
This commit is contained in:
348
services/chantierService.ts
Normal file
348
services/chantierService.ts
Normal file
@@ -0,0 +1,348 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user