Added custom tour ordering

This commit is contained in:
Dennis Nemec
2025-12-20 21:00:33 +01:00
parent 0c61f65961
commit edb8676f5a
13 changed files with 502 additions and 166 deletions

View File

@ -4,6 +4,8 @@ import 'package:hl_lieferservice/dto/discount_add_response.dart';
import 'package:hl_lieferservice/dto/discount_update_response.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_event.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/delivery_state.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_bloc.dart';
import 'package:hl_lieferservice/feature/delivery/detail/bloc/note_event.dart';
import 'package:hl_lieferservice/feature/delivery/detail/repository/delivery_repository.dart';
import 'package:hl_lieferservice/feature/delivery/detail/repository/note_repository.dart';
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
@ -15,6 +17,7 @@ import '../../../../model/delivery.dart' as model;
class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
OperationBloc opBloc;
NoteBloc noteBloc;
DeliveryRepository repository;
NoteRepository noteRepository;
@ -22,6 +25,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
required this.opBloc,
required this.repository,
required this.noteRepository,
required this.noteBloc
}) : super(DeliveryInitial()) {
on<UnscanArticleEvent>(_unscan);
on<ResetScanAmountEvent>(_resetAmount);
@ -34,10 +38,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
on<FinishDeliveryEvent>(_finishDelivery);
}
void _finishDelivery(
FinishDeliveryEvent event,
Emitter<DeliveryState> emit,
) async {
void _finishDelivery(FinishDeliveryEvent event,
Emitter<DeliveryState> emit,) async {
final currentState = state;
opBloc.add(LoadOperation());
@ -70,10 +72,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _updatePayment(
UpdateSelectedPaymentMethodEvent event,
Emitter<DeliveryState> emit,
) {
void _updatePayment(UpdateSelectedPaymentMethodEvent event,
Emitter<DeliveryState> emit,) {
final currentState = state;
if (currentState is DeliveryLoaded) {
@ -85,25 +85,23 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _updateDeliveryOptions(
UpdateDeliveryOptionEvent event,
Emitter<DeliveryState> emit,
) {
void _updateDeliveryOptions(UpdateDeliveryOptionEvent event,
Emitter<DeliveryState> emit,) {
final currentState = state;
if (currentState is DeliveryLoaded) {
List<model.DeliveryOption> options =
currentState.delivery.options.map((option) {
if (option.key == event.key) {
if (option.numerical) {
return option.copyWith(value: event.value);
} else {
return option.copyWith(value: event.value == true ? "1" : "0");
}
}
currentState.delivery.options.map((option) {
if (option.key == event.key) {
if (option.numerical) {
return option.copyWith(value: event.value);
} else {
return option.copyWith(value: event.value == true ? "1" : "0");
}
}
return option;
}).toList();
return option;
}).toList();
emit(
DeliveryLoaded(
@ -113,10 +111,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _updateDiscount(
UpdateDiscountEvent event,
Emitter<DeliveryState> emit,
) async {
void _updateDiscount(UpdateDiscountEvent event,
Emitter<DeliveryState> emit,) async {
opBloc.add(LoadOperation());
try {
@ -139,22 +135,22 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
String discountArticleNumber = delivery.discount!.article.articleNumber;
delivery.discount = model.Discount(
article:
response.values?.article != null
? Article.fromDTO(response.values!.article)
: delivery.discount!.article,
response.values?.article != null
? Article.fromDTO(response.values!.article)
: delivery.discount!.article,
note:
response.values?.note != null
? response.values!.note.noteDescription
: delivery.discount!.note,
response.values?.note != null
? response.values!.note.noteDescription
: delivery.discount!.note,
noteId:
response.values?.note != null
? response.values!.note.rowId
: delivery.discount!.noteId,
response.values?.note != null
? response.values!.note.rowId
: delivery.discount!.noteId,
);
delivery.articles = [
...delivery.articles.where(
(article) => article.articleNumber != discountArticleNumber,
(article) => article.articleNumber != discountArticleNumber,
),
delivery.discount!.article,
];
@ -165,7 +161,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
} catch (e, st) {
debugPrint(
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:",
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event
.deliveryId}:",
);
debugPrint("$e");
debugPrint("$st");
@ -176,10 +173,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _removeDiscount(
RemoveDiscountEvent event,
Emitter<DeliveryState> emit,
) async {
void _removeDiscount(RemoveDiscountEvent event,
Emitter<DeliveryState> emit,) async {
opBloc.add(LoadOperation());
try {
@ -196,9 +191,9 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
delivery.articles
.where(
(article) =>
article.internalId !=
delivery.discount?.article.internalId,
)
article.internalId !=
delivery.discount?.article.internalId,
)
.toList();
delivery.discount = null;
@ -245,6 +240,10 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
noteId: response.values.note.rowId,
);
noteBloc.add(AddNoteOffline(note: response.values.note.noteDescription,
deliveryId: delivery.id,
noteId: response.values.note.rowId));
delivery.articles = [...delivery.articles, delivery.discount!.article];
emit(currentState.copyWith(delivery));
@ -253,7 +252,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
} catch (e, st) {
debugPrint(
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event.deliveryId}:",
"Fehler beim Hinzufügen eins Discounts zur Lieferung: ${event
.deliveryId}:",
);
debugPrint("$e");
debugPrint("$st");
@ -284,7 +284,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
if (currentState is DeliveryLoaded) {
Article article = currentState.delivery.articles.firstWhere(
(article) => article.internalId == int.parse(event.articleId),
(article) => article.internalId == int.parse(event.articleId),
);
article.removeNoteId = noteId;
@ -293,7 +293,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
List<Article> articles = [
...currentState.delivery.articles.where(
(article) => article.internalId != int.parse(event.articleId),
(article) => article.internalId != int.parse(event.articleId),
),
article,
];
@ -313,10 +313,8 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
}
}
void _resetAmount(
ResetScanAmountEvent event,
Emitter<DeliveryState> emit,
) async {
void _resetAmount(ResetScanAmountEvent event,
Emitter<DeliveryState> emit,) async {
opBloc.add(LoadOperation());
try {
@ -325,7 +323,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
if (currentState is DeliveryLoaded) {
Article article = currentState.delivery.articles.firstWhere(
(article) => article.internalId == int.parse(event.articleId),
(article) => article.internalId == int.parse(event.articleId),
);
article.removeNoteId = null;
@ -334,7 +332,7 @@ class DeliveryBloc extends Bloc<DeliveryEvent, DeliveryState> {
List<Article> articles = [
...currentState.delivery.articles.where(
(article) => article.internalId != int.parse(event.articleId),
(article) => article.internalId != int.parse(event.articleId),
),
article,
];

View File

@ -15,7 +15,7 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
final OperationBloc opBloc;
NoteBloc({required this.repository, required this.opBloc})
: super(NoteInitial()) {
: super(NoteInitial()) {
on<LoadNote>(_load);
on<AddNote>(_add);
on<EditNote>(_edit);
@ -23,16 +23,44 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
on<AddImageNote>(_upload);
on<RemoveImageNote>(_removeImage);
on<ResetNotes>(_reset);
on<AddNoteOffline>(_addOffline);
}
Future<void> _reset(ResetNotes event, Emitter<NoteState> emit) async {
emit.call(NoteInitial());
}
Future<void> _removeImage(
RemoveImageNote event,
Emitter<NoteState> emit,
) async {
Future<void> _addOffline(AddNoteOffline event,
Emitter<NoteState> emit,) async {
if (state is NoteInitial) {
emit(
NoteLoadedBase(
notes: [Note(content: event.note, id: int.parse(event.noteId))],
),
);
}
if (state is NoteLoadedBase) {
emit(
NoteLoadedBase(
notes: [
...(state as NoteLoadedBase).notes,
Note(content: event.note, id: int.parse(event.noteId)),
],
),
);
}
if (state is NoteLoaded) {
final current = state as NoteLoaded;
emit(NoteLoaded(notes: [...current.notes, Note(content: event.note, id: int.parse(event.noteId))],
templates: [...current.templates],
images: [...current.images]));
}
}
Future<void> _removeImage(RemoveImageNote event,
Emitter<NoteState> emit,) async {
opBloc.add(LoadOperation());
try {
@ -43,9 +71,9 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
emit.call(
currentState.copyWith(
images:
currentState.images
.where((image) => image.$1.objectId != event.objectId)
.toList(),
currentState.images
.where((image) => image.$1.objectId != event.objectId)
.toList(),
),
);
}
@ -89,7 +117,7 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
try {
List<String> urls =
event.delivery.images.map((image) => image.url).toList();
event.delivery.images.map((image) => image.url).toList();
List<Note> notes = await repository.loadNotes(event.delivery.id);
List<NoteTemplate> templates = await repository.loadTemplates();
List<Uint8List> images = await repository.loadImages(urls);
@ -100,7 +128,7 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
templates: templates,
images: List.generate(
images.length,
(index) => (event.delivery.images[index], images[index]),
(index) => (event.delivery.images[index], images[index]),
),
),
);
@ -148,7 +176,7 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
if (currentState is NoteLoaded) {
List<Note> refreshedNotes = [
...currentState.notes.where(
(note) => note.id != int.parse(event.noteId),
(note) => note.id != int.parse(event.noteId),
),
Note(content: event.content, id: int.parse(event.noteId)),
];
@ -173,9 +201,9 @@ class NoteBloc extends Bloc<NoteEvent, NoteState> {
if (currentState is NoteLoaded) {
List<Note> refreshedNotes =
currentState.notes
.where((note) => note.id != int.parse(event.noteId))
.toList();
currentState.notes
.where((note) => note.id != int.parse(event.noteId))
.toList();
emit.call(currentState.copyWith(notes: refreshedNotes));
}

View File

@ -18,6 +18,14 @@ class AddNote extends NoteEvent {
final String deliveryId;
}
class AddNoteOffline extends NoteEvent {
AddNoteOffline({required this.note, required this.deliveryId, required this.noteId});
final String note;
final String noteId;
final String deliveryId;
}
class RemoveNote extends NoteEvent {
RemoveNote({required this.noteId});

View File

@ -10,14 +10,21 @@ class NoteLoading extends NoteState {}
class NoteLoadingFailed extends NoteState {}
class NoteLoaded extends NoteState {
NoteLoaded({
class NoteLoadedBase extends NoteState {
NoteLoadedBase({
required this.notes,
required this.templates,
required this.images,
});
List<Note> notes;
}
class NoteLoaded extends NoteLoadedBase {
NoteLoaded({
required this.templates,
required this.images,
required super.notes,
});
List<NoteTemplate> templates;
List<(ImageNote, Uint8List)> images;

View File

@ -48,11 +48,29 @@ class _SignatureViewState extends State<SignatureView> {
bool _customerAccepted = false;
bool _noteAccepted = false;
bool _notesEmpty = true;
bool _isCustomerSignatureEmpty = true;
bool _isDriverSignatureEmpty = true;
@override
void initState() {
super.initState();
_customerController.addListener(() {
if (_isCustomerSignatureEmpty != _customerController.isEmpty) {
setState(() {
_isCustomerSignatureEmpty = _customerController.isEmpty;
});
}
});
_driverController.addListener(() {
if (_isDriverSignatureEmpty != _driverController.isEmpty) {
setState(() {
_isDriverSignatureEmpty = _driverController.isEmpty;
});
}
});
// only load notes if they are not already loaded
final noteState = context.read<NoteBloc>().state;
if (noteState is NoteInitial) {
@ -63,6 +81,7 @@ class _SignatureViewState extends State<SignatureView> {
@override
void dispose() {
_customerController.dispose();
_driverController.dispose();
super.dispose();
}
@ -92,12 +111,18 @@ class _SignatureViewState extends State<SignatureView> {
_notesEmpty = current.notes.isEmpty;
});
}
if (current is NoteLoadedBase) {
setState(() {
_notesEmpty = current.notes.isEmpty;
});
}
},
builder: (context, state) {
final current = state;
if (current is NoteLoaded) {
if (current is NoteLoadedBase) {
if (current.notes.isEmpty) {
return const SizedBox(
width: double.infinity,
@ -107,12 +132,12 @@ class _SignatureViewState extends State<SignatureView> {
return ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ListTile(
leading: const Icon(Icons.event_note_outlined),
title: Text(current.notes[index].content),
contentPadding: EdgeInsets.all(20),
contentPadding: const EdgeInsets.all(20),
tileColor: Theme.of(context).colorScheme.onSecondary,
);
},
@ -121,7 +146,7 @@ class _SignatureViewState extends State<SignatureView> {
);
}
return SizedBox(
return const SizedBox(
width: double.infinity,
child: Center(child: CircularProgressIndicator()),
);
@ -156,10 +181,17 @@ class _SignatureViewState extends State<SignatureView> {
});
},
),
const Flexible(
child: Text(
"Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.",
overflow: TextOverflow.fade,
Flexible(
child: InkWell(
onTap: _notesEmpty ? null : () {
setState(() {
_noteAccepted = !_noteAccepted;
});
},
child: Text(
"Ich nehme die oben genannten Anmerkungen zur Lieferung zur Kenntnis.",
overflow: TextOverflow.fade,
),
),
),
],
@ -177,10 +209,17 @@ class _SignatureViewState extends State<SignatureView> {
});
},
),
const Flexible(
child: Text(
"Ich bestätige, dass ich die Ware im ordnungsgemäßen Zustand erhalten habe und, dass die Aufstell- und Einbauarbeiten korrekt durchgeführt wurden.",
overflow: TextOverflow.fade,
Flexible(
child: InkWell(
child: Text(
"Ich bestätige, dass ich die Ware im ordnungsgemäßen Zustand erhalten habe und, dass die Aufstell- und Einbauarbeiten korrekt durchgeführt wurden.",
overflow: TextOverflow.fade,
),
onTap: () {
setState(() {
_customerAccepted = !_customerAccepted;
});
},
),
),
],
@ -195,6 +234,16 @@ class _SignatureViewState extends State<SignatureView> {
Widget build(BuildContext context) {
String formattedDate = DateFormat("dd.MM.yyyy").format(DateTime.now());
bool isButtonEnabled;
if (!_isDriverSigning) {
isButtonEnabled =
_customerAccepted &&
(_noteAccepted || _notesEmpty) &&
!_isCustomerSignatureEmpty;
} else {
isButtonEnabled = !_isDriverSignatureEmpty;
}
return Scaffold(
appBar: AppBar(
title:
@ -247,9 +296,8 @@ class _SignatureViewState extends State<SignatureView> {
child: Center(
child: FilledButton(
onPressed:
!(_customerAccepted && (_noteAccepted || _notesEmpty))
? null
: () async {
isButtonEnabled
? () async {
if (!_isDriverSigning) {
setState(() {
_isDriverSigning = true;
@ -260,7 +308,8 @@ class _SignatureViewState extends State<SignatureView> {
(await _driverController.toPngBytes())!,
);
}
},
}
: null,
child:
!_isDriverSigning
? const Text("Weiter")