|
|
@ -1,9 +1,14 @@ |
|
|
import 'package:flutter/foundation.dart'; |
|
|
import 'package:flutter/foundation.dart'; |
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:http/http.dart' as http; |
|
|
import 'package:http/http.dart' as http; |
|
|
|
|
|
import 'package:itrimobe/models/command_detail.dart'; |
|
|
|
|
|
import 'package:itrimobe/models/payment_method.dart'; |
|
|
|
|
|
import 'package:itrimobe/pages/caisse_screen.dart'; |
|
|
|
|
|
import 'package:itrimobe/pages/facture_screen.dart'; |
|
|
import 'dart:convert'; |
|
|
import 'dart:convert'; |
|
|
|
|
|
|
|
|
import 'package:itrimobe/pages/tables.dart'; |
|
|
import 'package:itrimobe/pages/tables.dart'; |
|
|
|
|
|
import 'package:itrimobe/services/pdf_service.dart'; |
|
|
|
|
|
|
|
|
import '../layouts/main_layout.dart'; |
|
|
import '../layouts/main_layout.dart'; |
|
|
|
|
|
|
|
|
@ -315,7 +320,12 @@ class _CartPageState extends State<CartPage> { |
|
|
return sum + (item.prix * item.quantity); |
|
|
return sum + (item.prix * item.quantity); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// Confirmer le paiement |
|
|
// Sélectionner le moyen de paiement |
|
|
|
|
|
PaymentMethod? selectedPaymentMethod = await _showPaymentMethodDialog(); |
|
|
|
|
|
|
|
|
|
|
|
if (selectedPaymentMethod == null) return; |
|
|
|
|
|
|
|
|
|
|
|
// Confirmer le paiement avec le moyen sélectionné |
|
|
final bool? confirm = await showDialog<bool>( |
|
|
final bool? confirm = await showDialog<bool>( |
|
|
context: context, |
|
|
context: context, |
|
|
builder: |
|
|
builder: |
|
|
@ -337,6 +347,49 @@ class _CartPageState extends State<CartPage> { |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
const SizedBox(height: 16), |
|
|
const SizedBox(height: 16), |
|
|
|
|
|
Container( |
|
|
|
|
|
padding: const EdgeInsets.all(12), |
|
|
|
|
|
decoration: BoxDecoration( |
|
|
|
|
|
color: selectedPaymentMethod.color.withOpacity(0.1), |
|
|
|
|
|
borderRadius: BorderRadius.circular(8), |
|
|
|
|
|
border: Border.all( |
|
|
|
|
|
color: selectedPaymentMethod.color.withOpacity(0.3), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
child: Row( |
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
|
children: [ |
|
|
|
|
|
Icon( |
|
|
|
|
|
selectedPaymentMethod.icon, |
|
|
|
|
|
color: selectedPaymentMethod.color, |
|
|
|
|
|
size: 24, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(width: 8), |
|
|
|
|
|
Column( |
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
|
|
children: [ |
|
|
|
|
|
Text( |
|
|
|
|
|
'Paiement: ${selectedPaymentMethod.name}', |
|
|
|
|
|
style: TextStyle( |
|
|
|
|
|
fontWeight: FontWeight.w600, |
|
|
|
|
|
color: selectedPaymentMethod.color, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
Text( |
|
|
|
|
|
selectedPaymentMethod.description, |
|
|
|
|
|
style: TextStyle( |
|
|
|
|
|
fontSize: 12, |
|
|
|
|
|
color: selectedPaymentMethod.color.withOpacity( |
|
|
|
|
|
0.7, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 16), |
|
|
const Text('Valider et payer cette commande ?'), |
|
|
const Text('Valider et payer cette commande ?'), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
@ -348,9 +401,12 @@ class _CartPageState extends State<CartPage> { |
|
|
ElevatedButton( |
|
|
ElevatedButton( |
|
|
onPressed: () => Navigator.of(context).pop(true), |
|
|
onPressed: () => Navigator.of(context).pop(true), |
|
|
style: ElevatedButton.styleFrom( |
|
|
style: ElevatedButton.styleFrom( |
|
|
backgroundColor: Colors.orange[600], |
|
|
backgroundColor: selectedPaymentMethod.color, |
|
|
|
|
|
), |
|
|
|
|
|
child: const Text( |
|
|
|
|
|
'Confirmer le paiement', |
|
|
|
|
|
style: TextStyle(color: Colors.white), |
|
|
), |
|
|
), |
|
|
child: const Text('Confirmer le paiement'), |
|
|
|
|
|
), |
|
|
), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
@ -363,13 +419,13 @@ class _CartPageState extends State<CartPage> { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 1. Créer la commande avec les items du panier |
|
|
// Créer la commande avec le moyen de paiement sélectionné |
|
|
final response = await http.post( |
|
|
final response = await http.post( |
|
|
Uri.parse('https://restaurant.careeracademy.mg/api/commandes'), |
|
|
Uri.parse('https://restaurant.careeracademy.mg/api/commandes'), |
|
|
headers: {'Content-Type': 'application/json'}, |
|
|
headers: {'Content-Type': 'application/json'}, |
|
|
body: jsonEncode({ |
|
|
body: jsonEncode({ |
|
|
'table_id': widget.tableId, |
|
|
'table_id': widget.tableId, |
|
|
'statut': 'payee', // Directement payée |
|
|
'statut': 'payee', |
|
|
'items': |
|
|
'items': |
|
|
_cartItems |
|
|
_cartItems |
|
|
.map( |
|
|
.map( |
|
|
@ -378,33 +434,47 @@ class _CartPageState extends State<CartPage> { |
|
|
'quantite': item.quantity, |
|
|
'quantite': item.quantity, |
|
|
'prix_unitaire': item.prix, |
|
|
'prix_unitaire': item.prix, |
|
|
'commentaire': item.commentaire, |
|
|
'commentaire': item.commentaire, |
|
|
// Pas de réservation |
|
|
|
|
|
}, |
|
|
}, |
|
|
) |
|
|
) |
|
|
.toList(), |
|
|
.toList(), |
|
|
'methode_paiement': 'especes', |
|
|
'methode_paiement': |
|
|
|
|
|
selectedPaymentMethod.id, // mvola, carte, ou especes |
|
|
'montant_paye': total, |
|
|
'montant_paye': total, |
|
|
'date_paiement': DateTime.now().toIso8601String(), |
|
|
'date_paiement': DateTime.now().toIso8601String(), |
|
|
'client_id': 1, // Valeur par défaut |
|
|
'client_id': 1, |
|
|
'reservation_id': 1, |
|
|
'reservation_id': 1, |
|
|
'serveur': 'Serveur par défaut', // Valeur par défaut |
|
|
'serveur': 'Serveur par défaut', |
|
|
}), |
|
|
}), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
if (response.statusCode == 201) { |
|
|
if (response.statusCode == 201) { |
|
|
final commandeData = jsonDecode(response.body); |
|
|
final commandeData = jsonDecode(response.body); |
|
|
|
|
|
|
|
|
// 2. Libérer la table |
|
|
// Libérer la table |
|
|
await http.patch( |
|
|
await http.patch( |
|
|
Uri.parse( |
|
|
Uri.parse( |
|
|
'https://restaurant.careeracademy.mg/api/tables/${widget.tableId}', |
|
|
'https://restaurant.careeracademy.mg/api/tables/${widget.tableId}', |
|
|
), |
|
|
), |
|
|
headers: {'Content-Type': 'application/json'}, |
|
|
headers: {'Content-Type': 'application/json'}, |
|
|
body: jsonEncode({'statut': 'libre'}), |
|
|
body: jsonEncode({'status': 'available'}), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// Convertir le Map en objet CommandeDetail |
|
|
|
|
|
final commandeDetail = CommandeDetail.fromJson(commandeData['data']); |
|
|
|
|
|
|
|
|
|
|
|
// Navigation avec l'objet converti |
|
|
|
|
|
Navigator.of(context).pushReplacement( |
|
|
|
|
|
MaterialPageRoute( |
|
|
|
|
|
builder: |
|
|
|
|
|
(context) => FactureScreen( |
|
|
|
|
|
commande: |
|
|
|
|
|
commandeDetail, // Maintenant c'est un objet CommandeDetail |
|
|
|
|
|
paymentMethod: selectedPaymentMethod!.id, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
// 3. Succès |
|
|
// _showPaymentSuccessDialog(commandeData, total, selectedPaymentMethod); |
|
|
_showPaymentSuccessDialog(commandeData, total); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
throw Exception('Erreur lors du paiement'); |
|
|
throw Exception('Erreur lors du paiement'); |
|
|
} |
|
|
} |
|
|
@ -422,9 +492,104 @@ class _CartPageState extends State<CartPage> { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Dialog de sélection du moyen de paiement |
|
|
|
|
|
Future<PaymentMethod?> _showPaymentMethodDialog() async { |
|
|
|
|
|
return await showDialog<PaymentMethod>( |
|
|
|
|
|
context: context, |
|
|
|
|
|
builder: |
|
|
|
|
|
(context) => AlertDialog( |
|
|
|
|
|
title: const Row( |
|
|
|
|
|
children: [ |
|
|
|
|
|
Icon(Icons.payment, color: Colors.blue), |
|
|
|
|
|
SizedBox(width: 8), |
|
|
|
|
|
Text('Choisir le moyen de paiement'), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
content: SizedBox( |
|
|
|
|
|
width: double.maxFinite, |
|
|
|
|
|
child: ListView.separated( |
|
|
|
|
|
shrinkWrap: true, |
|
|
|
|
|
itemCount: paymentMethods.length, |
|
|
|
|
|
separatorBuilder: (context, index) => const SizedBox(height: 8), |
|
|
|
|
|
itemBuilder: (context, index) { |
|
|
|
|
|
final method = paymentMethods[index]; |
|
|
|
|
|
return InkWell( |
|
|
|
|
|
onTap: () => Navigator.of(context).pop(method), |
|
|
|
|
|
borderRadius: BorderRadius.circular(8), |
|
|
|
|
|
child: Container( |
|
|
|
|
|
padding: const EdgeInsets.all(16), |
|
|
|
|
|
decoration: BoxDecoration( |
|
|
|
|
|
border: Border.all( |
|
|
|
|
|
color: method.color.withOpacity(0.3), |
|
|
|
|
|
), |
|
|
|
|
|
borderRadius: BorderRadius.circular(8), |
|
|
|
|
|
color: method.color.withOpacity(0.05), |
|
|
|
|
|
), |
|
|
|
|
|
child: Row( |
|
|
|
|
|
children: [ |
|
|
|
|
|
Container( |
|
|
|
|
|
padding: const EdgeInsets.all(8), |
|
|
|
|
|
decoration: BoxDecoration( |
|
|
|
|
|
color: method.color.withOpacity(0.1), |
|
|
|
|
|
borderRadius: BorderRadius.circular(8), |
|
|
|
|
|
), |
|
|
|
|
|
child: Icon( |
|
|
|
|
|
method.icon, |
|
|
|
|
|
color: method.color, |
|
|
|
|
|
size: 24, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(width: 16), |
|
|
|
|
|
Expanded( |
|
|
|
|
|
child: Column( |
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
|
|
children: [ |
|
|
|
|
|
Text( |
|
|
|
|
|
method.name, |
|
|
|
|
|
style: TextStyle( |
|
|
|
|
|
fontSize: 16, |
|
|
|
|
|
fontWeight: FontWeight.w600, |
|
|
|
|
|
color: method.color, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(height: 2), |
|
|
|
|
|
Text( |
|
|
|
|
|
method.description, |
|
|
|
|
|
style: TextStyle( |
|
|
|
|
|
fontSize: 12, |
|
|
|
|
|
color: method.color.withOpacity(0.7), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
Icon( |
|
|
|
|
|
Icons.arrow_forward_ios, |
|
|
|
|
|
color: method.color, |
|
|
|
|
|
size: 16, |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
}, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
actions: [ |
|
|
|
|
|
TextButton( |
|
|
|
|
|
onPressed: () => Navigator.of(context).pop(), |
|
|
|
|
|
child: const Text('Annuler'), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Dialog de succès mis à jour |
|
|
void _showPaymentSuccessDialog( |
|
|
void _showPaymentSuccessDialog( |
|
|
Map<String, dynamic> commandeData, |
|
|
Map<String, dynamic> commandeData, |
|
|
double total, |
|
|
double total, |
|
|
|
|
|
PaymentMethod paymentMethod, |
|
|
) { |
|
|
) { |
|
|
showDialog( |
|
|
showDialog( |
|
|
context: context, |
|
|
context: context, |
|
|
@ -446,41 +611,55 @@ class _CartPageState extends State<CartPage> { |
|
|
Text('Table ${widget.tablename} libérée'), |
|
|
Text('Table ${widget.tablename} libérée'), |
|
|
const SizedBox(height: 8), |
|
|
const SizedBox(height: 8), |
|
|
Text( |
|
|
Text( |
|
|
'Montant: ${total.toStringAsFixed(2)} €', |
|
|
'Montant: ${total.toStringAsFixed(0)} MGA', |
|
|
style: const TextStyle(fontWeight: FontWeight.bold), |
|
|
style: const TextStyle(fontWeight: FontWeight.bold), |
|
|
), |
|
|
), |
|
|
|
|
|
const SizedBox(height: 12), |
|
|
|
|
|
Container( |
|
|
|
|
|
padding: const EdgeInsets.symmetric( |
|
|
|
|
|
horizontal: 12, |
|
|
|
|
|
vertical: 8, |
|
|
|
|
|
), |
|
|
|
|
|
decoration: BoxDecoration( |
|
|
|
|
|
color: paymentMethod.color.withOpacity(0.1), |
|
|
|
|
|
borderRadius: BorderRadius.circular(8), |
|
|
|
|
|
border: Border.all( |
|
|
|
|
|
color: paymentMethod.color.withOpacity(0.3), |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
child: Row( |
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
|
children: [ |
|
|
|
|
|
Icon( |
|
|
|
|
|
paymentMethod.icon, |
|
|
|
|
|
color: paymentMethod.color, |
|
|
|
|
|
size: 16, |
|
|
|
|
|
), |
|
|
|
|
|
const SizedBox(width: 6), |
|
|
|
|
|
Text( |
|
|
|
|
|
paymentMethod.name, |
|
|
|
|
|
style: TextStyle( |
|
|
|
|
|
color: paymentMethod.color, |
|
|
|
|
|
fontWeight: FontWeight.w600, |
|
|
|
|
|
fontSize: 14, |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
const SizedBox(height: 16), |
|
|
const SizedBox(height: 16), |
|
|
const Text('Voulez-vous imprimer un reçu ?'), |
|
|
const Text('Voulez-vous imprimer un reçu ?'), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
actions: [ |
|
|
actions: [ |
|
|
TextButton( |
|
|
TextButton( |
|
|
onPressed: () { |
|
|
onPressed: () => _navigateToTables(), |
|
|
Navigator.of(context).pop(); // Fermer le dialog |
|
|
|
|
|
// Naviguer vers la page des tables |
|
|
|
|
|
Navigator.of(context).pushAndRemoveUntil( |
|
|
|
|
|
MaterialPageRoute( |
|
|
|
|
|
builder: |
|
|
|
|
|
(context) => const MainLayout(child: TablesScreen()), |
|
|
|
|
|
), |
|
|
|
|
|
(route) => false, |
|
|
|
|
|
); |
|
|
|
|
|
}, |
|
|
|
|
|
child: const Text('Non merci'), |
|
|
child: const Text('Non merci'), |
|
|
), |
|
|
), |
|
|
ElevatedButton( |
|
|
ElevatedButton( |
|
|
onPressed: () async { |
|
|
onPressed: |
|
|
Navigator.of(context).pop(); // Fermer le dialog |
|
|
() => |
|
|
await _imprimerTicketPaiement(commandeData, total); |
|
|
_imprimerEtNaviguer(commandeData, total, paymentMethod), |
|
|
// Naviguer vers la page des tables après l'impression |
|
|
|
|
|
Navigator.of(context).pushAndRemoveUntil( |
|
|
|
|
|
MaterialPageRoute( |
|
|
|
|
|
builder: |
|
|
|
|
|
(context) => const MainLayout(child: TablesScreen()), |
|
|
|
|
|
), |
|
|
|
|
|
(route) => false, |
|
|
|
|
|
); |
|
|
|
|
|
}, |
|
|
|
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.green), |
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.green), |
|
|
child: const Text('Imprimer'), |
|
|
child: const Text('Imprimer'), |
|
|
), |
|
|
), |
|
|
@ -489,6 +668,73 @@ class _CartPageState extends State<CartPage> { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void _navigateToTables() { |
|
|
|
|
|
Navigator.of(context).pop(); // Fermer le dialog |
|
|
|
|
|
Navigator.of(context).pushAndRemoveUntil( |
|
|
|
|
|
MaterialPageRoute( |
|
|
|
|
|
builder: (context) => const MainLayout(child: TablesScreen()), |
|
|
|
|
|
), |
|
|
|
|
|
(route) => false, |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> _imprimerEtNaviguer( |
|
|
|
|
|
Map<String, dynamic> commandeData, |
|
|
|
|
|
double total, |
|
|
|
|
|
PaymentMethod paymentMethod, |
|
|
|
|
|
) async { |
|
|
|
|
|
Navigator.of(context).pop(); // Fermer le dialog |
|
|
|
|
|
|
|
|
|
|
|
// Afficher un indicateur de chargement pendant l'impression |
|
|
|
|
|
showDialog( |
|
|
|
|
|
context: context, |
|
|
|
|
|
barrierDismissible: false, |
|
|
|
|
|
builder: |
|
|
|
|
|
(context) => const AlertDialog( |
|
|
|
|
|
content: Row( |
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
|
children: [ |
|
|
|
|
|
CircularProgressIndicator(), |
|
|
|
|
|
SizedBox(width: 16), |
|
|
|
|
|
Text('Impression en cours...'), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
await _imprimerTicketPaiement(commandeData, total); |
|
|
|
|
|
|
|
|
|
|
|
// Fermer le dialog d'impression |
|
|
|
|
|
Navigator.of(context).pop(); |
|
|
|
|
|
|
|
|
|
|
|
// Afficher un message de succès |
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
const SnackBar( |
|
|
|
|
|
content: Text('Ticket imprimé avec succès'), |
|
|
|
|
|
backgroundColor: Colors.green, |
|
|
|
|
|
duration: Duration(seconds: 2), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
// Fermer le dialog d'impression |
|
|
|
|
|
Navigator.of(context).pop(); |
|
|
|
|
|
|
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar( |
|
|
|
|
|
SnackBar( |
|
|
|
|
|
content: Text('Erreur d\'impression: ${e.toString()}'), |
|
|
|
|
|
backgroundColor: Colors.orange, |
|
|
|
|
|
duration: const Duration(seconds: 3), |
|
|
|
|
|
), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Attendre un peu puis naviguer |
|
|
|
|
|
Future.delayed(const Duration(milliseconds: 500), () { |
|
|
|
|
|
_navigateToTables(); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Future<void> _imprimerTicketPaiement( |
|
|
Future<void> _imprimerTicketPaiement( |
|
|
Map<String, dynamic> commandeData, |
|
|
Map<String, dynamic> commandeData, |
|
|
double total, |
|
|
double total, |
|
|
|