From bee5e05296542b9509ca4abda88ceb85a3802a6e Mon Sep 17 00:00:00 2001 From: alzalia1 Date: Fri, 15 Aug 2025 14:48:27 +0200 Subject: [PATCH] feat: complete sell --- .../book_instance_repository.dart | 8 ++ lib/data/services/api_client.dart | 25 ++++ .../sell_page/view_model/sell_view_model.dart | 32 ++++- .../sell_page/widgets/sell_choice_popup.dart | 2 +- lib/ui/sell_page/widgets/sell_page.dart | 115 ++++++++++++------ 5 files changed, 144 insertions(+), 38 deletions(-) diff --git a/lib/data/repositories/book_instance_repository.dart b/lib/data/repositories/book_instance_repository.dart index d59bf32..3e5c036 100644 --- a/lib/data/repositories/book_instance_repository.dart +++ b/lib/data/repositories/book_instance_repository.dart @@ -23,4 +23,12 @@ class BookInstanceRepository { ) async { return await _apiClient.sendBook(book, owner, bal, price); } + + Future> sellBooks(List books) async { + Map res = {}; + for (BookInstance instance in books) { + res[instance.id.toString()] = instance.soldPrice; + } + return await _apiClient.sellBooks(res); + } } diff --git a/lib/data/services/api_client.dart b/lib/data/services/api_client.dart index 3ae3708..b0e5942 100644 --- a/lib/data/services/api_client.dart +++ b/lib/data/services/api_client.dart @@ -310,6 +310,31 @@ class ApiClient { } } + Future> sellBooks(Map books) async { + final client = Client(); + try { + final headers = await _getHeaders({"Content-Type": "application/json"}); + debugPrint("\n\n\n\nMAP: $books\n\n\n\n"); + final body = jsonEncode(books); + debugPrint("\n\n\n\nSENT: $body\n\n\n\n"); + final response = await client.post( + Uri.parse("https://$apiBasePath/book_instance/sell/bulk"), + headers: headers, + body: body, + ); + if (response.statusCode == 200) { + return Result.ok(response.statusCode); + } else { + throw "Unknown error"; + } + } catch (e) { + debugPrint("\n\n\n\nERROR : ${e.toString()}\n\n\n\n"); + return Result.error(Exception(e)); + } finally { + client.close(); + } + } + Future> sendBook( Book book, Owner owner, diff --git a/lib/ui/sell_page/view_model/sell_view_model.dart b/lib/ui/sell_page/view_model/sell_view_model.dart index 717aacb..caff883 100644 --- a/lib/ui/sell_page/view_model/sell_view_model.dart +++ b/lib/ui/sell_page/view_model/sell_view_model.dart @@ -50,14 +50,41 @@ class SellViewModel extends ChangeNotifier { List get scannedBooks => _scannedBooks; bool isScanLoaded = false; + bool isSendingSell = false; + double minimumAmount = 0; void sellBook(BookStack addedBook) { + minimumAmount += addedBook.instance.price; _soldBooks.add(addedBook); notifyListeners(); } - void sendSell() { + void sendSell(double givenAmount) async { + isSendingSell = true; + notifyListeners(); + List toSend = []; + int nbOfPl = 0; + for (BookStack book in _soldBooks) { + if (book.instance.price != 0) { + book.instance.soldPrice = book.instance.price; + givenAmount -= book.instance.price; + toSend.add(book.instance); + } else { + nbOfPl++; + } + } + if (nbOfPl != 0) { + double amountPerPl = givenAmount / nbOfPl; + for (BookStack book in _soldBooks) { + if (book.instance.price == 0) { + book.instance.soldPrice = amountPerPl; + toSend.add(book.instance); + } + } + } + await _bookInstanceRepository.sellBooks(toSend); _soldBooks.clear(); + isSendingSell = false; notifyListeners(); } @@ -75,6 +102,9 @@ class SellViewModel extends ChangeNotifier { switch (result) { case Ok(): for (BookInstance instance in result.value) { + if (instance.available == false) { + continue; + } if (_soldBooks .where((book) => book.instance.id == instance.id) .isNotEmpty) { diff --git a/lib/ui/sell_page/widgets/sell_choice_popup.dart b/lib/ui/sell_page/widgets/sell_choice_popup.dart index 340c128..1931a86 100644 --- a/lib/ui/sell_page/widgets/sell_choice_popup.dart +++ b/lib/ui/sell_page/widgets/sell_choice_popup.dart @@ -25,7 +25,7 @@ class SellChoicePopup extends StatelessWidget { children: [ (viewModel.scannedBooks.isEmpty) ? Text( - "Ce livre n'a jamais été rentré, ou vous l'avez déjà mis dans cette vente.", + "Ce livre n'a jamais été rentré, il a déjà été vendu ou vous l'avez déjà mis dans cette vente.", ) : SizedBox(), for (BookStack book in viewModel.scannedBooks) diff --git a/lib/ui/sell_page/widgets/sell_page.dart b/lib/ui/sell_page/widgets/sell_page.dart index 1cef8bb..3b3f2a8 100644 --- a/lib/ui/sell_page/widgets/sell_page.dart +++ b/lib/ui/sell_page/widgets/sell_page.dart @@ -17,6 +17,8 @@ class SellPage extends StatefulWidget { } class _SellPageState extends State { + TextEditingController price = TextEditingController(); + @override Widget build(BuildContext context) { return Scaffold( @@ -70,52 +72,60 @@ class _SellPageState extends State { ), SizedBox(height: 15), Expanded( - child: ListView( - children: [ - (widget.viewModel.scannedBooks.isEmpty) - ? Center(child: Text("Aucun")) - : SizedBox(), - for (BookStack book in widget.viewModel.soldBooks) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 15, - ), - child: Card( - child: ListTile( - leading: Text( - "${book.instance.price.toString()}€", - style: TextStyle(fontSize: 30), + child: (widget.viewModel.isSendingSell) + ? Center(child: CircularProgressIndicator()) + : ListView( + children: [ + (widget.viewModel.scannedBooks.isEmpty) + ? Center(child: Text("Aucun")) + : SizedBox(), + for (BookStack book + in widget.viewModel.soldBooks) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + ), + child: Card( + child: ListTile( + leading: Text( + "${book.instance.price.toString()}€", + style: TextStyle(fontSize: 30), + ), + title: Text( + "${book.book.title} · ${book.book.author}", + ), + subtitle: Text( + "${book.owner.firstName} ${book.owner.lastName} (${book.shortId()})", + ), + trailing: IconButton( + onPressed: () { + widget.viewModel.deleteBook( + book.instance.id, + ); + }, + icon: Icon(Icons.delete), + ), + ), + ), ), - title: Text( - "${book.book.title} · ${book.book.author}", - ), - subtitle: Text( - "${book.owner.firstName} ${book.owner.lastName} (${book.shortId()})", - ), - trailing: IconButton( - onPressed: () { - widget.viewModel.deleteBook( - book.instance.id, - ); - }, - icon: Icon(Icons.delete), - ), - ), - ), + ], ), - ], - ), ), SizedBox(height: 40), - Text("Montant minimum à payer : 20€"), + Text( + "Montant minimum à payer : ${widget.viewModel.minimumAmount.toString()}€", + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 60.0), child: SizedBox( child: TextField( + controller: price, decoration: InputDecoration( labelText: "Argent reçu", + hintText: + "Utilisez un point (.) pour les virgules (,)", helperText: - "L'argent reçu sera réparti automatiquement", + "L'argent reçu sera réparti automatiquement.", suffixText: "€", border: OutlineInputBorder(), ), @@ -129,7 +139,40 @@ class _SellPageState extends State { children: [ IconButton( onPressed: () { - widget.viewModel.sendSell(); + if (double.tryParse(price.text) == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Veuillez indiquer un prix de vente valide", + ), + behavior: SnackBarBehavior.floating, + ), + ); + return; + } else if (double.parse(price.text) < + widget.viewModel.minimumAmount) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "Le prix de vente est inférieur au montant minimum", + ), + behavior: SnackBarBehavior.floating, + ), + ); + return; + } + widget.viewModel.sendSell( + double.parse(price.text), + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + "La vente a bien été enregistrée.", + ), + behavior: SnackBarBehavior.floating, + ), + ); + return; }, icon: Icon(Icons.check), style: ButtonStyle(