first commit
This commit is contained in:
916
app/lib/presentation/blocs/scan/scan_bloc.dart
Normal file
916
app/lib/presentation/blocs/scan/scan_bloc.dart
Normal file
@@ -0,0 +1,916 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../../domain/entities/logistic_object.dart';
|
||||
import '../../../domain/entities/tour.dart';
|
||||
import '../../../domain/repositories/tour_repository.dart';
|
||||
import '../../../core/constants/app_constants.dart';
|
||||
import '../../../core/errors/failures.dart';
|
||||
|
||||
part 'scan_event.dart';
|
||||
part 'scan_state.dart';
|
||||
|
||||
class ScanBloc extends Bloc<ScanEvent, ScanState> {
|
||||
final TourRepository repository;
|
||||
Tour? currentTour;
|
||||
String? currentPageId;
|
||||
String? scannedFsaId;
|
||||
|
||||
// Container handling for VS state machine
|
||||
String? containerId;
|
||||
String? containerType; // 'a' or 'b'
|
||||
|
||||
ScanBloc({required this.repository}) : super(ScanInitial()) {
|
||||
on<InitializeScan>(_onInitializeScan);
|
||||
on<ProcessBarcode>(_onProcessBarcode);
|
||||
on<ValidateBarcode>(_onValidateBarcode);
|
||||
on<UpdateObjectState>(_onUpdateObjectState);
|
||||
on<ResetScan>(_onResetScan);
|
||||
on<CreateUnknownObject>(_onCreateUnknownObject);
|
||||
}
|
||||
|
||||
Future<void> _onInitializeScan(InitializeScan event, Emitter<ScanState> emit) async {
|
||||
currentTour = event.tour;
|
||||
containerId = null;
|
||||
containerType = null;
|
||||
emit(ScanReady(tour: event.tour));
|
||||
}
|
||||
|
||||
Future<void> _onProcessBarcode(ProcessBarcode event, Emitter<ScanState> emit) async {
|
||||
emit(ScanProcessing(barcode: event.barcode));
|
||||
|
||||
final barcode = event.barcode.trim();
|
||||
|
||||
// Prüfe auf spezielle Barcodes (Seiten-Codes)
|
||||
if (currentTour != null) {
|
||||
final pageInfo = _findPageForBarcode(barcode);
|
||||
if (pageInfo != null) {
|
||||
emit(ScanPageDetected(
|
||||
pageId: pageInfo['pageId']!,
|
||||
label: pageInfo['label']!,
|
||||
tour: currentTour!,
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Suche Objekt nach Barcode
|
||||
final result = await repository.getObjectByBarcode(barcode);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(ScanError(message: _mapFailureToMessage(failure))),
|
||||
(object) {
|
||||
if (object != null) {
|
||||
_processScannedObject(object, barcode, emit);
|
||||
} else {
|
||||
// Unbekanntes Objekt - prüfe auf gültiges Präfix
|
||||
final prefix = barcode.length >= 3 ? barcode.substring(0, 3) : '';
|
||||
if (_isValidPrefix(prefix)) {
|
||||
emit(ScanUnknownObject(
|
||||
barcode: barcode,
|
||||
prefix: prefix,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else {
|
||||
emit(ScanError(message: 'Unbekannter Barcode: $barcode'));
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _processScannedObject(LogisticObject object, String barcode, Emitter<ScanState> emit) {
|
||||
if (currentTour == null) {
|
||||
emit(const ScanError(message: 'Keine Tour ausgewählt'));
|
||||
return;
|
||||
}
|
||||
|
||||
final tourType = currentTour!.type;
|
||||
|
||||
// Dispatch to appropriate state machine based on tour type
|
||||
switch (tourType) {
|
||||
case TourTypes.stockStart:
|
||||
_stockStartStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.vehStart:
|
||||
_vehStartStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.vehBulk:
|
||||
_vehBulkStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.veh:
|
||||
_vehStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.fsa:
|
||||
_fsaStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.vs:
|
||||
_vsStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.vehVs:
|
||||
_vehVsStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.gi:
|
||||
_giStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.vehEnd:
|
||||
_vehEndStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.stockEnd:
|
||||
_stockEndStateMachine(object, emit);
|
||||
break;
|
||||
case TourTypes.stock:
|
||||
_stockStateMachine(object, emit);
|
||||
break;
|
||||
default:
|
||||
// Fallback to simple state machine for unknown tour types
|
||||
final nextState = _determineNextState(object);
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: nextState,
|
||||
tour: currentTour,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: stockStart (Lager Beladung)
|
||||
// ============================================================================
|
||||
void _stockStartStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final currentState = object.state;
|
||||
|
||||
if (currentState == ObjectStates.unknown) {
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.toDelivery,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else if (currentState == ObjectStates.finGITmp) {
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retGI,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else {
|
||||
emit(ScanError(
|
||||
message: 'Fehler: Ungültiger Barcode für Lager Beladung',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: vehStart (Fahrzeug Start)
|
||||
// ============================================================================
|
||||
void _vehStartStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final currentState = object.state;
|
||||
|
||||
if (currentState == ObjectStates.toDelivery) {
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.delivery,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else if (currentState == ObjectStates.retGI) {
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retGIFzg,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Ungültiger Barcode'));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: vehBulk (Fahrzeug Bulk)
|
||||
// ============================================================================
|
||||
void _vehBulkStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final objectType = object.type;
|
||||
final currentState = object.state;
|
||||
|
||||
// Type 9 = special bulk handling
|
||||
if (objectType == 9) {
|
||||
// Bulk update all objects with state to_delivery
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.delivery,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else {
|
||||
if (currentState == ObjectStates.toDelivery) {
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.delivery,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else if (currentState == ObjectStates.retGI) {
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retGIFzg,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Ungültiger Barcode'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: veh (Fahrzeug - Stationen)
|
||||
// ============================================================================
|
||||
void _vehStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final currentState = object.state;
|
||||
|
||||
switch (currentState) {
|
||||
case ObjectStates.delivery:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.station,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retFail:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retFailFzg,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retGI:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retGIFzg,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retDS:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retDSFzg,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.station:
|
||||
// Reverse transition: back to delivery
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.delivery,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.inFA:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.hdl,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
default:
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: fsa (Fahrscheinautomat)
|
||||
// ============================================================================
|
||||
void _fsaStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final objectType = object.type;
|
||||
final currentState = object.state;
|
||||
final subtype = object.subtype.toLowerCase();
|
||||
|
||||
// GK = Geldkassette (type 1)
|
||||
if (_isTypeGK(objectType, subtype)) {
|
||||
_fsaGKStateMachine(object, currentState, emit);
|
||||
}
|
||||
// HP = Hauptkasse/Druckerpatronen (type 2)
|
||||
else if (_isTypeHP(objectType, subtype)) {
|
||||
_fsaHPStateMachine(object, currentState, emit);
|
||||
}
|
||||
// FR = Fahrkartenrolle (type 5)
|
||||
else if (_isTypeFR(objectType, subtype)) {
|
||||
_fsaFRStateMachine(object, currentState, emit);
|
||||
}
|
||||
else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Typ für FSA'));
|
||||
}
|
||||
}
|
||||
|
||||
void _fsaGKStateMachine(LogisticObject object, String currentState, Emitter<ScanState> emit) {
|
||||
switch (currentState) {
|
||||
case ObjectStates.station:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.inFA,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.inFA:
|
||||
// Special handling: Fehlkassette logic
|
||||
emit(ScanFehlKassetteDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retFail,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.unknown:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retGI,
|
||||
tour: currentTour,
|
||||
originBarcode: object.code,
|
||||
));
|
||||
break;
|
||||
default:
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
|
||||
void _fsaHPStateMachine(LogisticObject object, String currentState, Emitter<ScanState> emit) {
|
||||
switch (currentState) {
|
||||
case ObjectStates.station:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.inFA,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.inFA:
|
||||
// Bidirectional: back to station
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.station,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.unknown:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retDS,
|
||||
tour: currentTour,
|
||||
originBarcode: object.code,
|
||||
));
|
||||
break;
|
||||
default:
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
|
||||
void _fsaFRStateMachine(LogisticObject object, String currentState, Emitter<ScanState> emit) {
|
||||
switch (currentState) {
|
||||
case ObjectStates.station:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.inFA,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.inFA:
|
||||
// Bidirectional: back to station
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.station,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
default:
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: vs (Versorgungsstelle)
|
||||
// ============================================================================
|
||||
void _vsStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final objectType = object.type;
|
||||
final currentState = object.state;
|
||||
final subtype = object.subtype.toLowerCase();
|
||||
|
||||
// SB = Safebag
|
||||
if (_isTypeSB(objectType, subtype)) {
|
||||
_vsSBStateMachine(object, currentState, emit);
|
||||
}
|
||||
// ABS = Abfallbehälter
|
||||
else if (_isTypeABS(objectType, subtype)) {
|
||||
_vsABSStateMachine(object, currentState, emit);
|
||||
}
|
||||
// CNTR = Container
|
||||
else if (_isTypeCNTR(objectType, subtype)) {
|
||||
_vsCNTRStateMachine(object, currentState, subtype, emit);
|
||||
}
|
||||
else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
|
||||
void _vsSBStateMachine(LogisticObject object, String currentState, Emitter<ScanState> emit) {
|
||||
if (containerId == null) {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Zustand - Container nicht ausgewählt'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (containerType == 'a') {
|
||||
if (currentState == ObjectStates.inVS) {
|
||||
emit(ScanContainerObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retGI,
|
||||
tour: currentTour,
|
||||
containerId: containerId!,
|
||||
containerType: containerType!,
|
||||
));
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Zustand'));
|
||||
}
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Zustand'));
|
||||
}
|
||||
}
|
||||
|
||||
void _vsABSStateMachine(LogisticObject object, String currentState, Emitter<ScanState> emit) {
|
||||
if (containerId == null) {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Zustand - Container nicht ausgewählt'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (containerType == 'a') {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Zustand'));
|
||||
} else {
|
||||
if (currentState == ObjectStates.inVS) {
|
||||
emit(ScanContainerObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retDS,
|
||||
tour: currentTour,
|
||||
containerId: containerId!,
|
||||
containerType: containerType!,
|
||||
));
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Zustand'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _vsCNTRStateMachine(LogisticObject object, String currentState, String subtype, Emitter<ScanState> emit) {
|
||||
containerId = object.code;
|
||||
|
||||
if (subtype == 'cntra') {
|
||||
containerType = 'a';
|
||||
emit(ScanContainerDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retcGI,
|
||||
tour: currentTour,
|
||||
containerId: containerId!,
|
||||
containerType: containerType!,
|
||||
));
|
||||
} else if (subtype == 'cntrb') {
|
||||
containerType = 'b';
|
||||
emit(ScanContainerDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retcDS,
|
||||
tour: currentTour,
|
||||
containerId: containerId!,
|
||||
containerType: containerType!,
|
||||
));
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Unbekannter Container-Typ'));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: vehVs (Fahrzeug VS)
|
||||
// ============================================================================
|
||||
void _vehVsStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final objectType = object.type;
|
||||
final currentState = object.state;
|
||||
final subtype = object.subtype.toLowerCase();
|
||||
|
||||
// SB and ABS not allowed in vehVs
|
||||
if (_isTypeSB(objectType, subtype) || _isTypeABS(objectType, subtype)) {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
return;
|
||||
}
|
||||
|
||||
// CNTR = Container
|
||||
if (_isTypeCNTR(objectType, subtype)) {
|
||||
_vehVsCNTRStateMachine(object, currentState, subtype, emit);
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Typ'));
|
||||
}
|
||||
}
|
||||
|
||||
void _vehVsCNTRStateMachine(LogisticObject object, String currentState, String subtype, Emitter<ScanState> emit) {
|
||||
if (subtype == 'cntra') {
|
||||
if (currentState == ObjectStates.retcGI) {
|
||||
// Update all container objects and clear container
|
||||
emit(ScanContainerCloseDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.unknown,
|
||||
targetStateForObjects: ObjectStates.retGIFzg,
|
||||
tour: currentTour,
|
||||
containerType: 'a',
|
||||
));
|
||||
containerId = null;
|
||||
containerType = null;
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
} else if (subtype == 'cntrb') {
|
||||
if (currentState == ObjectStates.retcDS) {
|
||||
// Update all container objects and clear container
|
||||
emit(ScanContainerCloseDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.unknown,
|
||||
targetStateForObjects: ObjectStates.retDSFzg,
|
||||
tour: currentTour,
|
||||
containerType: 'b',
|
||||
));
|
||||
containerId = null;
|
||||
containerType = null;
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: gi (Geldinstitut)
|
||||
// ============================================================================
|
||||
void _giStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final objectType = object.type;
|
||||
final currentState = object.state;
|
||||
final subtype = object.subtype.toLowerCase();
|
||||
|
||||
// GK = Geldkassette
|
||||
if (_isTypeGK(objectType, subtype)) {
|
||||
_giGKStateMachine(object, currentState, emit);
|
||||
}
|
||||
// SB = Safebag
|
||||
else if (_isTypeSB(objectType, subtype)) {
|
||||
_giSBStateMachine(object, currentState, emit);
|
||||
}
|
||||
else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Typ'));
|
||||
}
|
||||
}
|
||||
|
||||
void _giGKStateMachine(LogisticObject object, String currentState, Emitter<ScanState> emit) {
|
||||
switch (currentState) {
|
||||
case ObjectStates.retGIFzg:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.finGI,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.unknown:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retDSEmpty,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.delivery:
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
break;
|
||||
default:
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
|
||||
void _giSBStateMachine(LogisticObject object, String currentState, Emitter<ScanState> emit) {
|
||||
if (currentState == ObjectStates.retGIFzg) {
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.finGI,
|
||||
tour: currentTour,
|
||||
));
|
||||
} else {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: vehEnd (Fahrzeug Ende)
|
||||
// ============================================================================
|
||||
void _vehEndStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final currentState = object.state;
|
||||
|
||||
switch (currentState) {
|
||||
case ObjectStates.retFailFzg:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retFailStk,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retDSFzg:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retDSStk,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.delivery:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retDSStk,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retDSEmpty:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retDSStk,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retGIFzg:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retGIStk,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
default:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.retDSErr,
|
||||
tour: currentTour,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: stockEnd (Lager Ende)
|
||||
// ============================================================================
|
||||
void _stockEndStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final currentState = object.state;
|
||||
|
||||
switch (currentState) {
|
||||
case ObjectStates.retFailStk:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.finDSFail,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retDSStk:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.finDS,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retDSErr:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.finDSErr,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.retGIStk:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.finGITmp,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
default:
|
||||
emit(const ScanError(message: 'Fehler: Falscher Status'));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATE MACHINE: stock (Lager - HADAG)
|
||||
// ============================================================================
|
||||
void _stockStateMachine(LogisticObject object, Emitter<ScanState> emit) {
|
||||
final objectType = object.type;
|
||||
final currentState = object.state;
|
||||
final subtype = object.subtype.toLowerCase();
|
||||
|
||||
// Only HP and GK allowed
|
||||
if (!_isTypeHP(objectType, subtype) && !_isTypeGK(objectType, subtype)) {
|
||||
emit(const ScanError(message: 'Fehler: Falscher Typ'));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (currentState) {
|
||||
case ObjectStates.unknown:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.station,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.stkHadag:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.station,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
case ObjectStates.station:
|
||||
emit(ScanObjectDetected(
|
||||
object: object,
|
||||
suggestedState: ObjectStates.stkHadag,
|
||||
tour: currentTour,
|
||||
));
|
||||
break;
|
||||
default:
|
||||
emit(ScanUnknownObject(
|
||||
barcode: object.code,
|
||||
prefix: object.code.length >= 3 ? object.code.substring(0, 3) : '',
|
||||
tour: currentTour,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper methods for type checking
|
||||
// ============================================================================
|
||||
bool _isTypeGK(int type, String subtype) {
|
||||
// GK = Geldkassette (type 1, subtypes meka, mekb, mekc, mekd, beka, bekb, bekc, bekd)
|
||||
return type == 1 || subtype.startsWith('mek') || subtype.startsWith('bek');
|
||||
}
|
||||
|
||||
bool _isTypeHP(int type, String subtype) {
|
||||
// HP = Hauptkasse/Druckerpatronen (type 2, subtypes hp1a, hp1b, etc.)
|
||||
return type == 2 || subtype.startsWith('hp');
|
||||
}
|
||||
|
||||
bool _isTypeFR(int type, String subtype) {
|
||||
// FR = Fahrkartenrolle (type 5, subtype fra)
|
||||
return type == 5 || subtype.startsWith('fr');
|
||||
}
|
||||
|
||||
bool _isTypeSB(int type, String subtype) {
|
||||
// SB = Safebag (type 6)
|
||||
return type == 6 || subtype == 'sb';
|
||||
}
|
||||
|
||||
bool _isTypeABS(int type, String subtype) {
|
||||
// ABS = Abfallbehälter (type 7)
|
||||
return type == 7 || subtype == 'abs';
|
||||
}
|
||||
|
||||
bool _isTypeCNTR(int type, String subtype) {
|
||||
// CNTR = Container (type 8)
|
||||
return type == 8 || subtype.startsWith('cntr');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Legacy simple state machine (fallback)
|
||||
// ============================================================================
|
||||
String _determineNextState(LogisticObject object) {
|
||||
switch (object.state) {
|
||||
case ObjectStates.unknown:
|
||||
return ObjectStates.toDelivery;
|
||||
case ObjectStates.toDelivery:
|
||||
return ObjectStates.delivery;
|
||||
case ObjectStates.delivery:
|
||||
return ObjectStates.station;
|
||||
case ObjectStates.station:
|
||||
return ObjectStates.inFA;
|
||||
case ObjectStates.inFA:
|
||||
return ObjectStates.retGI;
|
||||
case ObjectStates.retGI:
|
||||
return ObjectStates.retGIFzg;
|
||||
case ObjectStates.retGIFzg:
|
||||
return ObjectStates.finGI;
|
||||
case ObjectStates.retFail:
|
||||
return ObjectStates.retFailFzg;
|
||||
case ObjectStates.retFailFzg:
|
||||
return ObjectStates.retFailStk;
|
||||
case ObjectStates.retDS:
|
||||
return ObjectStates.retDSFzg;
|
||||
case ObjectStates.retDSFzg:
|
||||
return ObjectStates.finDS;
|
||||
default:
|
||||
return object.state;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Event handlers
|
||||
// ============================================================================
|
||||
Future<void> _onValidateBarcode(ValidateBarcode event, Emitter<ScanState> emit) async {
|
||||
if (event.barcode.isEmpty) {
|
||||
emit(const ScanValidationError(message: 'Barcode darf nicht leer sein'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.barcode.length < 6) {
|
||||
emit(const ScanValidationError(message: 'Barcode zu kurz'));
|
||||
return;
|
||||
}
|
||||
|
||||
add(ProcessBarcode(barcode: event.barcode));
|
||||
}
|
||||
|
||||
Future<void> _onUpdateObjectState(UpdateObjectState event, Emitter<ScanState> emit) async {
|
||||
emit(ScanProcessing(barcode: event.object.code));
|
||||
|
||||
final result = await repository.updateObjectState(
|
||||
event.object.objectId,
|
||||
event.newState,
|
||||
locationId: currentTour?.locationId,
|
||||
refType: currentTour != null ? _getTourTypeCode(currentTour!.type) : null,
|
||||
refId: currentTour?.tourId,
|
||||
containerCode: event.containerCode,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(ScanError(message: _mapFailureToMessage(failure))),
|
||||
(_) {
|
||||
emit(ScanObjectUpdated(
|
||||
object: event.object.copyWith(state: event.newState),
|
||||
previousState: event.object.state,
|
||||
newState: event.newState,
|
||||
tour: currentTour,
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onCreateUnknownObject(CreateUnknownObject event, Emitter<ScanState> emit) async {
|
||||
emit(ScanProcessing(barcode: event.barcode));
|
||||
|
||||
final result = await repository.createObject(
|
||||
type: event.type,
|
||||
code: event.barcode,
|
||||
isManual: true,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) => emit(ScanError(message: _mapFailureToMessage(failure))),
|
||||
(_) {
|
||||
emit(ScanObjectCreated(barcode: event.barcode));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onResetScan(ResetScan event, Emitter<ScanState> emit) {
|
||||
if (currentTour != null) {
|
||||
emit(ScanReady(tour: currentTour!));
|
||||
} else {
|
||||
emit(ScanInitial());
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper methods
|
||||
// ============================================================================
|
||||
Map<String, String>? _findPageForBarcode(String barcode) {
|
||||
if (currentTour == null) return null;
|
||||
|
||||
for (final page in currentTour!.pages) {
|
||||
if (page.code == barcode) {
|
||||
return {
|
||||
'pageId': page.pageId,
|
||||
'label': page.label ?? page.pageId,
|
||||
};
|
||||
}
|
||||
|
||||
if (page.pageId.toLowerCase().startsWith('fsa') && page.type.isNotEmpty) {
|
||||
// FSA page detected
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _isValidPrefix(String prefix) {
|
||||
final validPrefixes = ['MEK', 'BEK', 'HOP', 'H1P', 'H2P', 'H3P', 'FR', 'SB', 'ABS', 'FZG'];
|
||||
return validPrefixes.any((p) => prefix.toUpperCase().startsWith(p));
|
||||
}
|
||||
|
||||
int? _getTourTypeCode(String type) {
|
||||
switch (type) {
|
||||
case TourTypes.stockStart:
|
||||
return 1;
|
||||
case TourTypes.vehStart:
|
||||
return 2;
|
||||
case TourTypes.veh:
|
||||
return 3;
|
||||
case TourTypes.fsa:
|
||||
return 4;
|
||||
case TourTypes.vs:
|
||||
return 5;
|
||||
case TourTypes.gi:
|
||||
return 6;
|
||||
case TourTypes.vehEnd:
|
||||
return 7;
|
||||
case TourTypes.stockEnd:
|
||||
return 8;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String _mapFailureToMessage(Failure failure) {
|
||||
return switch (failure) {
|
||||
ServerFailure _ => 'Serverfehler: ${failure.message}',
|
||||
NetworkFailure _ => 'Netzwerkfehler. Bitte überprüfen Sie Ihre Internetverbindung.',
|
||||
NotFoundFailure _ => 'Objekt nicht gefunden',
|
||||
BarcodeFailure _ => 'Barcode-Fehler: ${failure.message}',
|
||||
_ => 'Ein Fehler ist aufgetreten: ${failure.message}',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user