diff --git a/README.md b/README.md index 2501fde..ed140aa 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,7 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + +## JSON code generation + +use `dart run build_runner watch --delete-conflicting-outputs` for generating \ No newline at end of file diff --git a/lib/dto/set_article_amount_request.dart b/lib/dto/set_article_amount_request.dart new file mode 100644 index 0000000..ebf3f61 --- /dev/null +++ b/lib/dto/set_article_amount_request.dart @@ -0,0 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'set_article_amount_request.g.dart'; + +@JsonSerializable(fieldRename: FieldRename.snake) +class SetArticleAmountRequestDTO { + SetArticleAmountRequestDTO({ + required this.articleId, + required this.deliveryId, + required this.amount, + this.reason + }); + + String deliveryId; + int amount; + String articleId; + String? reason; + + factory SetArticleAmountRequestDTO.fromJson(Map json) => + _$SetArticleAmountRequestDTOFromJson(json); + + Map toJson() => _$SetArticleAmountRequestDTOToJson(this); +} diff --git a/lib/dto/set_article_amount_response.dart b/lib/dto/set_article_amount_response.dart new file mode 100644 index 0000000..05bc199 --- /dev/null +++ b/lib/dto/set_article_amount_response.dart @@ -0,0 +1,21 @@ +import 'package:hl_lieferservice/dto/basic_response.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'set_article_amount_response.g.dart'; + +@JsonSerializable(fieldRename: FieldRename.snake) +class SetArticleAmountResponseDTO extends BasicResponseDTO { + SetArticleAmountResponseDTO({ + required super.succeeded, + required super.message, + this.noteId + }); + + String? noteId; + + factory SetArticleAmountResponseDTO.fromJson(Map json) => + _$SetArticleAmountResponseDTOFromJson(json); + + @override + Map toJson() => _$SetArticleAmountResponseDTOToJson(this); +} diff --git a/lib/feature/delivery/bloc/tour_bloc.dart b/lib/feature/delivery/bloc/tour_bloc.dart index b4438c4..3f11092 100644 --- a/lib/feature/delivery/bloc/tour_bloc.dart +++ b/lib/feature/delivery/bloc/tour_bloc.dart @@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_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/feature/delivery/overview/model/sorting_information.dart'; -import 'package:hl_lieferservice/feature/delivery/overview/repository/tour_repository.dart'; +import 'package:hl_lieferservice/feature/delivery/repository/tour_repository.dart'; import 'package:hl_lieferservice/feature/delivery/overview/service/distance_service.dart'; import 'package:hl_lieferservice/feature/delivery/overview/service/reorder_service.dart'; import 'package:hl_lieferservice/model/tour.dart'; @@ -55,6 +55,7 @@ class TourBloc extends Bloc { on(_requestSortingInformation); on(_reorderDelivery); on(_carsLoaded); + on(_setArticleAmount); } @override @@ -64,6 +65,33 @@ class TourBloc extends Bloc { return super.close(); } + void _setArticleAmount( + SetArticleAmountEvent event, + Emitter emit, + ) async { + final currentState = state; + if (currentState is TourLoaded) { + opBloc.add(LoadOperation()); + try { + await tourRepository.setArticleAmount( + event.deliveryId, + event.articleId, + event.amount, + event.reason + ); + + opBloc.add(FinishOperation()); + } catch (e, st) { + opBloc.add( + FailOperation(message: "Fehler beim Ändern der Menge des Artikels"), + ); + + debugPrint("$e"); + debugPrint("$st"); + } + } + } + void _carsLoaded(CarsLoadedEvent event, Emitter emit) { final currentState = state; if (currentState is TourLoaded) { @@ -78,13 +106,16 @@ class TourBloc extends Bloc { ) async { final currentState = state; if (currentState is TourLoaded) { - int newPosition = event.newPosition == currentState.sortingInformation.sorting.length ? event.newPosition - 1 : event.newPosition; - SortingInformation informationOld = currentState.sortingInformation.sorting - .firstWhere((info) => info.position == event.oldPosition); - - SortingInformation information = currentState + int newPosition = + event.newPosition == currentState.sortingInformation.sorting.length + ? event.newPosition - 1 + : event.newPosition; + SortingInformation informationOld = currentState .sortingInformation .sorting + .firstWhere((info) => info.position == event.oldPosition); + + SortingInformation information = currentState.sortingInformation.sorting .firstWhere((info) => info.position == newPosition); information.position = event.oldPosition; diff --git a/lib/feature/delivery/bloc/tour_event.dart b/lib/feature/delivery/bloc/tour_event.dart index e008ff2..b35986f 100644 --- a/lib/feature/delivery/bloc/tour_event.dart +++ b/lib/feature/delivery/bloc/tour_event.dart @@ -203,3 +203,17 @@ class FinishDeliveryEvent extends TourEvent { Uint8List customerSignature; Uint8List driverSignature; } + +class SetArticleAmountEvent extends TourEvent { + final String deliveryId; + final String articleId; + final String? reason; + final int amount; + + SetArticleAmountEvent({ + required this.deliveryId, + required this.articleId, + required this.amount, + this.reason, + }); +} \ No newline at end of file diff --git a/lib/feature/delivery/detail/presentation/article/article_list_item.dart b/lib/feature/delivery/detail/presentation/article/article_list_item.dart index 725873e..4d6824b 100644 --- a/lib/feature/delivery/detail/presentation/article/article_list_item.dart +++ b/lib/feature/delivery/detail/presentation/article/article_list_item.dart @@ -23,6 +23,10 @@ class _ArticleListItem extends State { Color? color; Color? textColor; + if (!widget.article.scannable) { + amount = widget.article.amount; + } + if (amount == 0) { color = Colors.redAccent; textColor = Theme.of(context).colorScheme.onSecondary; @@ -56,7 +60,8 @@ class _ArticleListItem extends State { ), ); - if (widget.article.unscanned()) { + if ((widget.article.unscanned() && widget.article.scannable) || + !widget.article.scannable && widget.article.amount == 0) { actionButton = IconButton.outlined( style: ButtonStyle( backgroundColor: WidgetStatePropertyAll(Colors.blueAccent), diff --git a/lib/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart b/lib/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart index 52ae391..b034eff 100644 --- a/lib/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart +++ b/lib/feature/delivery/detail/presentation/article/article_reset_scan_dialog.dart @@ -20,33 +20,86 @@ class ResetArticleAmountDialog extends StatefulWidget { } class _ResetArticleAmountDialogState extends State { + int _selectedAmount = 1; + void _reset() { - context.read().add( - ResetScanAmountEvent( - articleId: widget.article.internalId.toString(), - deliveryId: widget.deliveryId, - ), - ); + String deliveryId = widget.deliveryId; + String articleId = widget.article.internalId.toString(); + + if (widget.article.scannable) { + context.read().add( + ResetScanAmountEvent( + articleId: widget.article.internalId.toString(), + deliveryId: widget.deliveryId, + ), + ); + } else { + debugPrint("ID: $articleId"); + debugPrint("AMOUNT :$_selectedAmount"); + + context.read().add( + SetArticleAmountEvent( + deliveryId: deliveryId, + articleId: articleId, + amount: _selectedAmount, + ), + ); + } Navigator.pop(context); } + Widget _amountSelection() { + final list = List.generate(3, (index) => index + 1); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Anzahl:", style: Theme.of(context).textTheme.labelLarge), + Padding( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: + list + .map( + (index) => ChoiceChip( + label: Text("$index"), + selected: _selectedAmount == index, + onSelected: (bool selected) { + setState(() { + _selectedAmount = index; + }); + }, + ), + ) + .toList(), + ), + ), + ], + ); + } + @override Widget build(BuildContext context) { return AlertDialog( title: const Text("Anzahl Artikel zurücksetzen?"), content: SizedBox( - height: 120, + height: MediaQuery.of(context).size.height * 0.25, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text("Wollen Sie die entfernten Artikel wieder hinzufügen?"), + !widget.article.scannable ? _amountSelection() : Container(), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ FilledButton( onPressed: _reset, - child: const Text("Zurücksetzen"), + child: + widget.article.scannable + ? const Text("Zurücksetzen") + : const Text("Hinzufügen"), ), OutlinedButton( onPressed: () { diff --git a/lib/feature/delivery/detail/presentation/article/article_unscan_dialog.dart b/lib/feature/delivery/detail/presentation/article/article_unscan_dialog.dart index 1c4fab1..7e037a7 100644 --- a/lib/feature/delivery/detail/presentation/article/article_unscan_dialog.dart +++ b/lib/feature/delivery/detail/presentation/article/article_unscan_dialog.dart @@ -7,7 +7,11 @@ import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart'; import '../../../../../model/article.dart'; class ArticleUnscanDialog extends StatefulWidget { - const ArticleUnscanDialog({super.key, required this.article, required this.deliveryId}); + const ArticleUnscanDialog({ + super.key, + required this.article, + required this.deliveryId, + }); final String deliveryId; final Article article; @@ -23,14 +27,32 @@ class _ArticleUnscanDialogState extends State { final _formKey = GlobalKey(); void _unscan() { - context.read().add( - UnscanArticleEvent( - deliveryId: widget.deliveryId, - articleId: widget.article.internalId.toString(), - newAmount: int.parse(unscanAmountController.text), - reason: unscanNoteController.text, - ), - ); + int amountToBeDeleted = int.parse(unscanAmountController.text); + String deliveryId = widget.deliveryId; + String articleId = widget.article.internalId.toString(); + String reason = unscanNoteController.text; + + if (widget.article.scannable) { + context.read().add( + UnscanArticleEvent( + deliveryId: deliveryId, + articleId: articleId, + newAmount: amountToBeDeleted, + reason: reason, + ), + ); + } else { + // If the article is not scannable we need to adjust the quantity of the article + // directly. + context.read().add( + SetArticleAmountEvent( + deliveryId: deliveryId, + articleId: articleId, + amount: widget.article.amount - amountToBeDeleted, + reason: reason + ), + ); + } Navigator.pop(context); } diff --git a/lib/feature/delivery/detail/presentation/delivery_detail_page.dart b/lib/feature/delivery/detail/presentation/delivery_detail_page.dart index 934a02e..f5801f2 100644 --- a/lib/feature/delivery/detail/presentation/delivery_detail_page.dart +++ b/lib/feature/delivery/detail/presentation/delivery_detail_page.dart @@ -136,6 +136,9 @@ class _DeliveryDetailState extends State { driverSignature: driver, ), ); + + Navigator.pop(context); + Navigator.pop(context); } Widget _stepsNavigation(Delivery delivery) { diff --git a/lib/feature/delivery/detail/presentation/delivery_sign.dart b/lib/feature/delivery/detail/presentation/delivery_sign.dart index 2f97324..a3d322f 100644 --- a/lib/feature/delivery/detail/presentation/delivery_sign.dart +++ b/lib/feature/delivery/detail/presentation/delivery_sign.dart @@ -117,8 +117,6 @@ class _SignatureViewState extends State { builder: (context, state) { final current = state; - debugPrint("STATE: $current"); - if (current is NoteLoaded) { if (current.notes.isEmpty) { return const SizedBox( diff --git a/lib/feature/delivery/detail/repository/delivery_repository.dart b/lib/feature/delivery/detail/repository/delivery_repository.dart index a77c842..ac8130a 100644 --- a/lib/feature/delivery/detail/repository/delivery_repository.dart +++ b/lib/feature/delivery/detail/repository/delivery_repository.dart @@ -5,7 +5,7 @@ import 'package:hl_lieferservice/dto/discount_remove_response.dart'; import 'package:hl_lieferservice/dto/discount_update_response.dart'; import 'package:hl_lieferservice/feature/delivery/detail/repository/note_repository.dart'; import 'package:hl_lieferservice/feature/delivery/detail/service/notes_service.dart'; -import 'package:hl_lieferservice/feature/delivery/overview/service/delivery_info_service.dart'; +import 'package:hl_lieferservice/feature/delivery/service/tour_service.dart'; import 'package:hl_lieferservice/model/delivery.dart'; class DeliveryRepository { diff --git a/lib/feature/delivery/overview/presentation/delivery_list.dart b/lib/feature/delivery/overview/presentation/delivery_list.dart index cf0c28c..882da42 100644 --- a/lib/feature/delivery/overview/presentation/delivery_list.dart +++ b/lib/feature/delivery/overview/presentation/delivery_list.dart @@ -60,7 +60,7 @@ class _DeliveryListState extends State { .where( (delivery) => delivery.carId == widget.selectedCarId && - delivery.allArticlesScanned(), + delivery.allArticlesScanned() || delivery.state == DeliveryState.finished, ) .toList(); diff --git a/lib/feature/delivery/overview/repository/tour_repository.dart b/lib/feature/delivery/repository/tour_repository.dart similarity index 87% rename from lib/feature/delivery/overview/repository/tour_repository.dart rename to lib/feature/delivery/repository/tour_repository.dart index 4244c75..500dd9c 100644 --- a/lib/feature/delivery/overview/repository/tour_repository.dart +++ b/lib/feature/delivery/repository/tour_repository.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; -import 'package:hl_lieferservice/feature/delivery/overview/service/delivery_info_service.dart'; +import 'package:hl_lieferservice/dto/set_article_amount_response.dart'; +import 'package:hl_lieferservice/feature/delivery/service/tour_service.dart'; import 'package:hl_lieferservice/model/delivery.dart'; import 'package:hl_lieferservice/model/tour.dart'; import 'package:rxdart/rxdart.dart'; @@ -9,8 +10,8 @@ import '../../../../dto/discount_add_response.dart'; import '../../../../dto/discount_remove_response.dart'; import '../../../../dto/discount_update_response.dart'; import '../../../../model/article.dart'; -import '../../detail/repository/note_repository.dart'; -import '../../detail/service/notes_service.dart'; +import '../detail/repository/note_repository.dart'; +import '../detail/service/notes_service.dart'; enum ScanResult { scanned, alreadyScanned, notFound } @@ -172,13 +173,19 @@ class TourRepository { Delivery delivery = tour.deliveries.firstWhere( (delivery) => delivery.id == deliveryId, ); + Article discountArticle = Article.fromDTO(response.values.article); delivery.totalNetValue = response.values.receipt.net; delivery.totalGrossValue = response.values.receipt.gross; + delivery.discount = Discount( article: Article.fromDTO(response.values.article), note: response.values.note.noteDescription, noteId: response.values.note.rowId, ); + delivery.articles = [ + ...delivery.articles, + discountArticle, + ]; _tourStream.add(tour); } @@ -275,6 +282,45 @@ class TourRepository { Future finishDelivery(String deliveryId) async { await _changeState(deliveryId, DeliveryState.finished); + Delivery delivery = _tourStream.value!.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + + await _updateDelivery(delivery); + await service.finishDelivery(deliveryId); + } + + Future setArticleAmount( + String deliveryId, + String articleId, + int amount, + String? reason, + ) async { + if (!_tourStream.hasValue) { + throw TourNotFoundException(); + } + + try { + SetArticleAmountResponseDTO dto = await service.setArticleAmount( + deliveryId, + articleId, + amount, + reason, + ); + + Delivery delivery = _tourStream.value!.deliveries.firstWhere( + (delivery) => delivery.id == deliveryId, + ); + Article article = delivery.articles.firstWhere( + (article) => article.internalId == int.parse(articleId), + ); + + article.amount = amount; + article.removeNoteId = dto.noteId; + _tourStream.add(_tourStream.value); + } catch (_) { + rethrow; + } } Future _changeState(String deliveryId, DeliveryState state) async { diff --git a/lib/feature/delivery/overview/service/delivery_info_service.dart b/lib/feature/delivery/service/tour_service.dart similarity index 80% rename from lib/feature/delivery/overview/service/delivery_info_service.dart rename to lib/feature/delivery/service/tour_service.dart index 36d2fe1..a199236 100644 --- a/lib/feature/delivery/overview/service/delivery_info_service.dart +++ b/lib/feature/delivery/service/tour_service.dart @@ -7,18 +7,20 @@ import 'package:hl_lieferservice/dto/delivery_update.dart'; import 'package:hl_lieferservice/dto/delivery_update_response.dart'; import 'package:hl_lieferservice/dto/payment.dart'; import 'package:hl_lieferservice/dto/payments.dart'; +import 'package:hl_lieferservice/dto/set_article_amount_request.dart'; +import 'package:hl_lieferservice/dto/set_article_amount_response.dart'; import 'package:hl_lieferservice/model/car.dart'; import 'package:hl_lieferservice/model/delivery.dart'; import 'package:hl_lieferservice/model/tour.dart'; import 'package:hl_lieferservice/util.dart'; import 'package:http/http.dart'; -import '../../../../dto/basic_response.dart'; -import '../../../../dto/discount_add_response.dart'; -import '../../../../dto/discount_remove_response.dart'; -import '../../../../dto/discount_update_response.dart'; -import '../../../../dto/scan_response.dart'; -import '../../../authentication/exceptions.dart'; +import '../../../dto/basic_response.dart'; +import '../../../dto/discount_add_response.dart'; +import '../../../dto/discount_remove_response.dart'; +import '../../../dto/discount_update_response.dart'; +import '../../../dto/scan_response.dart'; +import '../../authentication/exceptions.dart'; class TourService { TourService(); @@ -267,6 +269,76 @@ class TourService { } } + Future finishDelivery(String deliveryId) async { + try { + var response = await post( + urlBuilder("_web_finishDelivery"), + headers: getSessionOrThrow(), + body: {"delivery_id": deliveryId}, + ); + + if (response.statusCode == HttpStatus.unauthorized) { + throw UserUnauthorized(); + } + + debugPrint("BODY: ${response.body}"); + Map responseJson = jsonDecode(response.body); + + // let it throw, if the values are invalid + return BasicResponseDTO.fromJson(responseJson); + } catch (e, st) { + debugPrint("ERROR while adding discount"); + debugPrint(e.toString()); + debugPrint(st.toString()); + + rethrow; + } + } + + Future setArticleAmount( + String deliveryId, + String articleId, + int amount, + String? reason, + ) async { + try { + var response = await post( + urlBuilder("_web_setArticleAmount"), + headers: {...getSessionOrThrow(), "Content-Type": "application/json"}, + body: jsonEncode( + SetArticleAmountRequestDTO( + articleId: articleId, + deliveryId: deliveryId, + amount: amount, + reason: reason, + ), + ), + ); + + if (response.statusCode == HttpStatus.unauthorized) { + throw UserUnauthorized(); + } + + debugPrint("BODY: ${response.body}"); + + Map responseJson = jsonDecode(response.body); + // let it throw, if the values are invalid + SetArticleAmountResponseDTO responseDto = + SetArticleAmountResponseDTO.fromJson(responseJson); + + if (!responseDto.succeeded) { + throw responseDto.message; + } else { + return responseDto; + } + } catch (e, st) { + debugPrint(e.toString()); + debugPrint(st.toString()); + + rethrow; + } + } + Future removeDiscount(String deliveryId) async { try { var response = await post( diff --git a/lib/feature/scan/presentation/scan_page.dart b/lib/feature/scan/presentation/scan_page.dart index 4897b02..bca921b 100644 --- a/lib/feature/scan/presentation/scan_page.dart +++ b/lib/feature/scan/presentation/scan_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart'; import 'package:hl_lieferservice/feature/scan/presentation/scan_screen.dart'; +import 'package:hl_lieferservice/model/delivery.dart'; import 'package:hl_lieferservice/model/tour.dart'; import 'package:hl_lieferservice/widget/home/bloc/navigation_bloc.dart'; import 'package:hl_lieferservice/widget/home/bloc/navigation_event.dart'; @@ -36,7 +37,7 @@ class _ScanPageState extends State { Widget _tourSteps(Tour tour) { var allArticlesScanned = tour.deliveries.every( - (delivery) => delivery.allArticlesScanned(), + (delivery) => delivery.allArticlesScanned() || delivery.state == DeliveryState.finished, ); return Stepper( diff --git a/lib/model/delivery.dart b/lib/model/delivery.dart index 7aca977..eea5abc 100644 --- a/lib/model/delivery.dart +++ b/lib/model/delivery.dart @@ -1,6 +1,5 @@ import 'dart:typed_data'; -import 'package:flutter/cupertino.dart'; import 'package:hl_lieferservice/dto/contact_person.dart'; import 'package:hl_lieferservice/dto/delivery.dart'; import 'package:hl_lieferservice/dto/image_note_response.dart'; @@ -297,11 +296,7 @@ class Delivery implements Comparable { List
getDeliveredArticles() { return articles .where( - (article) { - debugPrint("Scannable: ${article.scannable}"); - - return article.scannedAmount > 0 || !article.scannable; - }, + (article) => article.scannedAmount > 0 || !article.scannable, ) .toList(); } diff --git a/lib/widget/app.dart b/lib/widget/app.dart index b743d48..a294899 100644 --- a/lib/widget/app.dart +++ b/lib/widget/app.dart @@ -6,13 +6,13 @@ import 'package:hl_lieferservice/feature/authentication/presentation/login_enfor import 'package:hl_lieferservice/feature/authentication/service/userinfo.dart'; import 'package:hl_lieferservice/feature/cars/presentation/car_management_page.dart'; import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart'; -import 'package:hl_lieferservice/feature/delivery/overview/repository/tour_repository.dart'; +import 'package:hl_lieferservice/feature/delivery/repository/tour_repository.dart'; import 'package:hl_lieferservice/widget/home/bloc/navigation_bloc.dart'; import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart'; import 'package:hl_lieferservice/widget/operations/presentation/operation_view_enforcer.dart'; import 'package:hl_lieferservice/bloc/app_states.dart'; -import '../feature/delivery/overview/service/delivery_info_service.dart'; +import '../feature/delivery/service/tour_service.dart'; import 'home/presentation/home.dart'; class DeliveryApp extends StatefulWidget {