917 lines
28 KiB
Dart
917 lines
28 KiB
Dart
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}',
|
|
};
|
|
}
|
|
}
|