Merge branch 'feature/travel-cost-calculator' into main
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class DepotModel {
|
||||
final String id;
|
||||
final String name;
|
||||
final String address;
|
||||
final DateTime? createdAt;
|
||||
|
||||
const DepotModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.address,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
factory DepotModel.fromMap(Map<String, dynamic> map, String id) {
|
||||
return DepotModel(
|
||||
id: id,
|
||||
name: (map['name'] ?? '').toString(),
|
||||
address: (map['address'] ?? '').toString(),
|
||||
createdAt: map['createdAt'] is Timestamp
|
||||
? (map['createdAt'] as Timestamp).toDate()
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
factory DepotModel.fromFirestore(DocumentSnapshot doc) {
|
||||
return DepotModel.fromMap(doc.data() as Map<String, dynamic>, doc.id);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'name': name,
|
||||
'address': address,
|
||||
'createdAt': createdAt != null
|
||||
? Timestamp.fromDate(createdAt!)
|
||||
: FieldValue.serverTimestamp(),
|
||||
};
|
||||
}
|
||||
|
||||
DepotModel copyWith({String? id, String? name, String? address}) {
|
||||
return DepotModel(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
address: address ?? this.address,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/// 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<String, dynamic> 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<String, dynamic> map) {
|
||||
return FuelPrices(
|
||||
diesel: _parseDouble(map['diesel'] ?? 1.60),
|
||||
essence: _parseDouble(map['essence'] ?? 1.75),
|
||||
electricite: _parseDouble(map['electricite'] ?? 0.22),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class VehicleModel {
|
||||
final String id;
|
||||
final String name;
|
||||
final double consumptionPer100km; // L/100km (ou kWh/100km si électrique)
|
||||
final String fuelType; // 'Diesel', 'Essence', 'Electrique'
|
||||
final double maintenanceCostPerKm; // €/km
|
||||
final int tollCategoryId; // 1 à 5 (catégorie Ulys)
|
||||
final DateTime? createdAt;
|
||||
|
||||
const VehicleModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.consumptionPer100km,
|
||||
required this.fuelType,
|
||||
required this.maintenanceCostPerKm,
|
||||
required this.tollCategoryId,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
factory VehicleModel.fromMap(Map<String, dynamic> map, String id) {
|
||||
return VehicleModel(
|
||||
id: id,
|
||||
name: (map['name'] ?? '').toString(),
|
||||
consumptionPer100km: _parseDouble(map['consumptionPer100km'] ?? 0),
|
||||
fuelType: (map['fuelType'] ?? 'Diesel').toString(),
|
||||
maintenanceCostPerKm: _parseDouble(map['maintenanceCostPerKm'] ?? 0),
|
||||
tollCategoryId: _parseInt(map['tollCategoryId'] ?? 2),
|
||||
createdAt: map['createdAt'] is Timestamp
|
||||
? (map['createdAt'] as Timestamp).toDate()
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
factory VehicleModel.fromFirestore(DocumentSnapshot doc) {
|
||||
return VehicleModel.fromMap(
|
||||
doc.data() as Map<String, dynamic>,
|
||||
doc.id,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'name': name,
|
||||
'consumptionPer100km': consumptionPer100km,
|
||||
'fuelType': fuelType,
|
||||
'maintenanceCostPerKm': maintenanceCostPerKm,
|
||||
'tollCategoryId': tollCategoryId,
|
||||
'createdAt': createdAt != null
|
||||
? Timestamp.fromDate(createdAt!)
|
||||
: FieldValue.serverTimestamp(),
|
||||
};
|
||||
}
|
||||
|
||||
VehicleModel copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
double? consumptionPer100km,
|
||||
String? fuelType,
|
||||
double? maintenanceCostPerKm,
|
||||
int? tollCategoryId,
|
||||
}) {
|
||||
return VehicleModel(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
consumptionPer100km: consumptionPer100km ?? this.consumptionPer100km,
|
||||
fuelType: fuelType ?? this.fuelType,
|
||||
maintenanceCostPerKm: maintenanceCostPerKm ?? this.maintenanceCostPerKm,
|
||||
tollCategoryId: tollCategoryId ?? this.tollCategoryId,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Label lisible pour l'unité de consommation
|
||||
String get consumptionUnit {
|
||||
if (fuelType == 'Electrique') return 'kWh/100km';
|
||||
return 'L/100km';
|
||||
}
|
||||
|
||||
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) ?? 2;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user