Files
EM2_ERP/em2rp/lib/services/travel_service.dart
T
ElPoyo e14b333a67 feat: calculateur de frais de déplacement - backend et modèles Flutter
- Cloud Function travel.js : autocomplete Google Places + calcul itinéraires
  via Google Routes API avec péages Ulys /legs (precision=6) + /rate
- Modèles : VehicleModel, DepotModel, RouteResultModel + FuelPrices
- Services : VehicleService, TravelService (Firestore CRUD + API calls)
- Gestion des données : 3 nouveaux onglets (Dépôts, Véhicules, Prix carburants)
- Autocomplétion adresse dans le formulaire événement
- Dialog calcul frais : config + carte flutter_map OSM + sélection itinéraire
- Injection option FRAIS_KM dans l'événement à la sélection
- flutter_map 7.0.2 + latlong2 0.9.1 ajoutés
- npm: csv-parser + @mapbox/polyline installés dans functions
2026-06-04 14:28:22 +02:00

135 lines
4.6 KiB
Dart

import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:http/http.dart' as http;
import 'package:em2rp/config/api_config.dart';
import 'package:em2rp/models/depot_model.dart';
import 'package:em2rp/models/route_result_model.dart';
import 'package:em2rp/utils/debug_log.dart';
class TravelService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
// ─── Auth token ───────────────────────────────────────────
Future<String?> _getToken() async {
final user = FirebaseAuth.instance.currentUser;
return await user?.getIdToken();
}
Future<Map<String, String>> _headers() async {
final token = await _getToken();
return {
'Content-Type': 'application/json',
if (token != null) 'Authorization': 'Bearer $token',
};
}
// ─── Autocomplétion d'adresses ────────────────────────────
Future<List<String>> autocompleteAddress(String query) async {
if (query.trim().length < 3) return [];
try {
final headers = await _headers();
final url = Uri.parse('${ApiConfig.baseUrl}/googleMapsAutocomplete');
final response = await http.post(
url,
headers: headers,
body: jsonEncode({'data': {'query': query}}),
);
if (response.statusCode != 200) return [];
final data = jsonDecode(response.body) as Map<String, dynamic>;
final predictions = data['predictions'] as List<dynamic>? ?? [];
return predictions
.map((p) => (p['description'] ?? '').toString())
.where((s) => s.isNotEmpty)
.toList();
} catch (e) {
DebugLog.error('[Travel] autocompleteAddress error', e);
return [];
}
}
// ─── Calcul des itinéraires ───────────────────────────────
Future<List<RouteResult>> computeRoutes({
required String origin,
required String destination,
int vehicleTollCategory = 2,
}) async {
try {
final headers = await _headers();
final url = Uri.parse('${ApiConfig.baseUrl}/googleMapsComputeRoute');
final response = await http.post(
url,
headers: headers,
body: jsonEncode({
'data': {
'origin': origin,
'destination': destination,
'vehicleTollCategory': vehicleTollCategory,
},
}),
);
if (response.statusCode != 200) {
final err = jsonDecode(response.body);
throw Exception('googleMapsComputeRoute: ${err['error']}');
}
final data = jsonDecode(response.body) as Map<String, dynamic>;
final routes = data['routes'] as List<dynamic>? ?? [];
return routes
.map((r) => RouteResult.fromMap(r as Map<String, dynamic>))
.toList();
} catch (e) {
DebugLog.error('[Travel] computeRoutes error', e);
rethrow;
}
}
// ─── Prix des carburants ───────────────────────────────────
Future<FuelPrices> getFuelPrices() async {
try {
final doc = await _db.collection('app_config').doc('fuel_prices').get();
if (!doc.exists) return const FuelPrices();
return FuelPrices.fromMap(doc.data()!);
} catch (e) {
return const FuelPrices();
}
}
Future<void> saveFuelPrices(FuelPrices prices) async {
await _db.collection('app_config').doc('fuel_prices').set(prices.toMap());
}
// ─── Dépôts ───────────────────────────────────────────────
Future<List<DepotModel>> getDepots() async {
final snap = await _db.collection('depots').orderBy('name').get();
return snap.docs.map((d) => DepotModel.fromFirestore(d)).toList();
}
Stream<List<DepotModel>> watchDepots() {
return _db
.collection('depots')
.orderBy('name')
.snapshots()
.map((s) => s.docs.map((d) => DepotModel.fromFirestore(d)).toList());
}
Future<String> addDepot(DepotModel depot) async {
final ref = await _db.collection('depots').add(depot.toMap());
return ref.id;
}
Future<void> updateDepot(DepotModel depot) async {
final map = depot.toMap();
map.remove('createdAt');
await _db.collection('depots').doc(depot.id).update(map);
}
Future<void> deleteDepot(String depotId) async {
await _db.collection('depots').doc(depotId).delete();
}
}
/// Instance singleton
final travelService = TravelService();