e14b333a67
- 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
135 lines
4.6 KiB
Dart
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();
|