Files
Holzleitner-Lieferservice-App/lib/model/tour.dart
Dennis Nemec 3ecbc82885 Phase C+D-1: Cars-Domain auf Rust-Backend umgestellt
Clean-Arch-Schichten für Cars:
- lib/domain/entity/car.dart: UUID-id, accountId (Personalnummer),
  plate, active. Pendant zum Backend-Schema.
- lib/domain/repository/cars_repository.dart: Port — listMine,
  create, update. Keine teamId/personalnummer-Parameter, der
  Account fließt serverseitig aus dem JWT.
- lib/data/mapper/car_mapper.dart: API-DTO (built_value) → Domain.
- lib/data/repository/cars_repository_impl.dart: konkrete Impl via
  generierter CarsApi (dio), mit DioException → CarsRepositoryException-
  Übersetzung.

Feature-Cars-Refactoring:
- CarsBloc nimmt jetzt die Domain-Repository-Schnittstelle. Events:
  CarLoad/CarAdd/CarEdit/CarDeactivate (statt CarDelete). Keine
  teamId-Parameter mehr. Kein authBloc-Bezug, Session-Expiry läuft
  über den globalen Provider-Stream.
- CarsState sealed mit CarsInitial/Loading/LoadingFailed/Loaded.
- Pages: car_management_page, car_management, car_card, car_fail_page,
  car_selection_page komplett auf die neue Entity und Event-Signaturen.
- Alte lib/feature/cars/service/cars_service.dart und
  lib/feature/cars/repository/cars_repository.dart gelöscht.

CarSelectBloc + Storage:
- CarSelection.selectedCarId von int? auf String? umgestellt.
- CarSelectionRepository persistiert die UUID jetzt als String;
  defensive Migration für noch vorhandene int-Werte (alte
  Pre-Migration-Installations) verwirft den Wert leise und
  erzwingt Neuauswahl.

Konsequenz-Cleanup im Tour-Code (Phase-D-Vorbereitung):
- Delivery.carId String? statt int?.
- Tour.hasUndeliveredLoadedArticles / getFinishedDeliveries auf
  String carId.
- _selectedCarId / int? carId / int selectedCarId in DeliveryOverview,
  LoadingCustomerPage/OverviewPage, Home, DeliverySelection/SortPage,
  DeliveryInfo/List, CustomSortDialog, SortableDeliveryList auf
  String umgestellt.
- TourRepository ersetzt int.parse(carId)/int.tryParse-Zuweisungen
  direkt durch String.
- lib/model/car.dart wird zum Re-Export der neuen Domain-Entity,
  damit Legacy-Imports während Phase-D-Übergang weiter compilieren.

DI:
- app.dart: CarsBloc bekommt CarsRepositoryImpl(locator<HolzleitnerApi>())
  statt der alten CarsRepository(service: CarService()).

Build (flutter build apk --debug) durch, flutter analyze ohne
errors.
2026-05-15 11:55:24 +02:00

134 lines
3.3 KiB
Dart

import 'package:hl_lieferservice/dto/payment.dart';
import 'car.dart';
import 'delivery.dart';
class Payment {
const Payment({
required this.description,
required this.shortcode,
required this.id,
});
final String id;
final String description;
final String shortcode;
factory Payment.fromDTO(PaymentMethodDTO dto) {
return Payment(
description: dto.description,
shortcode: dto.shortCode,
id: dto.id,
);
}
Payment copyWith({
String? description,
String? shortcode,
String? id,
}) {
return Payment(
description: description ?? this.description,
shortcode: shortcode ?? this.shortcode,
id: id ?? this.id,
);
}
}
class Tour {
Tour({
required this.date,
required this.deliveries,
required this.driver,
required this.discountArticleNumber,
required this.paymentMethods,
}) : deliveriesPerCar = {};
final DateTime date;
final String discountArticleNumber;
Driver driver;
final List<Delivery> deliveries;
List<Payment> paymentMethods;
Map<String, List<Delivery>> deliveriesPerCar;
int getFinishedDeliveries(String carId) {
return deliveries
.where((delivery) => delivery.carId == carId)
.where((delivery) => delivery.state == DeliveryState.finished)
.toList()
.length;
}
/// Returns true if the car still has loaded articles assigned to a delivery
/// 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(String carId) {
return deliveries.any((delivery) {
if (delivery.carId != carId) return false;
if (delivery.state == DeliveryState.finished) return false;
return delivery.articles.any((article) {
if (article.scannable) {
return article.scannedAmount > article.scannedRemovedAmount;
}
return article.amount > 0;
});
});
}
Tour copyWith({
DateTime? date,
String? discountArticleNumber,
Driver? driver,
List<Delivery>? deliveries,
List<Payment>? paymentMethods,
}) {
return Tour(
date: date ?? this.date.copyWith(),
discountArticleNumber:
discountArticleNumber ?? this.discountArticleNumber,
driver: driver ?? this.driver,
deliveries: deliveries ?? this.deliveries,
paymentMethods: paymentMethods ?? this.paymentMethods,
);
}
}
class Driver {
Driver({
required this.teamNumber,
required this.name,
required this.salutation,
required this.cars,
});
final int teamNumber;
final String name;
final String salutation;
List<Car> cars;
/// If the driver is representing a company, then the company name is returned.
String getSalutatedLastName() {
if (salutation != "Firma") {
return "$salutation, ${name.split(" ").first}";
}
return "$salutation, $name";
}
Driver copyWith(
int? teamNumber,
String? name,
String? salutation,
List<Car>? cars,
) {
return Driver(
teamNumber: teamNumber ?? this.teamNumber,
name: name ?? this.name,
salutation: salutation ?? this.salutation,
cars: cars ?? this.cars,
);
}
}