/// Résultat d'un itinéraire calculé par Google Maps + Ulys. class RouteResult { /// 'TOLL' ou 'TOLL_FREE' final String routeType; final int distanceMeters; final int durationSeconds; final String encodedPolyline; final double tollCost; const RouteResult({ required this.routeType, required this.distanceMeters, required this.durationSeconds, required this.encodedPolyline, required this.tollCost, }); factory RouteResult.fromMap(Map map) { return RouteResult( routeType: (map['routeType'] ?? 'TOLL').toString(), distanceMeters: _parseInt(map['distanceMeters'] ?? 0), durationSeconds: _parseInt(map['durationSeconds'] ?? 0), encodedPolyline: (map['encodedPolyline'] ?? '').toString(), tollCost: _parseDouble(map['tollCost'] ?? 0), ); } bool get isTollFree => routeType == 'TOLL_FREE'; double get distanceKm => distanceMeters / 1000.0; double get durationMinutes => durationSeconds / 60.0; double get durationHours => durationSeconds / 3600.0; /// Calcule le coût carburant. /// [consumptionPer100km] : L/100km (ou kWh/100km si électrique) /// [fuelPricePerLiter] : €/L ou €/kWh /// [freeZoneKm] : km gratuits à déduire (zone de gratuité) double fuelCost({ required double consumptionPer100km, required double fuelPricePerLiter, double freeZoneKm = 0, }) { final effectiveKm = (distanceKm - freeZoneKm).clamp(0, double.infinity); return (effectiveKm / 100.0) * consumptionPer100km * fuelPricePerLiter; } /// Calcule le coût de maintenance. double maintenanceCost({ required double costPerKm, double freeZoneKm = 0, }) { final effectiveKm = (distanceKm - freeZoneKm).clamp(0, double.infinity); return effectiveKm * costPerKm; } /// Calcule le coût de main-d'œuvre (techniciens). /// [freeZoneMinutes] : minutes gratuites à déduire (zone de gratuité) double laborCost({ required int nbTechnicians, required double hourlyRate, double freeZoneMinutes = 0, }) { final effectiveMinutes = (durationMinutes - freeZoneMinutes).clamp(0, double.infinity); return (effectiveMinutes / 60.0) * nbTechnicians * hourlyRate; } /// Calcule le coût total pour un aller simple. double totalCost({ required double consumptionPer100km, required double fuelPricePerLiter, required double maintenanceCostPerKm, required int nbTechnicians, required double hourlyRate, bool applyFreeZone = false, }) { const freeKm = 20.0; const freeMinutes = 20.0; return fuelCost( consumptionPer100km: consumptionPer100km, fuelPricePerLiter: fuelPricePerLiter, freeZoneKm: applyFreeZone ? freeKm : 0, ) + maintenanceCost( costPerKm: maintenanceCostPerKm, freeZoneKm: applyFreeZone ? freeKm : 0, ) + laborCost( nbTechnicians: nbTechnicians, hourlyRate: hourlyRate, freeZoneMinutes: applyFreeZone ? freeMinutes : 0, ) + tollCost; } static double _parseDouble(dynamic v) { if (v is double) return v; if (v is int) return v.toDouble(); if (v is String) return double.tryParse(v) ?? 0.0; return 0.0; } static int _parseInt(dynamic v) { if (v is int) return v; if (v is double) return v.toInt(); if (v is String) return int.tryParse(v) ?? 0; return 0; } } /// Prix des carburants (stocké dans Firestore app_config/fuel_prices) class FuelPrices { final double diesel; // €/L final double essence; // €/L final double electricite; // €/kWh const FuelPrices({ this.diesel = 1.60, this.essence = 1.75, this.electricite = 0.22, }); factory FuelPrices.fromMap(Map map) { return FuelPrices( diesel: _parseDouble(map['diesel'] ?? 1.60), essence: _parseDouble(map['essence'] ?? 1.75), electricite: _parseDouble(map['electricite'] ?? 0.22), ); } Map toMap() => { 'diesel': diesel, 'essence': essence, 'electricite': electricite, }; double priceForFuelType(String fuelType) { switch (fuelType.toLowerCase()) { case 'diesel': return diesel; case 'essence': return essence; case 'electrique': case 'électrique': return electricite; default: return diesel; } } static double _parseDouble(dynamic v) { if (v is double) return v; if (v is int) return v.toDouble(); if (v is String) return double.tryParse(v) ?? 0.0; return 0.0; } }