command
This commit is contained in:
parent
059da2af1b
commit
e83f7af730
@ -17,6 +17,7 @@ class CommandeDetail {
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final List<CommandeItem> items;
|
||||
final String? tablename;
|
||||
|
||||
CommandeDetail({
|
||||
required this.id,
|
||||
@ -36,6 +37,7 @@ class CommandeDetail {
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.items,
|
||||
this.tablename,
|
||||
});
|
||||
|
||||
factory CommandeDetail.fromJson(Map<String, dynamic> json) {
|
||||
@ -76,6 +78,7 @@ class CommandeDetail {
|
||||
?.map((item) => CommandeItem.fromJson(item))
|
||||
.toList() ??
|
||||
[],
|
||||
tablename: data['tablename'] ?? 'Table inconnue',
|
||||
);
|
||||
}
|
||||
|
||||
@ -98,6 +101,7 @@ class CommandeDetail {
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'items': items.map((item) => item.toJson()).toList(),
|
||||
'tablename': tablename,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
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 'package:itrimobe/pages/tables.dart';
|
||||
import 'package:itrimobe/services/pdf_service.dart';
|
||||
|
||||
import '../layouts/main_layout.dart';
|
||||
|
||||
@ -315,7 +320,12 @@ class _CartPageState extends State<CartPage> {
|
||||
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>(
|
||||
context: context,
|
||||
builder:
|
||||
@ -337,6 +347,49 @@ class _CartPageState extends State<CartPage> {
|
||||
),
|
||||
),
|
||||
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 ?'),
|
||||
],
|
||||
),
|
||||
@ -348,9 +401,12 @@ class _CartPageState extends State<CartPage> {
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
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 {
|
||||
// 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(
|
||||
Uri.parse('https://restaurant.careeracademy.mg/api/commandes'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'table_id': widget.tableId,
|
||||
'statut': 'payee', // Directement payée
|
||||
'statut': 'payee',
|
||||
'items':
|
||||
_cartItems
|
||||
.map(
|
||||
@ -378,33 +434,47 @@ class _CartPageState extends State<CartPage> {
|
||||
'quantite': item.quantity,
|
||||
'prix_unitaire': item.prix,
|
||||
'commentaire': item.commentaire,
|
||||
// Pas de réservation
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
'methode_paiement': 'especes',
|
||||
'methode_paiement':
|
||||
selectedPaymentMethod.id, // mvola, carte, ou especes
|
||||
'montant_paye': total,
|
||||
'date_paiement': DateTime.now().toIso8601String(),
|
||||
'client_id': 1, // Valeur par défaut
|
||||
'client_id': 1,
|
||||
'reservation_id': 1,
|
||||
'serveur': 'Serveur par défaut', // Valeur par défaut
|
||||
'serveur': 'Serveur par défaut',
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
final commandeData = jsonDecode(response.body);
|
||||
|
||||
// 2. Libérer la table
|
||||
// Libérer la table
|
||||
await http.patch(
|
||||
Uri.parse(
|
||||
'https://restaurant.careeracademy.mg/api/tables/${widget.tableId}',
|
||||
),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({'statut': 'libre'}),
|
||||
body: jsonEncode({'status': 'available'}),
|
||||
);
|
||||
|
||||
// 3. Succès
|
||||
_showPaymentSuccessDialog(commandeData, total);
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// _showPaymentSuccessDialog(commandeData, total, selectedPaymentMethod);
|
||||
} else {
|
||||
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(
|
||||
Map<String, dynamic> commandeData,
|
||||
double total,
|
||||
PaymentMethod paymentMethod,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -446,41 +611,55 @@ class _CartPageState extends State<CartPage> {
|
||||
Text('Table ${widget.tablename} libérée'),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Montant: ${total.toStringAsFixed(2)} €',
|
||||
'Montant: ${total.toStringAsFixed(0)} MGA',
|
||||
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 Text('Voulez-vous imprimer un reçu ?'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
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,
|
||||
);
|
||||
},
|
||||
onPressed: () => _navigateToTables(),
|
||||
child: const Text('Non merci'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop(); // Fermer le dialog
|
||||
await _imprimerTicketPaiement(commandeData, total);
|
||||
// Naviguer vers la page des tables après l'impression
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(context) => const MainLayout(child: TablesScreen()),
|
||||
),
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
onPressed:
|
||||
() =>
|
||||
_imprimerEtNaviguer(commandeData, total, paymentMethod),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
|
||||
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(
|
||||
Map<String, dynamic> commandeData,
|
||||
double total,
|
||||
|
||||
@ -11,10 +11,10 @@ class FactureScreen extends StatefulWidget {
|
||||
final String paymentMethod;
|
||||
|
||||
const FactureScreen({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.commande,
|
||||
required this.paymentMethod,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
_FactureScreenState createState() => _FactureScreenState();
|
||||
@ -114,23 +114,23 @@ class _FactureScreenState extends State<FactureScreen> {
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Column(
|
||||
return const Column(
|
||||
children: [
|
||||
const Text(
|
||||
'RESTAURANT',
|
||||
Text(
|
||||
'RESTAURANT ITRIMOBE',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'Adresse: 123 Rue de la Paix',
|
||||
SizedBox(height: 12),
|
||||
Text(
|
||||
'Adresse: Moramanga, Madagascar',
|
||||
style: TextStyle(fontSize: 12, color: Colors.black87),
|
||||
),
|
||||
const Text(
|
||||
'Contact: +33 1 23 45 67 89',
|
||||
Text(
|
||||
'Contact: +261 34 12 34 56',
|
||||
style: TextStyle(fontSize: 12, color: Colors.black87),
|
||||
),
|
||||
],
|
||||
@ -157,7 +157,7 @@ class _FactureScreenState extends State<FactureScreen> {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Table: ${widget.commande.tableId}',
|
||||
'Via: ${widget.commande.tablename}',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
@ -55,11 +55,11 @@ class PlatformPrintService {
|
||||
const double lineHeight = 1.2;
|
||||
|
||||
final restaurantInfo = {
|
||||
'nom': 'RESTAURANT',
|
||||
'adresse': '123 Rue de la Paix',
|
||||
'ville': '75000 PARIS',
|
||||
'contact': '01.23.45.67.89',
|
||||
'email': 'contact@restaurant.fr',
|
||||
'nom': 'RESTAURANT ITRIMOBE',
|
||||
'adresse': 'Moramanga, Antananarivo',
|
||||
'ville': 'Madagascar',
|
||||
'contact': '261348415301',
|
||||
'email': 'contact@careeragency.mg',
|
||||
};
|
||||
|
||||
final factureNumber =
|
||||
@ -389,7 +389,7 @@ class PlatformPrintService {
|
||||
return await printTicket(commande: commande, paymentMethod: paymentMethod);
|
||||
}
|
||||
|
||||
// Utilitaires de formatage
|
||||
// Utilitaires de formatageπ
|
||||
static String _formatDate(DateTime dateTime) {
|
||||
return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}';
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user