Compare commits

..

No commits in common. "d9998e1a79d3b6bd089a9c5a97802a3be3358533" and "3b7d3f8cca382b43211ed35d083af2c6a9b7ae39" have entirely different histories.

5 changed files with 56 additions and 306 deletions

View File

@ -16,8 +16,8 @@ class CommandeDetail {
final DateTime? dateService; final DateTime? dateService;
final DateTime createdAt; final DateTime createdAt;
final DateTime updatedAt; final DateTime updatedAt;
final String tablename;
final List<CommandeItem> items; final List<CommandeItem> items;
final String? tablename;
CommandeDetail({ CommandeDetail({
required this.id, required this.id,
@ -101,7 +101,6 @@ class CommandeDetail {
'created_at': createdAt.toIso8601String(), 'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(),
'items': items.map((item) => item.toJson()).toList(), 'items': items.map((item) => item.toJson()).toList(),
'tablename': tablename,
}; };
} }

View File

@ -1,14 +1,9 @@
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';
@ -320,12 +315,7 @@ class _CartPageState extends State<CartPage> {
return sum + (item.prix * item.quantity); return sum + (item.prix * item.quantity);
}); });
// Sélectionner le moyen de paiement // Confirmer le 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:
@ -347,49 +337,6 @@ 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 ?'),
], ],
), ),
@ -401,12 +348,9 @@ 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: selectedPaymentMethod.color, backgroundColor: Colors.orange[600],
),
child: const Text(
'Confirmer le paiement',
style: TextStyle(color: Colors.white),
), ),
child: const Text('Confirmer le paiement'),
), ),
], ],
), ),
@ -419,13 +363,13 @@ class _CartPageState extends State<CartPage> {
}); });
try { try {
// Créer la commande avec le moyen de paiement sélectionné // 1. Créer la commande avec les items du panier
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', 'statut': 'payee', // Directement payée
'items': 'items':
_cartItems _cartItems
.map( .map(
@ -434,47 +378,33 @@ 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': 'methode_paiement': 'especes',
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, 'client_id': 1, // Valeur par défaut
'reservation_id': 1, 'reservation_id': 1,
'serveur': 'Serveur par défaut', 'serveur': 'Serveur par défaut', // Valeur par défaut
}), }),
); );
if (response.statusCode == 201) { if (response.statusCode == 201) {
final commandeData = jsonDecode(response.body); final commandeData = jsonDecode(response.body);
// Libérer la table // 2. 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({'status': 'available'}), body: jsonEncode({'statut': 'libre'}),
); );
// Convertir le Map en objet CommandeDetail // 3. Succès
final commandeDetail = CommandeDetail.fromJson(commandeData['data']); _showPaymentSuccessDialog(commandeData, total);
// 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 { } else {
throw Exception('Erreur lors du paiement'); throw Exception('Erreur lors du paiement');
} }
@ -492,104 +422,9 @@ 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,
@ -611,55 +446,41 @@ 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(0)} MGA', 'Montant: ${total.toStringAsFixed(2)}',
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: () => _navigateToTables(), 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,
);
},
child: const Text('Non merci'), child: const Text('Non merci'),
), ),
ElevatedButton( ElevatedButton(
onPressed: onPressed: () async {
() => Navigator.of(context).pop(); // Fermer le dialog
_imprimerEtNaviguer(commandeData, total, paymentMethod), 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,
);
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.green), style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
child: const Text('Imprimer'), child: const Text('Imprimer'),
), ),
@ -668,73 +489,6 @@ 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,

View File

@ -11,10 +11,10 @@ class FactureScreen extends StatefulWidget {
final String paymentMethod; final String paymentMethod;
const FactureScreen({ const FactureScreen({
super.key, Key? key,
required this.commande, required this.commande,
required this.paymentMethod, required this.paymentMethod,
}); }) : super(key: key);
@override @override
_FactureScreenState createState() => _FactureScreenState(); _FactureScreenState createState() => _FactureScreenState();
@ -114,23 +114,23 @@ class _FactureScreenState extends State<FactureScreen> {
} }
Widget _buildHeader() { Widget _buildHeader() {
return const Column( return Column(
children: [ children: [
Text( const Text(
'RESTAURANT ITRIMOBE', 'RESTAURANT',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
letterSpacing: 1.2, letterSpacing: 1.2,
), ),
), ),
SizedBox(height: 12), const SizedBox(height: 12),
Text( const Text(
'Adresse: Moramanga, Madagascar', 'Adresse: 123 Rue de la Paix',
style: TextStyle(fontSize: 12, color: Colors.black87), style: TextStyle(fontSize: 12, color: Colors.black87),
), ),
Text( const Text(
'Contact: +261 34 12 34 56', 'Contact: +33 1 23 45 67 89',
style: TextStyle(fontSize: 12, color: Colors.black87), style: TextStyle(fontSize: 12, color: Colors.black87),
), ),
], ],
@ -157,7 +157,7 @@ class _FactureScreenState extends State<FactureScreen> {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Via: ${widget.commande.tablename}', 'Table: ${widget.commande.tableId}',
style: const TextStyle(fontSize: 12, color: Colors.black87), style: const TextStyle(fontSize: 12, color: Colors.black87),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),

View File

@ -55,11 +55,11 @@ class PlatformPrintService {
const double lineHeight = 1.2; const double lineHeight = 1.2;
final restaurantInfo = { final restaurantInfo = {
'nom': 'RESTAURANT ITRIMOBE', 'nom': 'RESTAURANT',
'adresse': 'Moramanga, Antananarivo', 'adresse': '123 Rue de la Paix',
'ville': 'Madagascar', 'ville': '75000 PARIS',
'contact': '261348415301', 'contact': '01.23.45.67.89',
'email': 'contact@careeragency.mg', 'email': 'contact@restaurant.fr',
}; };
final factureNumber = final factureNumber =
@ -389,7 +389,7 @@ class PlatformPrintService {
return await printTicket(commande: commande, paymentMethod: paymentMethod); return await printTicket(commande: commande, paymentMethod: paymentMethod);
} }
// Utilitaires de formatageπ // Utilitaires de formatage
static String _formatDate(DateTime dateTime) { static String _formatDate(DateTime dateTime) {
return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}'; return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}';
} }

View File

@ -21,10 +21,7 @@ class RestaurantApiService {
static Future<List<TableOrder>> getCommandes() async { static Future<List<TableOrder>> getCommandes() async {
try { try {
final response = await http final response = await http
.get( .get(Uri.parse('$baseUrl/api/commandes?statut=servie'), headers: _headers)
Uri.parse('$baseUrl/api/commandes?statut=servie'),
headers: _headers,
)
.timeout( .timeout(
const Duration(seconds: 30), const Duration(seconds: 30),
onTimeout: () => throw TimeoutException('Délai d\'attente dépassé'), onTimeout: () => throw TimeoutException('Délai d\'attente dépassé'),