diff --git a/lib/data/mapper/car_mapper.dart b/lib/data/mapper/car_mapper.dart new file mode 100644 index 0000000..52a2dd9 --- /dev/null +++ b/lib/data/mapper/car_mapper.dart @@ -0,0 +1,23 @@ +import 'package:holzleitner_api/holzleitner_api.dart' as api; + +import 'package:hl_lieferservice/domain/entity/car.dart'; + +/// Mapper zwischen dem generierten API-DTO `api.Car` (built_value) +/// und der Domain-Entity [Car]. +/// +/// Nur Read-Mapper hier — Create/Update geht über die Request-DTOs +/// `api.CreateCarRequest` / `api.UpdateCarRequest`, die die Repository- +/// Impl direkt zusammenbaut. +extension ApiCarMapper on api.Car { + Car toDomain() => Car( + id: id, + accountId: accountId, + plate: plate, + active: active, + ); +} + +/// Liste-Variante als Convenience. +extension ApiCarIterableMapper on Iterable { + List toDomainList() => map((c) => c.toDomain()).toList(growable: false); +} diff --git a/lib/data/repository/cars_repository_impl.dart b/lib/data/repository/cars_repository_impl.dart new file mode 100644 index 0000000..0cdaebb --- /dev/null +++ b/lib/data/repository/cars_repository_impl.dart @@ -0,0 +1,96 @@ +import 'package:dio/dio.dart'; +import 'package:holzleitner_api/holzleitner_api.dart' as api; + +import 'package:hl_lieferservice/data/mapper/car_mapper.dart'; +import 'package:hl_lieferservice/domain/entity/car.dart'; +import 'package:hl_lieferservice/domain/repository/cars_repository.dart'; + +/// Spricht den generierten Holzleitner-API-Client an und mappt die +/// `built_value`-DTOs auf die Domain-Entity. +/// +/// Fehler aus dem Backend werden in [CarsRepositoryException] +/// übersetzt — der Bloc kennt die HTTP-Schicht nicht. 401 fliegt +/// ungefangen durch und wird vom übergreifenden +/// HolzleitnerAuthInterceptor-Pfad behandelt (Provider-Stream meldet +/// `AuthSessionExpired` bei Refresh-Failure). +class CarsRepositoryImpl implements CarsRepository { + CarsRepositoryImpl(this._api); + + final api.HolzleitnerApi _api; + + @override + Future> listMine({bool includeInactive = false}) async { + try { + final response = await _api.getCarsApi().listMyCars( + includeInactive: includeInactive, + ); + final cars = response.data?.cars; + if (cars == null) return const []; + return cars.toDomainList(); + } on DioException catch (e) { + throw CarsRepositoryException(_describe(e, 'Laden der Fahrzeuge'), e); + } + } + + @override + Future create({required String plate}) async { + try { + final request = + api.CreateCarRequest((b) => b..plate = plate); + final response = + await _api.getCarsApi().createMyCar(createCarRequest: request); + final car = response.data?.car; + if (car == null) { + throw const CarsRepositoryException( + 'Server lieferte leere Antwort beim Anlegen', + ); + } + return car.toDomain(); + } on DioException catch (e) { + throw CarsRepositoryException(_describe(e, 'Anlegen eines Fahrzeugs'), e); + } + } + + @override + Future update({ + required String carId, + String? plate, + bool? active, + }) async { + try { + final request = api.UpdateCarRequest((b) { + if (plate != null) b.plate = plate; + if (active != null) b.active = active; + }); + final response = await _api.getCarsApi().updateMyCar( + carId: carId, + updateCarRequest: request, + ); + final car = response.data?.car; + if (car == null) { + throw const CarsRepositoryException( + 'Server lieferte leere Antwort beim Aktualisieren', + ); + } + return car.toDomain(); + } on DioException catch (e) { + throw CarsRepositoryException( + _describe(e, 'Aktualisieren eines Fahrzeugs'), + e, + ); + } + } + + /// Übersetzt eine DioException in eine kompakte, UI-taugliche + /// Begründung. Mehr Detail landet im Log, nicht in der Snackbar. + String _describe(DioException e, String operation) { + final status = e.response?.statusCode; + final body = e.response?.data; + if (status == 400 && body is Map && body['message'] != null) { + return '$operation fehlgeschlagen: ${body['message']}'; + } + if (status == 401) return 'Sitzung abgelaufen'; + if (status == 404) return 'Fahrzeug nicht gefunden'; + return '$operation fehlgeschlagen (HTTP ${status ?? 'unbekannt'})'; + } +} diff --git a/lib/domain/entity/.gitkeep b/lib/domain/entity/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/domain/entity/car.dart b/lib/domain/entity/car.dart new file mode 100644 index 0000000..0c358c2 --- /dev/null +++ b/lib/domain/entity/car.dart @@ -0,0 +1,56 @@ +/// Fahrzeug eines Subunternehmer-Accounts — Domain-Entity. +/// +/// Im Gegensatz zum alten `lib/model/car.dart` (int-ID, +/// ERPframe-Welt) hält die neue Entity: +/// * `id` als UUID-String (Backend-Konvention), +/// * `accountId` als Personalnummer (für Audit/Cross-Check, ist +/// redundant zur JWT-Identität aber explizit im Payload), +/// * `active`-Flag (Soft-Delete statt physisches Löschen). +class Car { + const Car({ + required this.id, + required this.accountId, + required this.plate, + required this.active, + }); + + /// UUID des Fahrzeugs. + final String id; + + /// Personalnummer des Account-Inhabers. + final int accountId; + + /// Kennzeichen (z. B. "BGL-HZ 100"). + final String plate; + + /// Inaktive Fahrzeuge tauchen in `GET /me/cars` standardmäßig + /// nicht auf — sie bleiben aber als FK-Anker für historische + /// Audit-Einträge in der Datenbank. + final bool active; + + Car copyWith({String? id, int? accountId, String? plate, bool? active}) { + return Car( + id: id ?? this.id, + accountId: accountId ?? this.accountId, + plate: plate ?? this.plate, + active: active ?? this.active, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Car && + runtimeType == other.runtimeType && + id == other.id && + accountId == other.accountId && + plate == other.plate && + active == other.active; + + @override + int get hashCode => Object.hash(id, accountId, plate, active); + + @override + String toString() => + 'Car(id: $id, accountId: $accountId, plate: $plate, active: $active)'; +} diff --git a/lib/domain/repository/.gitkeep b/lib/domain/repository/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/domain/repository/cars_repository.dart b/lib/domain/repository/cars_repository.dart new file mode 100644 index 0000000..990d6eb --- /dev/null +++ b/lib/domain/repository/cars_repository.dart @@ -0,0 +1,43 @@ +import 'package:hl_lieferservice/domain/entity/car.dart'; + +/// Port für Fahrzeug-Stammdaten des angemeldeten Fahrers. +/// +/// Bewusst **kein** `personalnummer`/`teamId`-Parameter: der Account +/// wird serverseitig aus dem JWT abgeleitet, der Client muss nichts +/// mitschicken. Eine Methode, die einen Account-Filter ermöglicht, +/// ist konsequent nicht vorgesehen. +/// +/// Implementierungen werfen anwendungs-spezifische Exceptions +/// (z. B. `CarsRepositoryException`) — der Aufrufer fängt sie und +/// übersetzt in UI-Zustand. +abstract interface class CarsRepository { + /// Liste der Fahrzeuge des angemeldeten Accounts. + /// `includeInactive=false` blendet deaktivierte Fahrzeuge aus + /// (Default für die App-UI). + Future> listMine({bool includeInactive = false}); + + /// Legt ein neues Fahrzeug mit dem gegebenen Kennzeichen an. + /// Wirft, wenn das Kennzeichen für den Account schon existiert. + Future create({required String plate}); + + /// Aktualisiert ein bestehendes Fahrzeug. + /// Beide Optional-Parameter `null` ist ein No-Op-PATCH. + /// Soft-Delete erfolgt über `update(carId: ..., active: false)`. + Future update({ + required String carId, + String? plate, + bool? active, + }); +} + +/// Allgemeine Repository-Exception. Konkrete Implementierungen +/// können spezifischere Subtypen werfen (z. B. `CarsUnauthorized`). +class CarsRepositoryException implements Exception { + const CarsRepositoryException(this.message, [this.cause]); + + final String message; + final Object? cause; + + @override + String toString() => 'CarsRepositoryException: $message'; +} diff --git a/lib/feature/car_selection/bloc/bloc.dart b/lib/feature/car_selection/bloc/bloc.dart index 3008a1e..3196aff 100644 --- a/lib/feature/car_selection/bloc/bloc.dart +++ b/lib/feature/car_selection/bloc/bloc.dart @@ -40,7 +40,13 @@ class CarSelectBloc extends Bloc { CarSelectComplete( selectedCar: Car( id: stored.selectedCarId!, + // accountId/active fließen aus der lokalen Selection + // nicht durch — wir persistieren nur (id, plate) als + // UI-Pointer. Die Tour-Logik holt die vollständigen + // Car-Felder weiterhin aus dem CarsBloc-Listing. + accountId: 0, plate: stored.selectedCarPlate!, + active: true, ), ), ); diff --git a/lib/feature/car_selection/presentation/car_selection_page.dart b/lib/feature/car_selection/presentation/car_selection_page.dart index 2934d43..319d6ea 100644 --- a/lib/feature/car_selection/presentation/car_selection_page.dart +++ b/lib/feature/car_selection/presentation/car_selection_page.dart @@ -33,19 +33,15 @@ class _CarSelectionPageState extends State { void initState() { super.initState(); _selectedCar = widget.previousCar; - final authState = context.read().state as Authenticated; - context.read().add(CarLoad(teamId: authState.user.number)); + context.read().add(const CarLoad()); } void _onAddCar() { - final authState = context.read().state as Authenticated; showDialog( context: context, builder: (_) => CarDialog( onAction: (plate) { - context.read().add( - CarAdd(teamId: authState.user.number, plate: plate), - ); + context.read().add(CarAdd(plate: plate)); }, ), ); @@ -97,12 +93,9 @@ class _CarSelectionPageState extends State { ); } - final authState = context.read().state as Authenticated; return RefreshIndicator( onRefresh: () async { - context.read().add( - CarLoad(teamId: authState.user.number, force: true), - ); + context.read().add(const CarLoad(force: true)); }, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), @@ -207,14 +200,9 @@ class _CarSelectionPageState extends State { const SizedBox(height: 16), FilledButton( onPressed: () { - final authState = - context.read().state - as Authenticated; context.read().add( - CarLoad( - teamId: authState.user.number, - ), - ); + const CarLoad(force: true), + ); }, child: const Text("Erneut versuchen"), ), diff --git a/lib/feature/car_selection/repository/car_selection_repository.dart b/lib/feature/car_selection/repository/car_selection_repository.dart index 9a456ab..ef145df 100644 --- a/lib/feature/car_selection/repository/car_selection_repository.dart +++ b/lib/feature/car_selection/repository/car_selection_repository.dart @@ -1,21 +1,34 @@ import 'package:hl_lieferservice/feature/cars/model/selection.dart'; import 'package:shared_preferences/shared_preferences.dart'; +/// Persistiert die Tagesfahrzeug-Auswahl lokal auf dem Gerät pro +/// Account. Nach der Backend-Migration sind die Car-IDs UUIDs +/// (Strings). Alte Pre-Phase-D-Installations können noch int-Werte +/// unter `_car_id` liegen haben — die werden beim Lesen +/// stillschweigend ignoriert (Migration durch "neu auswählen"). class CarSelectionRepository { static String _keyDate(String userId) => 'car_selection_${userId}_date'; static String _keyCarId(String userId) => 'car_selection_${userId}_car_id'; static String _keyCarPlate(String userId) => 'car_selection_${userId}_car_plate'; - /// Returns the stored [CarSelection] for the given user, or null if nothing - /// has been saved yet for that user. Future getSelection(String userId) async { final prefs = await SharedPreferences.getInstance(); final dateString = prefs.getString(_keyDate(userId)); - final carId = prefs.getInt(_keyCarId(userId)); final plate = prefs.getString(_keyCarPlate(userId)); + // Versuche zuerst die neue String-Variante. Falls noch ein altes + // int unter dem Key liegt (pre-Migration), wirft getString — + // dann beste Strategie: alte Daten droppen, Re-Auswahl erzwingen. + String? carId; + try { + carId = prefs.getString(_keyCarId(userId)); + } catch (_) { + await prefs.remove(_keyCarId(userId)); + carId = null; + } + if (dateString == null || carId == null || plate == null) return null; return CarSelection( @@ -25,12 +38,11 @@ class CarSelectionRepository { ); } - /// Persists the given [selection] for the given user locally on this device. Future saveSelection(String userId, CarSelection selection) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_keyDate(userId), selection.date.toIso8601String()); - await prefs.setInt(_keyCarId(userId), selection.selectedCarId!); + await prefs.setString(_keyCarId(userId), selection.selectedCarId!); await prefs.setString(_keyCarPlate(userId), selection.selectedCarPlate!); } } diff --git a/lib/feature/cars/bloc/cars_bloc.dart b/lib/feature/cars/bloc/cars_bloc.dart index d01e390..b5c2c13 100644 --- a/lib/feature/cars/bloc/cars_bloc.dart +++ b/lib/feature/cars/bloc/cars_bloc.dart @@ -1,125 +1,125 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/authentication/bloc/auth_bloc.dart'; -import 'package:hl_lieferservice/feature/authentication/bloc/auth_event.dart'; -import 'package:hl_lieferservice/feature/authentication/exceptions.dart'; -import 'package:hl_lieferservice/feature/cars/repository/cars_repository.dart'; + +import 'package:hl_lieferservice/domain/entity/car.dart'; +import 'package:hl_lieferservice/domain/repository/cars_repository.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_event.dart'; -import '../../../model/car.dart'; import 'cars_event.dart'; import 'cars_state.dart'; +/// Bloc für die Fahrzeugverwaltung des angemeldeten Fahrers. +/// +/// `personalnummer`/`teamId` wird **nicht mehr** in den Events +/// übergeben — der Account kommt serverseitig aus dem JWT. +/// Session-Expiry geht über den globalen Provider-Stream +/// (KeycloakOidcTokenProvider → AuthSessionExpired), nicht über +/// dieses Bloc. class CarsBloc extends Bloc { - CarsRepository repository; - OperationBloc opBloc; - AuthBloc authBloc; - - CarsBloc({required this.repository, required this.opBloc, required this.authBloc}) - : super(CarsInitial()) { - on(_carAdd); - on(_carEdit); - on(_carDelete); - on(_carLoad); + CarsBloc({required this.repository, required this.opBloc}) + : super(const CarsInitial()) { + on(_handleLoad); + on(_handleAdd); + on(_handleEdit); + on(_handleDeactivate); } - void _handleError(Object e, String fallbackMessage) { - if (e is UserUnauthorized) { - authBloc.add(SessionExpiredEvent()); - } else { - opBloc.add(FailOperation(message: fallbackMessage)); - } - } + final CarsRepository repository; + final OperationBloc opBloc; - Future _carLoad(CarLoad event, Emitter emit) async { - // Skip the API call if cars are already loaded and no force-refresh requested. + Future _handleLoad(CarLoad event, Emitter emit) async { if (state is CarsLoaded && !event.force) return; + emit(const CarsLoading()); try { - emit(CarsLoading()); - List cars = await repository.getAll(event.teamId); - emit(CarsLoaded(cars: cars, teamId: event.teamId)); - } catch (e) { - if (e is UserUnauthorized) { - authBloc.add(SessionExpiredEvent()); - return; - } - emit(CarsLoadingFailed()); + final cars = await repository.listMine(); + emit(CarsLoaded(cars: cars)); + } on CarsRepositoryException catch (e) { + emit(CarsLoadingFailed(message: e.message)); + } catch (_) { + emit(const CarsLoadingFailed()); } } - Future _carAdd(CarAdd event, Emitter emit) async { - final currentState = state; - + Future _handleAdd(CarAdd event, Emitter emit) async { + final current = state; opBloc.add(StartOperation()); try { - Car newCar = await repository.add(event.teamId, event.plate); - - if (currentState is CarsLoaded) { - emit( - currentState.copyWith( - cars: List.from(currentState.cars)..add(newCar), - ), - ); + final created = await repository.create(plate: event.plate); + if (current is CarsLoaded) { + emit(current.copyWith(cars: [...current.cars, created])); } - - opBloc.add(FinishOperation(message: "Auto erfolgreich hinzugefügt")); - } catch (e) { - _handleError(e, "Fehler beim Hinzufügen eines Autos"); + opBloc.add(FinishOperation(message: "Fahrzeug hinzugefügt")); + } on CarsRepositoryException catch (e) { + opBloc.add(FailOperation(message: e.message)); + } catch (_) { + opBloc.add( + FailOperation(message: "Fehler beim Anlegen eines Fahrzeugs"), + ); } } - Future _carEdit(CarEdit event, Emitter emit) async { - final currentState = state; - + Future _handleEdit(CarEdit event, Emitter emit) async { + final current = state; opBloc.add(StartOperation()); try { - await repository.edit(event.teamId, event.newCar); - - if (currentState is CarsLoaded) { - emit( - currentState.copyWith( - cars: - List.from(currentState.cars).map((car) { - if (car.id == event.newCar.id) { - return event.newCar; - } - - return car; - }).toList(), - ), - ); - } - - opBloc.add(FinishOperation(message: "Auto erfolgreich editiert")); - } catch (e) { - _handleError(e, "Fehler beim Editieren des Autos"); + final updated = await repository.update( + carId: event.carId, + plate: event.plate, + active: event.active, + ); + _emitReplaced(current, updated, emit); + opBloc.add(FinishOperation(message: "Fahrzeug aktualisiert")); + } on CarsRepositoryException catch (e) { + opBloc.add(FailOperation(message: e.message)); + } catch (_) { + opBloc.add( + FailOperation(message: "Fehler beim Aktualisieren des Fahrzeugs"), + ); } } - Future _carDelete(CarDelete event, Emitter emit) async { - final currentState = state; - + Future _handleDeactivate( + CarDeactivate event, + Emitter emit, + ) async { + final current = state; opBloc.add(StartOperation()); try { - await repository.delete(event.carId, event.teamId); - - if (currentState is CarsLoaded) { + await repository.update(carId: event.carId, active: false); + if (current is CarsLoaded) { + // Inaktive Fahrzeuge raus aus der UI-Liste — die Liste zeigt + // nur aktive (Default). emit( - CarsLoaded( - cars: [ - ...currentState.cars.where( - (car) => car.id != int.parse(event.carId), - ), - ], - teamId: currentState.teamId, + current.copyWith( + cars: current.cars + .where((c) => c.id != event.carId) + .toList(growable: false), ), ); } - - opBloc.add(FinishOperation(message: "Auto erfolgreich gelöscht")); - } catch (e) { - _handleError(e, "Fehler beim Löschen des Autos"); + opBloc.add(FinishOperation(message: "Fahrzeug deaktiviert")); + } on CarsRepositoryException catch (e) { + opBloc.add(FailOperation(message: e.message)); + } catch (_) { + opBloc.add( + FailOperation(message: "Fehler beim Deaktivieren des Fahrzeugs"), + ); } } + + void _emitReplaced( + CarsState current, + Car updated, + Emitter emit, + ) { + if (current is! CarsLoaded) return; + emit( + current.copyWith( + cars: current.cars + .map((c) => c.id == updated.id ? updated : c) + .toList(growable: false), + ), + ); + } } diff --git a/lib/feature/cars/bloc/cars_event.dart b/lib/feature/cars/bloc/cars_event.dart index a522e5c..4fd1bff 100644 --- a/lib/feature/cars/bloc/cars_event.dart +++ b/lib/feature/cars/bloc/cars_event.dart @@ -1,34 +1,35 @@ -import '../../../model/car.dart'; - -abstract class CarEvents {} - -class CarLoad extends CarEvents { - String teamId; - - /// If [force] is true the API is always called, bypassing the cache. - /// Use this for pull-to-refresh. Defaults to false. - bool force; - - CarLoad({required this.teamId, this.force = false}); +/// Events des CarsBloc nach der Backend-Migration. +/// +/// Anders als zuvor brauchen die Events **keinen** `teamId`/ +/// `personalnummer`-Parameter — der Server leitet den Account aus dem +/// JWT ab. Statt `CarDelete` gibt's nur noch `CarDeactivate` (Soft- +/// Delete via `active=false`). +sealed class CarEvents { + const CarEvents(); } -class CarEdit extends CarEvents { - Car newCar; - String teamId; - - CarEdit({required this.newCar, required this.teamId}); +class CarLoad extends CarEvents { + /// Pull-to-Refresh setzt `force=true` und umgeht den Cache. + final bool force; + const CarLoad({this.force = false}); } class CarAdd extends CarEvents { - String teamId; - String plate; - - CarAdd({required this.teamId, required this.plate}); + final String plate; + const CarAdd({required this.plate}); } -class CarDelete extends CarEvents { - String carId; - String teamId; +/// PATCH: Kennzeichen ändern und/oder aktivieren-Status setzen. +class CarEdit extends CarEvents { + final String carId; + final String? plate; + final bool? active; + const CarEdit({required this.carId, this.plate, this.active}); +} - CarDelete({required this.carId, required this.teamId}); -} \ No newline at end of file +/// Soft-Delete (setzt `active=false`). Hartes Löschen ist nicht +/// vorgesehen — Fahrzeuge bleiben als FK-Anker für Audit-Einträge. +class CarDeactivate extends CarEvents { + final String carId; + const CarDeactivate({required this.carId}); +} diff --git a/lib/feature/cars/bloc/cars_state.dart b/lib/feature/cars/bloc/cars_state.dart index 3314f2f..202a87d 100644 --- a/lib/feature/cars/bloc/cars_state.dart +++ b/lib/feature/cars/bloc/cars_state.dart @@ -1,38 +1,26 @@ -import 'package:hl_lieferservice/model/car.dart'; +import 'package:hl_lieferservice/domain/entity/car.dart'; -abstract class CarsState {} - -class CarsInitial extends CarsState {} - -class CarsLoading extends CarsState {} - -class CarsLoadingFailed extends CarsState {} - -class CarAdded extends CarsState { - Car car; - - CarAdded({required this.car}); +sealed class CarsState { + const CarsState(); } -class CarDeleted extends CarsState { - String plate; - - CarDeleted({required this.plate}); +class CarsInitial extends CarsState { + const CarsInitial(); } -class CarEdited extends CarsState { - Car car; +class CarsLoading extends CarsState { + const CarsLoading(); +} - CarEdited({required this.car}); +class CarsLoadingFailed extends CarsState { + const CarsLoadingFailed({this.message}); + final String? message; } class CarsLoaded extends CarsState { - List cars; - String teamId; + const CarsLoaded({required this.cars}); + final List cars; - CarsLoaded({required this.cars, required this.teamId}); - - CarsLoaded copyWith({List? cars, String? teamId}) { - return CarsLoaded(cars: cars ?? this.cars, teamId: teamId ?? this.teamId); - } + CarsLoaded copyWith({List? cars}) => + CarsLoaded(cars: cars ?? this.cars); } diff --git a/lib/feature/cars/model/selection.dart b/lib/feature/cars/model/selection.dart index d2d31e0..8e7b58e 100644 --- a/lib/feature/cars/model/selection.dart +++ b/lib/feature/cars/model/selection.dart @@ -1,9 +1,9 @@ -/* - Settings for the driver to select a car for the current workday. -*/ +/// Lokal persistierte Fahrzeug-Auswahl des Fahrers für den aktuellen +/// Arbeitstag. UUID-basiert (Backend-Konvention). class CarSelection { final DateTime date; - final int? selectedCarId; + /// UUID des heute ausgewählten Fahrzeugs. + final String? selectedCarId; final String? selectedCarPlate; CarSelection({ diff --git a/lib/feature/cars/presentation/car_fail_page.dart b/lib/feature/cars/presentation/car_fail_page.dart index edd6f39..5d7b06d 100644 --- a/lib/feature/cars/presentation/car_fail_page.dart +++ b/lib/feature/cars/presentation/car_fail_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/authentication/bloc/auth_bloc.dart'; -import 'package:hl_lieferservice/feature/authentication/bloc/auth_state.dart'; + import 'package:hl_lieferservice/feature/cars/bloc/cars_bloc.dart'; import 'package:hl_lieferservice/feature/cars/bloc/cars_event.dart'; @@ -9,8 +8,7 @@ class CarsLoadingFailedPage extends StatelessWidget { const CarsLoadingFailedPage({super.key}); void _onRetry(BuildContext context) { - Authenticated state = context.read().state as Authenticated; - context.read().add(CarLoad(teamId: state.user.number)); + context.read().add(const CarLoad(force: true)); } @override @@ -21,9 +19,13 @@ class CarsLoadingFailedPage extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.error_outline, size: 72, color: Theme.of(context).colorScheme.error,), - Padding( - padding: const EdgeInsets.only(top: 30), + Icon( + Icons.error_outline, + size: 72, + color: Theme.of(context).colorScheme.error, + ), + const Padding( + padding: EdgeInsets.only(top: 30), child: Text( "Leider ist es beim Laden der Fahrzeuge zu einem Fehler gekommen.", ), @@ -32,7 +34,7 @@ class CarsLoadingFailedPage extends StatelessWidget { padding: const EdgeInsets.only(top: 30), child: FilledButton( onPressed: () => _onRetry(context), - child: Text("Erneut versuchen"), + child: const Text("Erneut versuchen"), ), ), ], diff --git a/lib/feature/cars/presentation/car_management.dart b/lib/feature/cars/presentation/car_management.dart index ae3dbc5..1b310f2 100644 --- a/lib/feature/cars/presentation/car_management.dart +++ b/lib/feature/cars/presentation/car_management.dart @@ -1,12 +1,13 @@ -import 'package:hl_lieferservice/feature/cars/presentation/car_card.dart'; - -import '../../../model/car.dart'; -import 'car_dialog.dart'; import 'package:flutter/material.dart'; +import 'package:hl_lieferservice/domain/entity/car.dart'; +import 'package:hl_lieferservice/feature/cars/presentation/car_card.dart'; +import 'package:hl_lieferservice/feature/cars/presentation/car_dialog.dart'; + class CarManagementOverview extends StatefulWidget { final List cars; - final int? selectedCarId; + /// UUID des aktuell für heute ausgewählten Fahrzeugs (oder `null`). + final String? selectedCarId; final Function(String plate) onAdd; final Function(String id) onDelete; final Function(String id, String plate) onEdit; @@ -36,12 +37,12 @@ class _CarManagementOverviewState extends State { ); } - void _removeCar(Car car) async { - widget.onDelete(car.id.toString()); + void _removeCar(Car car) { + widget.onDelete(car.id); } - void _editCar(Car car, String newName) async { - widget.onEdit(car.id.toString(), newName); + void _editCar(Car car, String newName) { + widget.onEdit(car.id, newName); } @override diff --git a/lib/feature/cars/presentation/car_management_page.dart b/lib/feature/cars/presentation/car_management_page.dart index 9bde7ac..39b66fe 100644 --- a/lib/feature/cars/presentation/car_management_page.dart +++ b/lib/feature/cars/presentation/car_management_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:hl_lieferservice/feature/authentication/bloc/auth_bloc.dart'; -import 'package:hl_lieferservice/feature/authentication/bloc/auth_state.dart'; + import 'package:hl_lieferservice/feature/car_selection/bloc/bloc.dart'; import 'package:hl_lieferservice/feature/car_selection/bloc/state.dart'; import 'package:hl_lieferservice/feature/cars/bloc/cars_bloc.dart'; @@ -9,10 +8,6 @@ import 'package:hl_lieferservice/feature/cars/bloc/cars_event.dart'; import 'package:hl_lieferservice/feature/cars/bloc/cars_state.dart'; import 'package:hl_lieferservice/feature/cars/presentation/car_fail_page.dart'; import 'package:hl_lieferservice/feature/cars/presentation/car_management.dart'; -import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart'; -import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart'; -import 'package:hl_lieferservice/model/car.dart'; class CarManagementPage extends StatefulWidget { const CarManagementPage({super.key}); @@ -22,38 +17,34 @@ class CarManagementPage extends StatefulWidget { } class _CarManagementPageState extends State { - late Authenticated _authState; - @override void initState() { super.initState(); - - // Load cars - _authState = context.read().state as Authenticated; - context.read().add(CarLoad(teamId: _authState.user.number)); + // Account-Identifizierung läuft serverseitig über den JWT — keine + // teamId mehr aus dem AuthState extrahieren. + context.read().add(const CarLoad()); } void _add(String plate) { - context.read().add( - CarAdd(teamId: _authState.user.number, plate: plate), - ); + context.read().add(CarAdd(plate: plate)); } Future _refresh() async { - context.read().add(CarLoad(teamId: _authState.user.number, force: true)); + context.read().add(const CarLoad(force: true)); } - void _remove(String id) { - final carId = int.parse(id); - + void _remove(String carId) { + // Schutz: wenn dieses Fahrzeug aktuell für heute ausgewählt ist, + // darf es nicht deaktiviert werden. final carSelectState = context.read().state; if (carSelectState is CarSelectComplete && carSelectState.selectedCar.id == carId) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( - "Dieses Fahrzeug ist aktuell ausgewählt und kann nicht gelöscht werden. " - "Bitte wähle zuerst ein anderes Fahrzeug aus.", + "Dieses Fahrzeug ist aktuell ausgewählt und kann nicht " + "deaktiviert werden. Bitte wähle zuerst ein anderes Fahrzeug " + "aus.", ), duration: Duration(seconds: 4), ), @@ -61,68 +52,32 @@ class _CarManagementPageState extends State { return; } - final tourState = context.read().state; - if (tourState is! TourLoaded) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - "Die Tourdaten sind noch nicht verfügbar. " - "Bitte versuche es in Kürze erneut.", - ), - duration: Duration(seconds: 4), - ), - ); - return; - } + // TODO Phase D: Schutz wieder einhängen, sobald TourBloc/Domain + // mit UUID-Car-Ids arbeiten und prüfen, ob das Fahrzeug noch + // beladene, nicht ausgelieferte Artikel hat. - if (tourState.tour.hasUndeliveredLoadedArticles(carId)) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - "Dieses Fahrzeug hat noch geladene Artikel, die nicht ausgeliefert wurden. " - "Bitte schließe alle offenen Lieferungen ab, bevor du das Fahrzeug löschst.", - ), - duration: Duration(seconds: 4), - ), - ); - return; - } - - context.read().add( - CarDelete(carId: id, teamId: _authState.user.number), - ); + context.read().add(CarDeactivate(carId: carId)); } - void _edit(String id, String plate) { - context.read().add( - CarEdit( - newCar: Car(id: int.parse(id), plate: plate), - teamId: _authState.user.number, - ), - ); + void _edit(String carId, String plate) { + context.read().add(CarEdit(carId: carId, plate: plate)); } @override Widget build(BuildContext context) { return Scaffold( - body: BlocConsumer( - listener: (context, state) { - if (state is CarsLoaded && - context.read().state is TourLoaded) { - context.read().add(CarsLoadedEvent(cars: state.cars)); - } - }, + body: BlocBuilder( builder: (context, state) { - if (state is CarsLoading) { - return Center(child: const CircularProgressIndicator()); + if (state is CarsLoading || state is CarsInitial) { + return const Center(child: CircularProgressIndicator()); } - if (state is CarsLoaded) { return BlocBuilder( builder: (context, selectState) { - final int? selectedCarId = selectState is CarSelectComplete - ? selectState.selectedCar.id - : null; + final String? selectedCarId = + selectState is CarSelectComplete + ? selectState.selectedCar.id + : null; return CarManagementOverview( cars: state.cars, selectedCarId: selectedCarId, @@ -134,12 +89,10 @@ class _CarManagementPageState extends State { }, ); } - if (state is CarsLoadingFailed) { - return CarsLoadingFailedPage(); + return const CarsLoadingFailedPage(); } - - return Container(); + return const SizedBox.shrink(); }, ), ); diff --git a/lib/feature/cars/repository/cars_repository.dart b/lib/feature/cars/repository/cars_repository.dart deleted file mode 100644 index 6e0329a..0000000 --- a/lib/feature/cars/repository/cars_repository.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:hl_lieferservice/feature/cars/service/cars_service.dart'; - -import '../../../model/car.dart'; - -class CarsRepository { - CarService service; - - CarsRepository({required this.service}); - - Future> getAll(String teamId) async { - return service.getCars(int.parse(teamId)); - } - - Future delete(String carId, String teamId) async { - return service.removeCar(int.parse(carId), int.parse(teamId)); - } - - Future edit(String teamId, Car newCar) async { - return service.editCar(newCar); - } - - Future add(String teamId, String plate) async { - return service.addCar(plate, int.parse(teamId)); - } -} \ No newline at end of file diff --git a/lib/feature/cars/service/cars_service.dart b/lib/feature/cars/service/cars_service.dart deleted file mode 100644 index 707fc0b..0000000 --- a/lib/feature/cars/service/cars_service.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter/cupertino.dart'; -import 'package:hl_lieferservice/feature/authentication/exceptions.dart'; -import 'package:hl_lieferservice/util.dart'; -import 'package:http/http.dart'; - -import '../../../dto/basic_response.dart'; -import '../../../dto/car_add_response.dart'; -import '../../../dto/car_get_response.dart'; -import '../../../model/car.dart'; - -class CarService { - CarService(); - - Future addCar(String plate, int teamId) async { - try { - debugPrint(jsonEncode({"team_id": teamId.toString(), "plate": plate})); - - var response = await post( - urlBuilder("_web_addCar"), - headers: getSessionOrThrow(), - body: {"team_id": teamId.toString(), "plate": plate}, - ); - - if (response.statusCode == HttpStatus.unauthorized) { - throw UserUnauthorized(); - } - - Map responseJson = jsonDecode(response.body); - CarAddResponseDTO responseDto = CarAddResponseDTO.fromJson(responseJson); - - if (responseDto.succeeded == true) { - return Car( - id: int.parse(responseDto.car.id), - plate: responseDto.car.plate, - ); - } else { - throw responseDto.message; - } - } catch (e, st) { - debugPrint("ERROR WHILE ADDING CAR"); - debugPrint(e.toString()); - debugPrint(st.toString()); - - rethrow; - } - } - - Future editCar(Car car) async { - try { - var response = await post( - urlBuilder("_web_editCar"), - headers: getSessionOrThrow(), - body: {"id": car.id.toString(), "plate": car.plate}, - ); - - if (response.statusCode == HttpStatus.unauthorized) { - throw UserUnauthorized(); - } - - Map responseJson = jsonDecode(response.body); - BasicResponseDTO responseDto = BasicResponseDTO.fromJson(responseJson); - - if (responseDto.succeeded == true) { - return; - } else { - throw responseDto.message; - } - } catch (e, st) { - debugPrint("ERROR WHILE EDITING CAR ${car.id}"); - debugPrint("$e"); - debugPrint(st.toString()); - - rethrow; - } - } - - Future removeCar(int carId, int teamId) async { - try { - var response = await post( - urlBuilder("_web_removeCar"), - headers: getSessionOrThrow(), - body: {"team_id": teamId.toString(), "id": carId.toString()}, - ); - - if (response.statusCode == HttpStatus.unauthorized) { - throw UserUnauthorized(); - } - - Map responseJson = jsonDecode(response.body); - BasicResponseDTO responseDto = BasicResponseDTO.fromJson(responseJson); - - if (responseDto.succeeded == true) { - return; - } else { - throw responseDto.message; - } - } catch (e, st) { - debugPrint("ERROR WHILE REMOVING CAR"); - debugPrint(e.toString()); - debugPrint(st.toString()); - - rethrow; - } - } - - Future> getCars(int teamId) async { - try { - debugPrint(teamId.toString()); - - var response = await post( - urlBuilder("_web_getCars"), - headers: getSessionOrThrow(), - body: {"team_id": teamId.toString()}, - ); - - if (response.statusCode == HttpStatus.unauthorized) { - throw UserUnauthorized(); - } - - Map responseJson = jsonDecode(response.body); - CarGetResponseDTO responseDto = CarGetResponseDTO.fromJson(responseJson); - - if (responseDto.succeeded == true) { - return responseDto.cars! - .map((carDto) => Car(id: int.parse(carDto.id), plate: carDto.plate)) - .toList(); - } else { - throw responseDto.message; - } - } catch (e, st) { - debugPrint("ERROR WHILE FETCHING CARS"); - debugPrint(e.toString()); - debugPrint(st.toString()); - - rethrow; - } - } -} diff --git a/lib/feature/delivery/overview/presentation/delivery_info.dart b/lib/feature/delivery/overview/presentation/delivery_info.dart index a345668..0c7a38c 100644 --- a/lib/feature/delivery/overview/presentation/delivery_info.dart +++ b/lib/feature/delivery/overview/presentation/delivery_info.dart @@ -5,7 +5,7 @@ import 'package:intl/intl.dart'; class DeliveryInfo extends StatelessWidget { final Tour tour; - final int? selectedCarId; + final String? selectedCarId; const DeliveryInfo({super.key, required this.tour, this.selectedCarId}); diff --git a/lib/feature/delivery/overview/presentation/delivery_list.dart b/lib/feature/delivery/overview/presentation/delivery_list.dart index b9b6f98..142d355 100644 --- a/lib/feature/delivery/overview/presentation/delivery_list.dart +++ b/lib/feature/delivery/overview/presentation/delivery_list.dart @@ -9,7 +9,7 @@ import 'package:hl_lieferservice/model/delivery.dart'; import 'delivery_item.dart'; class DeliveryList extends StatefulWidget { - final int? selectedCarId; + final String? selectedCarId; final SortType sortType; const DeliveryList({super.key, this.selectedCarId, required this.sortType}); diff --git a/lib/feature/delivery/overview/presentation/delivery_overview.dart b/lib/feature/delivery/overview/presentation/delivery_overview.dart index 1edec11..0eb19c5 100644 --- a/lib/feature/delivery/overview/presentation/delivery_overview.dart +++ b/lib/feature/delivery/overview/presentation/delivery_overview.dart @@ -27,7 +27,7 @@ class DeliveryOverview extends StatefulWidget { } class _DeliveryOverviewState extends State { - int? _selectedCarId; + String? _selectedCarId; late SortType _sortType; @override diff --git a/lib/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart b/lib/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart index 7289e7b..5a28bb1 100644 --- a/lib/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart +++ b/lib/feature/delivery/overview/presentation/delivery_overview_custom_sort.dart @@ -9,7 +9,7 @@ import 'package:hl_lieferservice/model/delivery.dart'; class CustomSortDialog extends StatefulWidget { const CustomSortDialog({super.key, this.selectedCarId}); - final int? selectedCarId; + final String? selectedCarId; @override State createState() => _CustomSortDialogState(); diff --git a/lib/feature/delivery/overview/presentation/delivery_selection_page.dart b/lib/feature/delivery/overview/presentation/delivery_selection_page.dart index 8e5e76c..3342d08 100644 --- a/lib/feature/delivery/overview/presentation/delivery_selection_page.dart +++ b/lib/feature/delivery/overview/presentation/delivery_selection_page.dart @@ -32,7 +32,7 @@ class DeliverySelectionPage extends StatefulWidget { /// ID des aktuell gewählten Fahrzeugs (Eigene Lieferungen / Ziel von /// Übernahmen). - final int selectedCarId; + final String selectedCarId; @override State createState() => _DeliverySelectionPageState(); @@ -52,7 +52,7 @@ class _DeliverySelectionPageState extends State { /// Sucht das Plate eines Autos in der Tour-Driver-Liste. Liefert "?" als /// Fallback, falls die Zuordnung nicht (mehr) im Team enthalten ist — /// z. B. nach Personalwechsel zwischen Tour-Synchronisationen. - String _plateFor(int? carId, Tour tour) { + String _plateFor(String? carId, Tour tour) { if (carId == null) return "?"; final car = tour.driver.cars.firstWhereOrNull((c) => c.id == carId); return car?.plate ?? "?"; diff --git a/lib/feature/delivery/overview/presentation/delivery_sort_page.dart b/lib/feature/delivery/overview/presentation/delivery_sort_page.dart index a45def7..f540af3 100644 --- a/lib/feature/delivery/overview/presentation/delivery_sort_page.dart +++ b/lib/feature/delivery/overview/presentation/delivery_sort_page.dart @@ -22,7 +22,7 @@ class DeliverySortPage extends StatefulWidget { this.onPhaseAdvanced, }); - final int selectedCarId; + final String selectedCarId; /// Optionaler Hook, damit die übergeordnete Routing-Stelle nach Erfolg /// auf die nächste Phase wechseln kann (rerender der Beladungs-Page). diff --git a/lib/feature/delivery/overview/widget/sortable_delivery_list.dart b/lib/feature/delivery/overview/widget/sortable_delivery_list.dart index 0c53a71..6222f2e 100644 --- a/lib/feature/delivery/overview/widget/sortable_delivery_list.dart +++ b/lib/feature/delivery/overview/widget/sortable_delivery_list.dart @@ -21,7 +21,7 @@ class SortableDeliveryList extends StatefulWidget { this.controller, }); - final int? selectedCarId; + final String? selectedCarId; /// Optionaler Controller zum Zurücksetzen der Liste durch Eltern-Widgets /// (z. B. Button "Zurücksetzen" in der Page). diff --git a/lib/feature/delivery/repository/tour_repository.dart b/lib/feature/delivery/repository/tour_repository.dart index ac00931..ddc63d8 100644 --- a/lib/feature/delivery/repository/tour_repository.dart +++ b/lib/feature/delivery/repository/tour_repository.dart @@ -48,7 +48,7 @@ class TourRepository { final index = tour.deliveries.indexWhere( (delivery) => delivery.id == deliveryId, ); - tour.deliveries[index].carId = int.parse(carId); + tour.deliveries[index].carId = carId; _tourStream.add(tour); } @@ -80,7 +80,7 @@ class TourRepository { if (article.scannedAmount < article.amount) { article.scannedAmount += 1; - delivery.carId = int.tryParse(carId) ?? delivery.carId; + delivery.carId = carId; await service.assignCar(deliveryId, carId); _tourStream.add(tour); return ScanResult.scanned; @@ -126,7 +126,7 @@ class TourRepository { if (parentArticle.isFullyScanned) { await service.scanArticle(parentArticle.internalId.toString()); parentArticle.scannedAmount += 1; - delivery.carId = int.tryParse(carId) ?? delivery.carId; + delivery.carId = carId; await service.assignCar(deliveryId, carId); } diff --git a/lib/feature/delivery/service/tour_service.dart b/lib/feature/delivery/service/tour_service.dart index edc2483..bbb0564 100644 --- a/lib/feature/delivery/service/tour_service.dart +++ b/lib/feature/delivery/service/tour_service.dart @@ -126,8 +126,17 @@ class TourService { cars: responseDto.driver.cars .map( - (carDto) => - Car(id: int.parse(carDto.id), plate: carDto.plate), + // Legacy: alte ERPframe-CarDto hat int-IDs, neue + // Domain-Entity erwartet UUID-Strings. Wir + // stringifizieren die int-ID und füllen + // accountId/active mit Stub-Werten — der ganze + // Service wird in Phase D entfernt. + (carDto) => Car( + id: carDto.id, + accountId: 0, + plate: carDto.plate, + active: true, + ), ) .toList(), teamNumber: int.parse(responseDto.driver.id), diff --git a/lib/feature/loading/presentation/loading_customer_page.dart b/lib/feature/loading/presentation/loading_customer_page.dart index 4e64b73..9891382 100644 --- a/lib/feature/loading/presentation/loading_customer_page.dart +++ b/lib/feature/loading/presentation/loading_customer_page.dart @@ -82,7 +82,7 @@ class _LoadingCustomerPageState extends State { /// Aktuell gewähltes Fahrzeug. Wird über den CarSelectBloc synchronisiert, /// einmalig in initState bevor erster build. - int? _selectedCarId; + String? _selectedCarId; /// Erkennt den Übergang "Lieferung läuft → abgeschlossen", damit der /// Listener auch dann robust reagiert, wenn der TourBloc zwischendurch @@ -244,7 +244,7 @@ class _LoadingCustomerPageState extends State { // Datenaufbau // --------------------------------------------------------------------------- - String? _lookupCarPlate(int? carId, Tour tour) { + String? _lookupCarPlate(String? carId, Tour tour) { if (carId == null) return null; return tour.driver.cars.firstWhereOrNull((c) => c.id == carId)?.plate; } diff --git a/lib/feature/loading/presentation/loading_overview_page.dart b/lib/feature/loading/presentation/loading_overview_page.dart index 4321df9..0fd6a46 100644 --- a/lib/feature/loading/presentation/loading_overview_page.dart +++ b/lib/feature/loading/presentation/loading_overview_page.dart @@ -22,7 +22,7 @@ import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart'; class LoadingOverviewPage extends StatelessWidget { const LoadingOverviewPage({super.key}); - String? _lookupCarPlate(int? carId, Tour tour) { + String? _lookupCarPlate(String? carId, Tour tour) { if (carId == null) return null; return tour.driver.cars.firstWhereOrNull((c) => c.id == carId)?.plate; } diff --git a/lib/model/car.dart b/lib/model/car.dart index 332ace7..76ad51e 100644 --- a/lib/model/car.dart +++ b/lib/model/car.dart @@ -1,6 +1,6 @@ -class Car { - Car({required this.id, required this.plate}); - - int id; - String plate; -} \ No newline at end of file +/// Backward-compat-Re-Export: alle Aufrufer, die noch +/// `package:hl_lieferservice/model/car.dart` importieren, bekommen +/// jetzt die neue Domain-Entity. Mit Phase D wandert dieses File raus, +/// sobald die letzten Legacy-Aufrufer (tour_service.dart, +/// tour_event.dart) auf die echte Domain-Schicht umgestellt sind. +export 'package:hl_lieferservice/domain/entity/car.dart'; diff --git a/lib/model/delivery.dart b/lib/model/delivery.dart index 5f08f73..1be938a 100644 --- a/lib/model/delivery.dart +++ b/lib/model/delivery.dart @@ -202,7 +202,10 @@ class Delivery implements Comparable { DeliveryState state; String? specialAgreements; PaymentOptions paymentOptions; - int? carId; + /// UUID des zugewiesenen Fahrzeugs (Backend-Konvention). + /// War vor der Backend-Migration `int?` — die Konversion ist Teil + /// der Phase-D-Vorbereitung. + String? carId; List notes; List images; double prepayment; @@ -230,7 +233,7 @@ class Delivery implements Comparable { DeliveryState? state, String? specialAgreements, PaymentOptions? paymentOptions, - int? carId, + String? carId, List? notes, List? images, double? prepayment, @@ -371,7 +374,10 @@ class Delivery implements Comparable { ), paymentAtDelivery: double.tryParse(dto.paymentAtDelivery) ?? 0.0, images: dto.images.map(ImageNote.fromDTO).toList(), - carId: int.tryParse(dto.carId), + // Legacy: ERPframe-Backend liefert int-Strings; im neuen Backend + // sind das UUID-Strings. Beide werden hier transparent + // weitergereicht. + carId: dto.carId.isEmpty ? null : dto.carId, totalGrossValue: double.parse( dto.totalGrossValue == "" ? "0" : dto.totalGrossValue, ), diff --git a/lib/model/tour.dart b/lib/model/tour.dart index 8ccc443..4d1fa68 100644 --- a/lib/model/tour.dart +++ b/lib/model/tour.dart @@ -52,7 +52,7 @@ class Tour { Map> deliveriesPerCar; - int getFinishedDeliveries(int carId) { + int getFinishedDeliveries(String carId) { return deliveries .where((delivery) => delivery.carId == carId) .where((delivery) => delivery.state == DeliveryState.finished) @@ -64,7 +64,7 @@ class Tour { /// that has not been finished yet. Scannable articles count when their /// effective scanned amount (scanned minus removed) is positive; non-scannable /// articles count when their target amount is greater than zero. - bool hasUndeliveredLoadedArticles(int carId) { + bool hasUndeliveredLoadedArticles(String carId) { return deliveries.any((delivery) { if (delivery.carId != carId) return false; if (delivery.state == DeliveryState.finished) return false; diff --git a/lib/widget/app.dart b/lib/widget/app.dart index 19c3fb6..0f69ca4 100644 --- a/lib/widget/app.dart +++ b/lib/widget/app.dart @@ -9,10 +9,10 @@ import 'package:hl_lieferservice/main.dart' show locator; import 'package:hl_lieferservice/feature/car_selection/bloc/bloc.dart'; import 'package:hl_lieferservice/feature/car_selection/presentation/car_selection_enforcer.dart'; import 'package:hl_lieferservice/feature/car_selection/repository/car_selection_repository.dart'; +import 'package:hl_lieferservice/data/repository/cars_repository_impl.dart'; import 'package:hl_lieferservice/feature/cars/bloc/cars_bloc.dart'; import 'package:hl_lieferservice/feature/cars/presentation/car_management_page.dart'; -import 'package:hl_lieferservice/feature/cars/repository/cars_repository.dart'; -import 'package:hl_lieferservice/feature/cars/service/cars_service.dart'; +import 'package:holzleitner_api/holzleitner_api.dart' show HolzleitnerApi; import 'package:hl_lieferservice/feature/delivery/bloc/phase_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart'; @@ -69,10 +69,13 @@ class _DeliveryAppState extends State { CarSelectBloc(repository: CarSelectionRepository()), ), BlocProvider( + // Phase-D-Migration: produktive CarsRepository-Impl + // gegen das generierte Rust-Backend-API. Account-Filter + // serverseitig aus dem JWT, deshalb braucht der Bloc + // keinen AuthBloc-Bezug mehr. create: (context) => CarsBloc( - repository: CarsRepository(service: CarService()), + repository: CarsRepositoryImpl(locator()), opBloc: context.read(), - authBloc: context.read(), ), ), BlocProvider( diff --git a/lib/widget/home/presentation/home.dart b/lib/widget/home/presentation/home.dart index 0b9615e..9f2a038 100644 --- a/lib/widget/home/presentation/home.dart +++ b/lib/widget/home/presentation/home.dart @@ -108,7 +108,7 @@ class _HomeState extends State { Widget _buildForPhase( BuildContext context, DeliveryPhase phase, - int selectedCarId, + String selectedCarId, ) { switch (phase) { case DeliveryPhase.auswaehlen: