From e02c4c8bef1b315335b6967c68f1c6bb4c44ac40 Mon Sep 17 00:00:00 2001 From: Stephane Date: Mon, 23 Jun 2025 23:04:06 +0300 Subject: [PATCH] change the facture to show only on superadmin --- lib/Views/commandManagement.dart | 4063 ++++++++++++++++-------------- 1 file changed, 2206 insertions(+), 1857 deletions(-) diff --git a/lib/Views/commandManagement.dart b/lib/Views/commandManagement.dart index 1663326..159a5a2 100644 --- a/lib/Views/commandManagement.dart +++ b/lib/Views/commandManagement.dart @@ -39,8 +39,13 @@ class _GestionCommandesPageState extends State { DateTime? _selectedDate; final TextEditingController _searchController = TextEditingController(); bool _showCancelledOrders = false; + // final userController = Get.find(); final userController = Get.find(); + bool verifAdmin() { + return userController.role == 'Super Admin'; + } + @override void initState() { super.initState(); @@ -73,17 +78,22 @@ class _GestionCommandesPageState extends State { final matchesDate = _selectedDate == null || DateFormat('yyyy-MM-dd').format(commande.dateCommande) == DateFormat('yyyy-MM-dd').format(_selectedDate!); - - final shouldShowCancelled = _showCancelledOrders || commande.statut != StatutCommande.annulee; - - return matchesSearch && matchesStatut && matchesDate && shouldShowCancelled; + + final shouldShowCancelled = + _showCancelledOrders || commande.statut != StatutCommande.annulee; + + return matchesSearch && + matchesStatut && + matchesDate && + shouldShowCancelled; }).toList(); }); } - Future _updateStatut(int commandeId, StatutCommande newStatut, {int? validateurId}) async { + Future _updateStatut(int commandeId, StatutCommande newStatut, + {int? validateurId}) async { final commandeExistante = await _database.getCommandeById(commandeId); - + if (commandeExistante == null) { Get.snackbar( 'Erreur', @@ -113,12 +123,12 @@ class _GestionCommandesPageState extends State { } else { await _database.updateStatutCommande(commandeId, newStatut); } - + await _loadCommandes(); - + String message = 'Statut de la commande mis à jour'; Color backgroundColor = Colors.green; - + switch (newStatut) { case StatutCommande.annulee: message = 'Commande annulée avec succès'; @@ -131,7 +141,7 @@ class _GestionCommandesPageState extends State { default: break; } - + Get.snackbar( 'Succès', message, @@ -152,18 +162,19 @@ class _GestionCommandesPageState extends State { if (selectedPayment.type == PaymentType.cash) { await _showCashPaymentDialog(commande, selectedPayment.amountGiven); } - + await _updateStatut( - commande.id!, + commande.id!, StatutCommande.confirmee, validateurId: userController.userId, ); - + await _generateReceipt(commande, selectedPayment); } } - Future _showCashPaymentDialog(Commande commande, double amountGiven) async { + Future _showCashPaymentDialog( + Commande commande, double amountGiven) async { final amountController = TextEditingController( text: amountGiven.toStringAsFixed(2), ); @@ -234,1714 +245,2032 @@ class _GestionCommandesPageState extends State { Future buildIconPhoneText() async { final font = pw.Font.ttf(await rootBundle.load('assets/fa-solid-900.ttf')); - return pw.Text(String.fromCharCode(0xf095), style: pw.TextStyle(font: font)); + return pw.Text(String.fromCharCode(0xf095), + style: pw.TextStyle(font: font)); } -Future buildIconGift() async { - final font = pw.Font.ttf(await rootBundle.load('assets/NotoEmoji-Regular.ttf')); - return pw.Text('🎁', style: pw.TextStyle(font: font, fontSize: 16)); -} + + Future buildIconGift() async { + final font = + pw.Font.ttf(await rootBundle.load('assets/NotoEmoji-Regular.ttf')); + return pw.Text('🎁', style: pw.TextStyle(font: font, fontSize: 16)); + } + Future buildIconCheckedText() async { final font = pw.Font.ttf(await rootBundle.load('assets/fa-solid-900.ttf')); - return pw.Text(String.fromCharCode(0xf14a), style: pw.TextStyle(font: font)); + return pw.Text(String.fromCharCode(0xf14a), + style: pw.TextStyle(font: font)); } Future buildIconGlobeText() async { final font = pw.Font.ttf(await rootBundle.load('assets/fa-solid-900.ttf')); - return pw.Text(String.fromCharCode(0xf0ac), style: pw.TextStyle(font: font)); + return pw.Text(String.fromCharCode(0xf0ac), + style: pw.TextStyle(font: font)); } // Bon de livraison============================================== -Future _generateBonLivraison(Commande commande) async { - final details = await _database.getDetailsCommande(commande.id!); - final client = await _database.getClientById(commande.clientId); - final pointDeVente = await _database.getPointDeVenteById(1); - - // Récupérer les informations des vendeurs - final commandeur = commande.commandeurId != null - ? await _database.getUserById(commande.commandeurId!) - : null; - final validateur = commande.validateurId != null - ? await _database.getUserById(commande.validateurId!) - : null; - - final iconPhone = await buildIconPhoneText(); - final iconChecked = await buildIconCheckedText(); - final iconGlobe = await buildIconGlobeText(); - - double sousTotal = 0; - double totalRemises = 0; - double totalCadeaux = 0; - int nombreCadeaux = 0; - - for (final detail in details) { - sousTotal += detail.sousTotal; - if (detail.estCadeau) { - totalCadeaux += detail.sousTotal; - nombreCadeaux += detail.quantite; - } else { - totalRemises += detail.montantRemise; + Future _generateBonLivraison(Commande commande) async { + final details = await _database.getDetailsCommande(commande.id!); + final client = await _database.getClientById(commande.clientId); + final pointDeVente = await _database.getPointDeVenteById(1); + + // Récupérer les informations des vendeurs + final commandeur = commande.commandeurId != null + ? await _database.getUserById(commande.commandeurId!) + : null; + final validateur = commande.validateurId != null + ? await _database.getUserById(commande.validateurId!) + : null; + + final iconPhone = await buildIconPhoneText(); + final iconChecked = await buildIconCheckedText(); + final iconGlobe = await buildIconGlobeText(); + + double sousTotal = 0; + double totalRemises = 0; + double totalCadeaux = 0; + int nombreCadeaux = 0; + + for (final detail in details) { + sousTotal += detail.sousTotal; + if (detail.estCadeau) { + totalCadeaux += detail.sousTotal; + nombreCadeaux += detail.quantite; + } else { + totalRemises += detail.montantRemise; + } } - } - final List> detailsAvecProduits = []; - for (final detail in details) { - final produit = await _database.getProductById(detail.produitId); - detailsAvecProduits.add({ - 'detail': detail, - 'produit': produit, - }); - } - - final pdf = pw.Document(); - final imageBytes = await loadImage(); - final image = pw.MemoryImage(imageBytes); - final italicFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Italic.ttf')); - - // Tailles de texte adaptées pour côte à côte - final tinyTextStyle = pw.TextStyle(fontSize: 5); - final smallTextStyle = pw.TextStyle(fontSize: 6); - final normalTextStyle = pw.TextStyle(fontSize: 7); - final boldTextStyle = pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold); - final boldClientStyle = pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold); - final frameTextStyle = pw.TextStyle(fontSize: 6); - final italicTextStyle = pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold, font: italicFont); - final italicLogoStyle = pw.TextStyle(fontSize: 4, fontWeight: pw.FontWeight.bold, font: italicFont); - final titleStyle = pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold); - - // Fonction pour créer un exemplaire - pw.Widget buildExemplaire(String typeExemplaire, {bool isSecond = false}) { - return pw.Container( - width: double.infinity, - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.black, width: 1), - ), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - // En-tête avec indication de l'exemplaire - pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.all(3), - decoration: pw.BoxDecoration( - color: typeExemplaire == "CLIENT" ? PdfColors.blue100 : PdfColors.green100, - ), - child: pw.Center( - child: pw.Text( - 'BON DE LIVRAISON - EXEMPLAIRE $typeExemplaire', - style: pw.TextStyle( - fontSize: 8, - fontWeight: pw.FontWeight.bold, - color: typeExemplaire == "CLIENT" ? PdfColors.blue800 : PdfColors.green800, + final List> detailsAvecProduits = []; + for (final detail in details) { + final produit = await _database.getProductById(detail.produitId); + detailsAvecProduits.add({ + 'detail': detail, + 'produit': produit, + }); + } + + final pdf = pw.Document(); + final imageBytes = await loadImage(); + final image = pw.MemoryImage(imageBytes); + final italicFont = + pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Italic.ttf')); + + // Tailles de texte adaptées pour côte à côte + final tinyTextStyle = pw.TextStyle(fontSize: 5); + final smallTextStyle = pw.TextStyle(fontSize: 6); + final normalTextStyle = pw.TextStyle(fontSize: 7); + final boldTextStyle = + pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold); + final boldClientStyle = + pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold); + final frameTextStyle = pw.TextStyle(fontSize: 6); + final italicTextStyle = pw.TextStyle( + fontSize: 5, fontWeight: pw.FontWeight.bold, font: italicFont); + final italicLogoStyle = pw.TextStyle( + fontSize: 4, fontWeight: pw.FontWeight.bold, font: italicFont); + final titleStyle = + pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold); + + // Fonction pour créer un exemplaire + pw.Widget buildExemplaire(String typeExemplaire, {bool isSecond = false}) { + return pw.Container( + width: double.infinity, + decoration: pw.BoxDecoration( + border: pw.Border.all(color: PdfColors.black, width: 1), + ), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + // En-tête avec indication de l'exemplaire + pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.all(3), + decoration: pw.BoxDecoration( + color: typeExemplaire == "CLIENT" + ? PdfColors.blue100 + : PdfColors.green100, + ), + child: pw.Center( + child: pw.Text( + 'BON DE LIVRAISON - EXEMPLAIRE $typeExemplaire', + style: pw.TextStyle( + fontSize: 8, + fontWeight: pw.FontWeight.bold, + color: typeExemplaire == "CLIENT" + ? PdfColors.blue800 + : PdfColors.green800, + ), ), ), ), - ), - - pw.Padding( - padding: const pw.EdgeInsets.all(6), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - // En-tête principal - pw.Row( - crossAxisAlignment: pw.CrossAxisAlignment.start, - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - // Logo et infos entreprise - très compact - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Container( - width: 45, - height: 45, - child: pw.Image(image), - ), - pw.Text('NOTRE COMPETENCE, A VOTRE SERVICE', style: italicLogoStyle), - pw.SizedBox(height: 3), - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text('📍 REMAX Andravoangy', style: tinyTextStyle), - pw.Text('📍 SUPREME CENTER Behoririka', style: tinyTextStyle), - pw.Text('📞 033 37 808 18', style: tinyTextStyle), - pw.Text('🌐 www.guycom.mg', style: tinyTextStyle), - pw.SizedBox(height: 1), - // Ajout du NIF - pw.Text('NIF: 1026/GC78-20-02-22', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold)), - ], - ), - ], - ), - - // Informations centrales - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.center, - children: [ - pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', style: boldClientStyle), - pw.SizedBox(height: 3), - pw.Container(width: 80, height: 1, color: PdfColors.black), - pw.SizedBox(height: 3), - pw.Container( - padding: const pw.EdgeInsets.all(3), - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.black), + + pw.Padding( + padding: const pw.EdgeInsets.all(6), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + // En-tête principal + pw.Row( + crossAxisAlignment: pw.CrossAxisAlignment.start, + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + // Logo et infos entreprise - très compact + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Container( + width: 45, + height: 45, + child: pw.Image(image), ), - child: pw.Column( + pw.Text('NOTRE COMPETENCE, A VOTRE SERVICE', + style: italicLogoStyle), + pw.SizedBox(height: 3), + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ - pw.Text('Boutique:', style: frameTextStyle), - pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', style: boldTextStyle), + pw.Text('📍 REMAX Andravoangy', + style: tinyTextStyle), + pw.Text('📍 SUPREME CENTER Behoririka', + style: tinyTextStyle), + pw.Text('📞 033 37 808 18', style: tinyTextStyle), + pw.Text('🌐 www.guycom.mg', style: tinyTextStyle), pw.SizedBox(height: 1), - pw.Text('Bon N°:', style: frameTextStyle), - pw.Text('${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', style: boldTextStyle), + // Ajout du NIF + pw.Text('NIF: 1026/GC78-20-02-22', + style: pw.TextStyle( + fontSize: 5, + fontWeight: pw.FontWeight.bold)), ], ), - ), - ], - ), - - // Informations client - compact - pw.Container( - width: 100, - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.black, width: 1), - ), - padding: const pw.EdgeInsets.all(4), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.center, - children: [ - pw.Text('CLIENT', style: frameTextStyle), - pw.SizedBox(height: 1), - pw.Text('ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', style: smallTextStyle), - pw.Container(width: 80, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 1)), - pw.Text(client?.nom ?? 'Non spécifié', style: boldTextStyle), - pw.SizedBox(height: 1), - pw.Text(client?.telephone ?? 'Non spécifié', style: tinyTextStyle), ], ), - ), - ], - ), - - pw.SizedBox(height: 4), - - // Tableau des produits - très compact - pw.Table( - border: pw.TableBorder.all(width: 0.5), - columnWidths: { - 0: const pw.FlexColumnWidth(3.5), - 1: const pw.FlexColumnWidth(0.8), - 2: const pw.FlexColumnWidth(1.2), - 3: const pw.FlexColumnWidth(1.5), - 4: const pw.FlexColumnWidth(1.2), - }, - children: [ - pw.TableRow( - decoration: const pw.BoxDecoration(color: PdfColors.grey200), - children: [ - pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Désignations', style: boldTextStyle)), - pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Qté', style: boldTextStyle, textAlign: pw.TextAlign.center)), - pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('P.U.', style: boldTextStyle, textAlign: pw.TextAlign.right)), - pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Remise/Cadeau', style: boldTextStyle, textAlign: pw.TextAlign.center)), - pw.Padding(padding: const pw.EdgeInsets.all(1), child: pw.Text('Montant', style: boldTextStyle, textAlign: pw.TextAlign.right)), - ], - ), - - ...detailsAvecProduits.map((item) { - final detail = item['detail'] as DetailCommande; - final produit = item['produit']; - - return pw.TableRow( - decoration: detail.estCadeau - ? const pw.BoxDecoration(color: PdfColors.green50) - : detail.aRemise - ? const pw.BoxDecoration(color: PdfColors.orange50) - : null, + + // Informations centrales + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.center, children: [ - pw.Padding( - padding: const pw.EdgeInsets.all(1), + pw.Text( + 'Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', + style: boldClientStyle), + pw.SizedBox(height: 3), + pw.Container( + width: 80, height: 1, color: PdfColors.black), + pw.SizedBox(height: 3), + pw.Container( + padding: const pw.EdgeInsets.all(3), + decoration: pw.BoxDecoration( + border: pw.Border.all(color: PdfColors.black), + ), child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ - pw.Row( - children: [ - pw.Expanded( - child: pw.Text(detail.produitNom ?? 'Produit inconnu', - style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)), - ), - if (detail.estCadeau) - pw.Container( - padding: const pw.EdgeInsets.symmetric(horizontal: 1, vertical: 0.5), - decoration: pw.BoxDecoration( - color: PdfColors.green, - borderRadius: pw.BorderRadius.circular(2), - ), - child: pw.Text('🎁', style: pw.TextStyle(fontSize: 4, color: PdfColors.white)), - ), - ], - ), - if (produit?.category != null && produit!.category.isNotEmpty) - pw.Text('${produit.category}${produit?.marque != null && produit!.marque.isNotEmpty ? ' - ${produit.marque}' : ''}', style: tinyTextStyle), - if (produit?.imei != null && produit!.imei!.isNotEmpty) - pw.Text('IMEI: ${produit.imei}', style: tinyTextStyle), + pw.Text('Boutique:', style: frameTextStyle), + pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', + style: boldTextStyle), + pw.SizedBox(height: 1), + pw.Text('Bon N°:', style: frameTextStyle), + pw.Text( + '${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', + style: boldTextStyle), ], ), ), + ], + ), + + // Informations client - compact + pw.Container( + width: 100, + decoration: pw.BoxDecoration( + border: + pw.Border.all(color: PdfColors.black, width: 1), + ), + padding: const pw.EdgeInsets.all(4), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.center, + children: [ + pw.Text('CLIENT', style: frameTextStyle), + pw.SizedBox(height: 1), + pw.Text( + 'ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', + style: smallTextStyle), + pw.Container( + width: 80, + height: 1, + color: PdfColors.black, + margin: + const pw.EdgeInsets.symmetric(vertical: 1)), + pw.Text(client?.nom ?? 'Non spécifié', + style: boldTextStyle), + pw.SizedBox(height: 1), + pw.Text(client?.telephone ?? 'Non spécifié', + style: tinyTextStyle), + ], + ), + ), + ], + ), + + pw.SizedBox(height: 4), + + // Tableau des produits - très compact + pw.Table( + border: pw.TableBorder.all(width: 0.5), + columnWidths: { + 0: const pw.FlexColumnWidth(3.5), + 1: const pw.FlexColumnWidth(0.8), + 2: const pw.FlexColumnWidth(1.2), + 3: const pw.FlexColumnWidth(1.5), + 4: const pw.FlexColumnWidth(1.2), + }, + children: [ + pw.TableRow( + decoration: + const pw.BoxDecoration(color: PdfColors.grey200), + children: [ pw.Padding( - padding: const pw.EdgeInsets.all(1), - child: pw.Text('${detail.quantite}', style: normalTextStyle, textAlign: pw.TextAlign.center), - ), + padding: const pw.EdgeInsets.all(1), + child: pw.Text('Désignations', + style: boldTextStyle)), pw.Padding( - padding: const pw.EdgeInsets.all(1), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, - children: [ - if (detail.estCadeau) ...[ - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', - style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)), - pw.Text('GRATUIT', style: pw.TextStyle(fontSize: 5, color: PdfColors.green700, fontWeight: pw.FontWeight.bold)), - ] else if (detail.aRemise) ...[ - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', - style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)), - pw.Text('${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}', - style: pw.TextStyle(fontSize: 6, color: PdfColors.orange)), - ] else - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', style: smallTextStyle), - ], - ), - ), + padding: const pw.EdgeInsets.all(1), + child: pw.Text('Qté', + style: boldTextStyle, + textAlign: pw.TextAlign.center)), pw.Padding( - padding: const pw.EdgeInsets.all(1), - child: pw.Text( - detail.estCadeau - ? 'CADEAU' - : detail.aRemise - ? 'REMISE' - : '-', - style: pw.TextStyle( - fontSize: 5, - color: detail.estCadeau ? PdfColors.green700 : detail.aRemise ? PdfColors.orange : PdfColors.grey600, - fontWeight: detail.estCadeau ? pw.FontWeight.bold : pw.FontWeight.normal, - ), - textAlign: pw.TextAlign.center, - ), - ), + padding: const pw.EdgeInsets.all(1), + child: pw.Text('P.U.', + style: boldTextStyle, + textAlign: pw.TextAlign.right)), pw.Padding( - padding: const pw.EdgeInsets.all(1), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, - children: [ - if (detail.estCadeau) ...[ - pw.Text('${detail.sousTotal.toStringAsFixed(0)}', - style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)), - pw.Text('GRATUIT', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold, color: PdfColors.green700)), - ] else if (detail.aRemise) ...[ - pw.Text('${detail.sousTotal.toStringAsFixed(0)}', - style: pw.TextStyle(fontSize: 4, decoration: pw.TextDecoration.lineThrough, color: PdfColors.grey600)), - pw.Text('${detail.prixFinal.toStringAsFixed(0)}', style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)), - ] else - pw.Text('${detail.prixFinal.toStringAsFixed(0)}', style: smallTextStyle), - ], - ), - ), + padding: const pw.EdgeInsets.all(1), + child: pw.Text('Remise/Cadeau', + style: boldTextStyle, + textAlign: pw.TextAlign.center)), + pw.Padding( + padding: const pw.EdgeInsets.all(1), + child: pw.Text('Montant', + style: boldTextStyle, + textAlign: pw.TextAlign.right)), ], - ); - }).toList(), - ], - ), + ), + ...detailsAvecProduits.map((item) { + final detail = item['detail'] as DetailCommande; + final produit = item['produit']; - pw.SizedBox(height: 4), - - // Section finale - très compacte - pw.Row( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - // Totaux - pw.Expanded( - flex: 2, - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, - children: [ - if (totalRemises > 0 || totalCadeaux > 0) ...[ - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.end, - children: [ - pw.Text('SOUS-TOTAL:', style: smallTextStyle), - pw.SizedBox(width: 8), - pw.Text('${sousTotal.toStringAsFixed(0)}', style: smallTextStyle), - ], + return pw.TableRow( + decoration: detail.estCadeau + ? const pw.BoxDecoration(color: PdfColors.green50) + : detail.aRemise + ? const pw.BoxDecoration( + color: PdfColors.orange50) + : null, + children: [ + pw.Padding( + padding: const pw.EdgeInsets.all(1), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Row( + children: [ + pw.Expanded( + child: pw.Text( + detail.produitNom ?? + 'Produit inconnu', + style: pw.TextStyle( + fontSize: 6, + fontWeight: + pw.FontWeight.bold)), + ), + if (detail.estCadeau) + pw.Container( + padding: + const pw.EdgeInsets.symmetric( + horizontal: 1, vertical: 0.5), + decoration: pw.BoxDecoration( + color: PdfColors.green, + borderRadius: + pw.BorderRadius.circular(2), + ), + child: pw.Text('🎁', + style: pw.TextStyle( + fontSize: 4, + color: PdfColors.white)), + ), + ], + ), + if (produit?.category != null && + produit!.category.isNotEmpty) + pw.Text( + '${produit.category}${produit?.marque != null && produit!.marque.isNotEmpty ? ' - ${produit.marque}' : ''}', + style: tinyTextStyle), + if (produit?.imei != null && + produit!.imei!.isNotEmpty) + pw.Text('IMEI: ${produit.imei}', + style: tinyTextStyle), + ], + ), ), - pw.SizedBox(height: 1), - ], - - if (totalRemises > 0) ...[ - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.end, - children: [ - pw.Text('REMISES:', style: pw.TextStyle(color: PdfColors.orange, fontSize: 6)), - pw.SizedBox(width: 8), - pw.Text('-${totalRemises.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.orange, fontSize: 6)), - ], + pw.Padding( + padding: const pw.EdgeInsets.all(1), + child: pw.Text('${detail.quantite}', + style: normalTextStyle, + textAlign: pw.TextAlign.center), + ), + pw.Padding( + padding: const pw.EdgeInsets.all(1), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + if (detail.estCadeau) ...[ + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 4, + decoration: + pw.TextDecoration.lineThrough, + color: PdfColors.grey600)), + pw.Text('GRATUIT', + style: pw.TextStyle( + fontSize: 5, + color: PdfColors.green700, + fontWeight: pw.FontWeight.bold)), + ] else if (detail.aRemise) ...[ + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 4, + decoration: + pw.TextDecoration.lineThrough, + color: PdfColors.grey600)), + pw.Text( + '${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 6, + color: PdfColors.orange)), + ] else + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: smallTextStyle), + ], + ), + ), + pw.Padding( + padding: const pw.EdgeInsets.all(1), + child: pw.Text( + detail.estCadeau + ? 'CADEAU' + : detail.aRemise + ? 'REMISE' + : '-', + style: pw.TextStyle( + fontSize: 5, + color: detail.estCadeau + ? PdfColors.green700 + : detail.aRemise + ? PdfColors.orange + : PdfColors.grey600, + fontWeight: detail.estCadeau + ? pw.FontWeight.bold + : pw.FontWeight.normal, + ), + textAlign: pw.TextAlign.center, + ), + ), + pw.Padding( + padding: const pw.EdgeInsets.all(1), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + if (detail.estCadeau) ...[ + pw.Text( + '${detail.sousTotal.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 4, + decoration: + pw.TextDecoration.lineThrough, + color: PdfColors.grey600)), + pw.Text('GRATUIT', + style: pw.TextStyle( + fontSize: 5, + fontWeight: pw.FontWeight.bold, + color: PdfColors.green700)), + ] else if (detail.aRemise) ...[ + pw.Text( + '${detail.sousTotal.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 4, + decoration: + pw.TextDecoration.lineThrough, + color: PdfColors.grey600)), + pw.Text( + '${detail.prixFinal.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 6, + fontWeight: pw.FontWeight.bold)), + ] else + pw.Text( + '${detail.prixFinal.toStringAsFixed(0)}', + style: smallTextStyle), + ], + ), ), - pw.SizedBox(height: 1), ], - - if (totalCadeaux > 0) ...[ + ); + }).toList(), + ], + ), + + pw.SizedBox(height: 4), + + // Section finale - très compacte + pw.Row( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + // Totaux + pw.Expanded( + flex: 2, + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + if (totalRemises > 0 || totalCadeaux > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.end, + children: [ + pw.Text('SOUS-TOTAL:', style: smallTextStyle), + pw.SizedBox(width: 8), + pw.Text('${sousTotal.toStringAsFixed(0)}', + style: smallTextStyle), + ], + ), + pw.SizedBox(height: 1), + ], + if (totalRemises > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.end, + children: [ + pw.Text('REMISES:', + style: pw.TextStyle( + color: PdfColors.orange, + fontSize: 6)), + pw.SizedBox(width: 8), + pw.Text('-${totalRemises.toStringAsFixed(0)}', + style: pw.TextStyle( + color: PdfColors.orange, + fontSize: 6)), + ], + ), + pw.SizedBox(height: 1), + ], + if (totalCadeaux > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.end, + children: [ + pw.Text('CADEAUX ($nombreCadeaux):', + style: pw.TextStyle( + color: PdfColors.green700, + fontSize: 6)), + pw.SizedBox(width: 8), + pw.Text('-${totalCadeaux.toStringAsFixed(0)}', + style: pw.TextStyle( + color: PdfColors.green700, + fontSize: 6)), + ], + ), + pw.SizedBox(height: 1), + ], + pw.Container( + width: 100, + height: 1, + color: PdfColors.black, + margin: + const pw.EdgeInsets.symmetric(vertical: 1)), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.end, children: [ - pw.Text('CADEAUX ($nombreCadeaux):', style: pw.TextStyle(color: PdfColors.green700, fontSize: 6)), + pw.Text('TOTAL:', style: boldTextStyle), pw.SizedBox(width: 8), - pw.Text('-${totalCadeaux.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.green700, fontSize: 6)), + pw.Text( + '${commande.montantTotal.toStringAsFixed(0)} MGA', + style: boldTextStyle), ], ), - pw.SizedBox(height: 1), - ], - - pw.Container(width: 100, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 1)), - - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.end, - children: [ - pw.Text('TOTAL:', style: boldTextStyle), - pw.SizedBox(width: 8), - pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA', style: boldTextStyle), + if (totalCadeaux > 0) ...[ + pw.SizedBox(height: 3), + pw.Container( + padding: const pw.EdgeInsets.all(3), + decoration: pw.BoxDecoration( + color: PdfColors.green50, + borderRadius: pw.BorderRadius.circular(3), + ), + child: pw.Text( + '🎁 $nombreCadeaux cadeau(s) offert(s) (${totalCadeaux.toStringAsFixed(0)} MGA)', + style: pw.TextStyle( + fontSize: 5, color: PdfColors.green700), + ), + ), ], - ), - - if (totalCadeaux > 0) ...[ - pw.SizedBox(height: 3), + ], + ), + ), + + pw.SizedBox(width: 10), + + // Informations vendeurs et signatures + pw.Expanded( + flex: 3, + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + // Vendeurs pw.Container( padding: const pw.EdgeInsets.all(3), decoration: pw.BoxDecoration( - color: PdfColors.green50, + color: PdfColors.grey100, borderRadius: pw.BorderRadius.circular(3), ), - child: pw.Text( - '🎁 $nombreCadeaux cadeau(s) offert(s) (${totalCadeaux.toStringAsFixed(0)} MGA)', - style: pw.TextStyle(fontSize: 5, color: PdfColors.green700), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text('VENDEURS', + style: pw.TextStyle( + fontSize: 6, + fontWeight: pw.FontWeight.bold)), + pw.SizedBox(height: 1), + pw.Row( + children: [ + pw.Expanded( + child: pw.Column( + crossAxisAlignment: + pw.CrossAxisAlignment.start, + children: [ + pw.Text('Initiateur:', + style: tinyTextStyle), + pw.Text( + commandeur != null + ? '${commandeur.name} ${commandeur.lastName ?? ''}' + .trim() + : 'N/A', + style: pw.TextStyle(fontSize: 5), + ), + ], + ), + ), + pw.Expanded( + child: pw.Column( + crossAxisAlignment: + pw.CrossAxisAlignment.start, + children: [ + pw.Text('Validateur:', + style: tinyTextStyle), + pw.Text( + validateur != null + ? '${validateur.name} ${validateur.lastName ?? ''}' + .trim() + : 'N/A', + style: pw.TextStyle(fontSize: 5), + ), + ], + ), + ), + ], + ), + ], ), ), + + pw.SizedBox(height: 6), + + // Signatures + pw.Row( + mainAxisAlignment: + pw.MainAxisAlignment.spaceBetween, + children: [ + pw.Column( + children: [ + pw.Text('Vendeur', + style: pw.TextStyle( + fontSize: 5, + fontWeight: pw.FontWeight.bold)), + pw.SizedBox(height: 8), + pw.Container( + width: 50, + height: 1, + color: PdfColors.black), + ], + ), + pw.Column( + children: [ + pw.Text('Client', + style: pw.TextStyle( + fontSize: 5, + fontWeight: pw.FontWeight.bold)), + pw.SizedBox(height: 8), + pw.Container( + width: 50, + height: 1, + color: PdfColors.black), + ], + ), + ], + ), ], - ], + ), ), - ), - - pw.SizedBox(width: 10), - - // Informations vendeurs et signatures - pw.Expanded( - flex: 3, - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - // Vendeurs - pw.Container( - padding: const pw.EdgeInsets.all(3), - decoration: pw.BoxDecoration( - color: PdfColors.grey100, - borderRadius: pw.BorderRadius.circular(3), - ), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text('VENDEURS', style: pw.TextStyle(fontSize: 6, fontWeight: pw.FontWeight.bold)), - pw.SizedBox(height: 1), - pw.Row( - children: [ - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text('Initiateur:', style: tinyTextStyle), - pw.Text( - commandeur != null ? '${commandeur.name} ${commandeur.lastName ?? ''}'.trim() : 'N/A', - style: pw.TextStyle(fontSize: 5), - ), - ], - ), - ), - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text('Validateur:', style: tinyTextStyle), - pw.Text( - validateur != null ? '${validateur.name} ${validateur.lastName ?? ''}'.trim() : 'N/A', - style: pw.TextStyle(fontSize: 5), - ), - ], - ), - ), - ], - ), - ], - ), - ), - - pw.SizedBox(height: 6), - - // Signatures - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Column( - children: [ - pw.Text('Vendeur', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold)), - pw.SizedBox(height: 8), - pw.Container(width: 50, height: 1, color: PdfColors.black), - ], - ), - pw.Column( - children: [ - pw.Text('Client', style: pw.TextStyle(fontSize: 5, fontWeight: pw.FontWeight.bold)), - pw.SizedBox(height: 8), - pw.Container(width: 50, height: 1, color: PdfColors.black), - ], - ), - ], - ), - ], - ), - ), - ], - ), - - pw.SizedBox(height: 3), - - // Note finale - pw.Text( - 'Arrêté à la somme de: ${_numberToWords(commande.montantTotal.toInt())} Ariary', - style: italicTextStyle, - ), - ], + ], + ), + + pw.SizedBox(height: 3), + + // Note finale + pw.Text( + 'Arrêté à la somme de: ${_numberToWords(commande.montantTotal.toInt())} Ariary', + style: italicTextStyle, + ), + ], + ), ), - ), - ], + ], + ), + ); + } + + pdf.addPage( + pw.Page( + pageFormat: PdfPageFormat.a4.landscape, + margin: const pw.EdgeInsets.all(10), + build: (pw.Context context) { + return pw.Row( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + // Exemplaire CLIENT (à gauche) + pw.Expanded( + child: buildExemplaire("CLIENT"), + ), + + pw.SizedBox(width: 10), + + // Ligne de séparation verticale avec ciseaux + pw.Column( + mainAxisAlignment: pw.MainAxisAlignment.center, + children: [ + pw.Transform.rotate( + angle: 3.14159 / 2, // 90 degrés en radians + child: pw.Text('✂️ DÉCOUPER ICI ✂️', + style: pw.TextStyle( + fontSize: 8, color: PdfColors.grey600)), + ), + pw.Container( + width: 1, + height: 200, + color: PdfColors.grey400, + ), + ], + ), + + pw.SizedBox(width: 10), + + // Exemplaire MAGASIN (à droite) + pw.Expanded( + child: buildExemplaire("MAGASIN", isSecond: true), + ), + ], + ); + }, ), ); + + // Sauvegarder le PDF + final output = await getTemporaryDirectory(); + final file = File("${output.path}/bon_livraison_${commande.id}.pdf"); + await file.writeAsBytes(await pdf.save()); + + // Partager ou ouvrir le fichier + await OpenFile.open(file.path); } - - pdf.addPage( - pw.Page( - pageFormat: PdfPageFormat.a4.landscape, - margin: const pw.EdgeInsets.all(10), - build: (pw.Context context) { - return pw.Row( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - // Exemplaire CLIENT (à gauche) - pw.Expanded( - child: buildExemplaire("CLIENT"), - ), - - pw.SizedBox(width: 10), - - // Ligne de séparation verticale avec ciseaux - pw.Column( - mainAxisAlignment: pw.MainAxisAlignment.center, - children: [ - pw.Transform.rotate( - angle: 3.14159 / 2, // 90 degrés en radians - child: pw.Text('✂️ DÉCOUPER ICI ✂️', style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600)), - ), - pw.Container( - width: 1, - height: 200, - color: PdfColors.grey400, - ), - ], - ), - - pw.SizedBox(width: 10), - - // Exemplaire MAGASIN (à droite) - pw.Expanded( - child: buildExemplaire("MAGASIN", isSecond: true), - ), - ], - ); - }, - ), - ); - - // Sauvegarder le PDF - final output = await getTemporaryDirectory(); - final file = File("${output.path}/bon_livraison_${commande.id}.pdf"); - await file.writeAsBytes(await pdf.save()); - - // Partager ou ouvrir le fichier - await OpenFile.open(file.path); -} //============================================================== - // Modifiez la méthode _generateInvoice dans GestionCommandesPage -Future _generateInvoice(Commande commande) async { - final details = await _database.getDetailsCommande(commande.id!); - final client = await _database.getClientById(commande.clientId); - final pointDeVente = await _database.getPointDeVenteById(1); - - // Récupérer les informations des vendeurs - final commandeur = commande.commandeurId != null - ? await _database.getUserById(commande.commandeurId!) - : null; - final validateur = commande.validateurId != null - ? await _database.getUserById(commande.validateurId!) - : null; - - final iconPhone = await buildIconPhoneText(); - final iconChecked = await buildIconCheckedText(); - final iconGlobe = await buildIconGlobeText(); - - double sousTotal = 0; - double totalRemises = 0; - double totalCadeaux = 0; - int nombreCadeaux = 0; - - for (final detail in details) { - sousTotal += detail.sousTotal; - if (detail.estCadeau) { - totalCadeaux += detail.sousTotal; - nombreCadeaux += detail.quantite; - } else { - totalRemises += detail.montantRemise; + Future _generateInvoice(Commande commande) async { + final details = await _database.getDetailsCommande(commande.id!); + final client = await _database.getClientById(commande.clientId); + final pointDeVente = await _database.getPointDeVenteById(1); + + // Récupérer les informations des vendeurs + final commandeur = commande.commandeurId != null + ? await _database.getUserById(commande.commandeurId!) + : null; + final validateur = commande.validateurId != null + ? await _database.getUserById(commande.validateurId!) + : null; + + final iconPhone = await buildIconPhoneText(); + final iconChecked = await buildIconCheckedText(); + final iconGlobe = await buildIconGlobeText(); + + double sousTotal = 0; + double totalRemises = 0; + double totalCadeaux = 0; + int nombreCadeaux = 0; + + for (final detail in details) { + sousTotal += detail.sousTotal; + if (detail.estCadeau) { + totalCadeaux += detail.sousTotal; + nombreCadeaux += detail.quantite; + } else { + totalRemises += detail.montantRemise; + } } - } - final List> detailsAvecProduits = []; - for (final detail in details) { - final produit = await _database.getProductById(detail.produitId); - detailsAvecProduits.add({ - 'detail': detail, - 'produit': produit, - }); - } - - final pdf = pw.Document(); - final imageBytes = await loadImage(); - final image = pw.MemoryImage(imageBytes); - final italicFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Italic.ttf')); - - // Tailles de texte adaptées pour le mode portrait - final smallTextStyle = pw.TextStyle(fontSize: 8); - final normalTextStyle = pw.TextStyle(fontSize: 9); - final boldTextStyle = pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold); - final boldClientTextStyle = pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold); - final frameTextStyle = pw.TextStyle(fontSize: 9); - final italicTextStyle = pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, font: italicFont); - final italicTextStyleLogo = pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold, font: italicFont); - final emojiSuportFont = pw.Font.ttf(await rootBundle.load('assets/NotoEmoji-Regular.ttf')); - final emojifont = pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold, font: emojiSuportFont); - - pdf.addPage( - pw.Page( - pageFormat: PdfPageFormat.a4, // Mode portrait - margin: const pw.EdgeInsets.all(20), // Marges normales - build: (pw.Context context) { - return pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - // En-tête avec logo et informations - optimisé pour portrait - pw.Row( - crossAxisAlignment: pw.CrossAxisAlignment.start, - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - // Section logo et adresses - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Container( - width: 120, - height: 120, - child: pw.Image(image), - ), - pw.Text(' NOTRE COMPETENCE, A VOTRE SERVICE', style: italicTextStyleLogo), - pw.SizedBox(height: 10), - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Row(children: [iconChecked, pw.SizedBox(width: 4), pw.Text('REMAX by GUYCOM Andravoangy', style: smallTextStyle)]), - pw.SizedBox(height: 2), - pw.Row(children: [iconChecked, pw.SizedBox(width: 4), pw.Text('SUPREME CENTER Behoririka box 405', style: smallTextStyle)]), - pw.SizedBox(height: 2), - pw.Row(children: [iconChecked, pw.SizedBox(width: 4), pw.Text('SUPREME CENTER Behoririka box 416', style: smallTextStyle)]), - pw.SizedBox(height: 2), - pw.Row(children: [iconChecked, pw.SizedBox(width: 4), pw.Text('SUPREME CENTER Behoririka box 119', style: smallTextStyle)]), - pw.SizedBox(height: 2), - pw.Row(children: [iconChecked, pw.SizedBox(width: 4), pw.Text('TRIPOLITSA Analakely BOX 7', style: smallTextStyle)]), - ], - ), - pw.SizedBox(height: 8), - pw.Row(children: [iconPhone, pw.SizedBox(width: 4), pw.Text('033 37 808 18', style: smallTextStyle)]), - pw.Row(children: [iconGlobe, pw.SizedBox(width: 4), pw.Text('www.guycom.mg', style: smallTextStyle)]), - pw.Text('Facebook: GuyCom', style: smallTextStyle), - ], - ), - - // Section droite - informations commande et client - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, - children: [ - pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', style: boldClientTextStyle), - pw.SizedBox(height: 8), - pw.Container(width: 200, height: 1, color: PdfColors.black), - pw.SizedBox(height: 10), - - // Informations boutique et facture - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.end, - children: [ - pw.Container( - width: 100, - height: 45, - padding: const pw.EdgeInsets.all(6), - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.black, width: 1), - ), - child: pw.Column( - mainAxisAlignment: pw.MainAxisAlignment.center, - children: [ - pw.Text('Boutique:', style: frameTextStyle), - pw.SizedBox(height: 2), - pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', style: boldClientTextStyle), - ] - ) - ), - pw.SizedBox(width: 10), - pw.Container( - width: 100, - height: 45, - padding: const pw.EdgeInsets.all(6), - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.black, width: 1), - ), - child: pw.Column( - mainAxisAlignment: pw.MainAxisAlignment.center, - children: [ - pw.Text('Facture N°:', style: frameTextStyle), - pw.SizedBox(height: 2), - pw.Text('${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', style: boldClientTextStyle), - ] - ) - ), - ], - ), - - pw.SizedBox(height: 15), - - // Section client - pw.Container( - width: 220, - height: 100, - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.black, width: 1), + final List> detailsAvecProduits = []; + for (final detail in details) { + final produit = await _database.getProductById(detail.produitId); + detailsAvecProduits.add({ + 'detail': detail, + 'produit': produit, + }); + } + + final pdf = pw.Document(); + final imageBytes = await loadImage(); + final image = pw.MemoryImage(imageBytes); + final italicFont = + pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Italic.ttf')); + + // Tailles de texte adaptées pour le mode portrait + final smallTextStyle = pw.TextStyle(fontSize: 8); + final normalTextStyle = pw.TextStyle(fontSize: 9); + final boldTextStyle = + pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold); + final boldClientTextStyle = + pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold); + final frameTextStyle = pw.TextStyle(fontSize: 9); + final italicTextStyle = pw.TextStyle( + fontSize: 9, fontWeight: pw.FontWeight.bold, font: italicFont); + final italicTextStyleLogo = pw.TextStyle( + fontSize: 7, fontWeight: pw.FontWeight.bold, font: italicFont); + final emojiSuportFont = + pw.Font.ttf(await rootBundle.load('assets/NotoEmoji-Regular.ttf')); + final emojifont = pw.TextStyle( + fontSize: 8, fontWeight: pw.FontWeight.bold, font: emojiSuportFont); + + pdf.addPage( + pw.Page( + pageFormat: PdfPageFormat.a4, // Mode portrait + margin: const pw.EdgeInsets.all(20), // Marges normales + build: (pw.Context context) { + return pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + // En-tête avec logo et informations - optimisé pour portrait + pw.Row( + crossAxisAlignment: pw.CrossAxisAlignment.start, + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + // Section logo et adresses + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Container( + width: 120, + height: 120, + child: pw.Image(image), ), - padding: const pw.EdgeInsets.all(10), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.center, + pw.Text(' NOTRE COMPETENCE, A VOTRE SERVICE', + style: italicTextStyleLogo), + pw.SizedBox(height: 10), + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ - pw.Text('ID Client: ', style: frameTextStyle), - pw.SizedBox(height: 4), - pw.Text('${pointDeVente?['nom'] ?? 'S405A'} - ${client?.id ?? 'Non spécifié'}', style: boldClientTextStyle), - pw.SizedBox(height: 6), - pw.Container(width: 180, height: 1, color: PdfColors.black), - pw.SizedBox(height: 4), - pw.Text(client?.nom ?? 'Non spécifié', style: boldClientTextStyle), - pw.SizedBox(height: 4), - pw.Text(client?.telephone ?? 'Non spécifié', style: frameTextStyle), + pw.Row(children: [ + iconChecked, + pw.SizedBox(width: 4), + pw.Text('REMAX by GUYCOM Andravoangy', + style: smallTextStyle) + ]), + pw.SizedBox(height: 2), + pw.Row(children: [ + iconChecked, + pw.SizedBox(width: 4), + pw.Text('SUPREME CENTER Behoririka box 405', + style: smallTextStyle) + ]), + pw.SizedBox(height: 2), + pw.Row(children: [ + iconChecked, + pw.SizedBox(width: 4), + pw.Text('SUPREME CENTER Behoririka box 416', + style: smallTextStyle) + ]), + pw.SizedBox(height: 2), + pw.Row(children: [ + iconChecked, + pw.SizedBox(width: 4), + pw.Text('SUPREME CENTER Behoririka box 119', + style: smallTextStyle) + ]), + pw.SizedBox(height: 2), + pw.Row(children: [ + iconChecked, + pw.SizedBox(width: 4), + pw.Text('TRIPOLITSA Analakely BOX 7', + style: smallTextStyle) + ]), ], ), - ), - ], - ), - ], - ), - - pw.SizedBox(height: 15), - - // Tableau des produits avec cadeaux - optimisé pour portrait - pw.Table( - border: pw.TableBorder.all(width: 0.5), - columnWidths: { - 0: const pw.FlexColumnWidth(3), // Désignations - 1: const pw.FlexColumnWidth(0.8), // Quantité - 2: const pw.FlexColumnWidth(1.2), // Prix unitaire - 3: const pw.FlexColumnWidth(1.5), // Remise/cadeau - 4: const pw.FlexColumnWidth(1.2), // Montant - }, - children: [ - pw.TableRow( - decoration: const pw.BoxDecoration(color: PdfColors.grey200), - children: [ - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Text('Désignations', style: boldTextStyle) - ), - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Text('Qté', style: boldTextStyle, textAlign: pw.TextAlign.center) - ), - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Text('Prix unitaire', style: boldTextStyle, textAlign: pw.TextAlign.right) - ), - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Text('Remise/Cadeau', style: boldTextStyle, textAlign: pw.TextAlign.center) - ), - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Text('Montant', style: boldTextStyle, textAlign: pw.TextAlign.right) - ), - ], - ), - - ...detailsAvecProduits.map((item) { - final detail = item['detail'] as DetailCommande; - final produit = item['produit']; - - return pw.TableRow( - decoration: detail.estCadeau - ? const pw.BoxDecoration( - color: PdfColors.green50, - border: pw.Border( - left: pw.BorderSide( - color: PdfColors.green300, - width: 3, - ), - ), - ) - : detail.aRemise - ? const pw.BoxDecoration( - color: PdfColors.orange50, - border: pw.Border( - left: pw.BorderSide( - color: PdfColors.orange300, - width: 3, - ), - ), - ) - : null, + pw.SizedBox(height: 8), + pw.Row(children: [ + iconPhone, + pw.SizedBox(width: 4), + pw.Text('033 37 808 18', style: smallTextStyle) + ]), + pw.Row(children: [ + iconGlobe, + pw.SizedBox(width: 4), + pw.Text('www.guycom.mg', style: smallTextStyle) + ]), + pw.Text('Facebook: GuyCom', style: smallTextStyle), + ], + ), + + // Section droite - informations commande et client + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, children: [ - pw.Padding( - padding: const pw.EdgeInsets.all(4), + pw.Text( + 'Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', + style: boldClientTextStyle), + pw.SizedBox(height: 8), + pw.Container( + width: 200, height: 1, color: PdfColors.black), + pw.SizedBox(height: 10), + + // Informations boutique et facture + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.end, + children: [ + pw.Container( + width: 100, + height: 45, + padding: const pw.EdgeInsets.all(6), + decoration: pw.BoxDecoration( + border: pw.Border.all( + color: PdfColors.black, width: 1), + ), + child: pw.Column( + mainAxisAlignment: + pw.MainAxisAlignment.center, + children: [ + pw.Text('Boutique:', style: frameTextStyle), + pw.SizedBox(height: 2), + pw.Text( + '${pointDeVente?['nom'] ?? 'S405A'}', + style: boldClientTextStyle), + ])), + pw.SizedBox(width: 10), + pw.Container( + width: 100, + height: 45, + padding: const pw.EdgeInsets.all(6), + decoration: pw.BoxDecoration( + border: pw.Border.all( + color: PdfColors.black, width: 1), + ), + child: pw.Column( + mainAxisAlignment: + pw.MainAxisAlignment.center, + children: [ + pw.Text('Facture N°:', + style: frameTextStyle), + pw.SizedBox(height: 2), + pw.Text( + '${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', + style: boldClientTextStyle), + ])), + ], + ), + + pw.SizedBox(height: 15), + + // Section client + pw.Container( + width: 220, + height: 100, + decoration: pw.BoxDecoration( + border: + pw.Border.all(color: PdfColors.black, width: 1), + ), + padding: const pw.EdgeInsets.all(10), child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, + crossAxisAlignment: pw.CrossAxisAlignment.center, children: [ - pw.Row( - children: [ - pw.Expanded( - child: pw.Text(detail.produitNom ?? 'Produit inconnu', - style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)), + pw.Text('ID Client: ', style: frameTextStyle), + pw.SizedBox(height: 4), + pw.Text( + '${pointDeVente?['nom'] ?? 'S405A'} - ${client?.id ?? 'Non spécifié'}', + style: boldClientTextStyle), + pw.SizedBox(height: 6), + pw.Container( + width: 180, height: 1, color: PdfColors.black), + pw.SizedBox(height: 4), + pw.Text(client?.nom ?? 'Non spécifié', + style: boldClientTextStyle), + pw.SizedBox(height: 4), + pw.Text(client?.telephone ?? 'Non spécifié', + style: frameTextStyle), + ], + ), + ), + ], + ), + ], + ), + + pw.SizedBox(height: 15), + + // Tableau des produits avec cadeaux - optimisé pour portrait + pw.Table( + border: pw.TableBorder.all(width: 0.5), + columnWidths: { + 0: const pw.FlexColumnWidth(3), // Désignations + 1: const pw.FlexColumnWidth(0.8), // Quantité + 2: const pw.FlexColumnWidth(1.2), // Prix unitaire + 3: const pw.FlexColumnWidth(1.5), // Remise/cadeau + 4: const pw.FlexColumnWidth(1.2), // Montant + }, + children: [ + pw.TableRow( + decoration: + const pw.BoxDecoration(color: PdfColors.grey200), + children: [ + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Text('Désignations', style: boldTextStyle)), + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Text('Qté', + style: boldTextStyle, + textAlign: pw.TextAlign.center)), + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Text('Prix unitaire', + style: boldTextStyle, + textAlign: pw.TextAlign.right)), + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Text('Remise/Cadeau', + style: boldTextStyle, + textAlign: pw.TextAlign.center)), + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Text('Montant', + style: boldTextStyle, + textAlign: pw.TextAlign.right)), + ], + ), + ...detailsAvecProduits.map((item) { + final detail = item['detail'] as DetailCommande; + final produit = item['produit']; + + return pw.TableRow( + decoration: detail.estCadeau + ? const pw.BoxDecoration( + color: PdfColors.green50, + border: pw.Border( + left: pw.BorderSide( + color: PdfColors.green300, + width: 3, ), - if (detail.estCadeau) - pw.Container( - padding: const pw.EdgeInsets.symmetric(horizontal: 4, vertical: 2), - decoration: pw.BoxDecoration( - color: PdfColors.green100, - borderRadius: pw.BorderRadius.circular(4), - ), - child: pw.Text( - 'CADEAU', - style: pw.TextStyle( - fontSize: 7, - fontWeight: pw.FontWeight.bold, - color: PdfColors.green700, - ), + ), + ) + : detail.aRemise + ? const pw.BoxDecoration( + color: PdfColors.orange50, + border: pw.Border( + left: pw.BorderSide( + color: PdfColors.orange300, + width: 3, ), ), - ], - ), - pw.SizedBox(height: 2), - if (produit?.category != null && produit!.category.isNotEmpty && produit?.marque != null && produit!.marque.isNotEmpty) - pw.Text('${produit.category} - ${produit.marque}', style: smallTextStyle), - if (produit?.imei != null && produit!.imei!.isNotEmpty) - pw.Text('IMEI: ${produit.imei}', style: smallTextStyle), - if (produit?.reference != null && produit!.reference!.isNotEmpty) + ) + : null, + children: [ + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ pw.Row( children: [ - if (produit?.ram != null && produit!.ram!.isNotEmpty) - pw.Text('${produit.ram}', style: smallTextStyle), - if (produit?.memoireInterne != null && produit!.memoireInterne!.isNotEmpty) - pw.Text(' | ${produit.memoireInterne}', style: smallTextStyle), - pw.Text(' | ${produit.reference}', style: smallTextStyle), + pw.Expanded( + child: pw.Text( + detail.produitNom ?? 'Produit inconnu', + style: pw.TextStyle( + fontSize: 9, + fontWeight: pw.FontWeight.bold)), + ), + if (detail.estCadeau) + pw.Container( + padding: const pw.EdgeInsets.symmetric( + horizontal: 4, vertical: 2), + decoration: pw.BoxDecoration( + color: PdfColors.green100, + borderRadius: + pw.BorderRadius.circular(4), + ), + child: pw.Text( + 'CADEAU', + style: pw.TextStyle( + fontSize: 7, + fontWeight: pw.FontWeight.bold, + color: PdfColors.green700, + ), + ), + ), ], ), + pw.SizedBox(height: 2), + if (produit?.category != null && + produit!.category.isNotEmpty && + produit?.marque != null && + produit!.marque.isNotEmpty) + pw.Text( + '${produit.category} - ${produit.marque}', + style: smallTextStyle), + if (produit?.imei != null && + produit!.imei!.isNotEmpty) + pw.Text('IMEI: ${produit.imei}', + style: smallTextStyle), + if (produit?.reference != null && + produit!.reference!.isNotEmpty) + pw.Row( + children: [ + if (produit?.ram != null && + produit!.ram!.isNotEmpty) + pw.Text('${produit.ram}', + style: smallTextStyle), + if (produit?.memoireInterne != null && + produit!.memoireInterne!.isNotEmpty) + pw.Text(' | ${produit.memoireInterne}', + style: smallTextStyle), + pw.Text(' | ${produit.reference}', + style: smallTextStyle), + ], + ), + ], + ), + ), + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Text('${detail.quantite}', + style: normalTextStyle, + textAlign: pw.TextAlign.center), + ), + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + if (detail.estCadeau) ...[ + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 7, + decoration: pw.TextDecoration.lineThrough, + color: PdfColors.grey600, + )), + pw.Text('GRATUIT', + style: pw.TextStyle( + fontSize: 8, + color: PdfColors.green700, + fontWeight: pw.FontWeight.bold, + )), + ] else if (detail.aRemise && + detail.prixUnitaire != + detail.sousTotal / detail.quantite) ...[ + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 7, + decoration: pw.TextDecoration.lineThrough, + color: PdfColors.grey600, + )), + pw.Text( + '${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 9, color: PdfColors.orange)), + ] else + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: normalTextStyle), + ], + ), + ), + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Text( + detail.estCadeau + ? 'CADEAU\nOFFERT' + : detail.aRemise + ? detail.remiseDescription + : '-', + style: pw.TextStyle( + fontSize: 7, + color: detail.estCadeau + ? PdfColors.green700 + : detail.aRemise + ? PdfColors.orange + : PdfColors.grey600, + fontWeight: detail.estCadeau + ? pw.FontWeight.bold + : pw.FontWeight.normal, + ), + textAlign: pw.TextAlign.center, + ), + ), + pw.Padding( + padding: const pw.EdgeInsets.all(4), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + if (detail.estCadeau) ...[ + pw.Text( + '${detail.sousTotal.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 7, + decoration: pw.TextDecoration.lineThrough, + color: PdfColors.grey600, + )), + pw.Text('GRATUIT', + style: pw.TextStyle( + fontSize: 8, + fontWeight: pw.FontWeight.bold, + color: PdfColors.green700, + )), + ] else if (detail.aRemise && + detail.sousTotal != detail.prixFinal) ...[ + pw.Text( + '${detail.sousTotal.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 7, + decoration: pw.TextDecoration.lineThrough, + color: PdfColors.grey600, + )), + pw.Text( + '${detail.prixFinal.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 9, + fontWeight: pw.FontWeight.bold)), + ] else + pw.Text( + '${detail.prixFinal.toStringAsFixed(0)}', + style: normalTextStyle), + ], + ), + ), + ], + ); + }).toList(), + ], + ), + + pw.SizedBox(height: 12), + + // Section totaux - alignée à droite + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.end, + children: [ + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + if (totalRemises > 0 || totalCadeaux > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.end, + children: [ + pw.Text('SOUS-TOTAL', style: normalTextStyle), + pw.SizedBox(width: 20), + pw.Container( + width: 80, + child: pw.Text('${sousTotal.toStringAsFixed(0)}', + style: normalTextStyle, + textAlign: pw.TextAlign.right), + ), ], ), - ), - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Text('${detail.quantite}', style: normalTextStyle, textAlign: pw.TextAlign.center), - ), - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, + pw.SizedBox(height: 4), + ], + if (totalRemises > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.end, children: [ - if (detail.estCadeau) ...[ - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', - style: pw.TextStyle( - fontSize: 7, - decoration: pw.TextDecoration.lineThrough, - color: PdfColors.grey600, - )), - pw.Text('GRATUIT', + pw.Text('REMISES TOTALES', style: pw.TextStyle( - fontSize: 8, - color: PdfColors.green700, - fontWeight: pw.FontWeight.bold, - )), - ] else if (detail.aRemise && detail.prixUnitaire != detail.sousTotal / detail.quantite) ...[ - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', - style: pw.TextStyle( - fontSize: 7, - decoration: pw.TextDecoration.lineThrough, - color: PdfColors.grey600, - )), - pw.Text('${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}', - style: pw.TextStyle(fontSize: 9, color: PdfColors.orange)), - ] else - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', - style: normalTextStyle), + color: PdfColors.orange, fontSize: 9)), + pw.SizedBox(width: 20), + pw.Container( + width: 80, + child: pw.Text( + '-${totalRemises.toStringAsFixed(0)}', + style: pw.TextStyle( + color: PdfColors.orange, + fontWeight: pw.FontWeight.bold, + fontSize: 9), + textAlign: pw.TextAlign.right), + ), ], ), - ), - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Text( - detail.estCadeau - ? 'CADEAU\nOFFERT' - : detail.aRemise - ? detail.remiseDescription - : '-', - style: pw.TextStyle( - fontSize: 7, - color: detail.estCadeau - ? PdfColors.green700 - : detail.aRemise - ? PdfColors.orange - : PdfColors.grey600, - fontWeight: detail.estCadeau ? pw.FontWeight.bold : pw.FontWeight.normal, - ), - textAlign: pw.TextAlign.center, - ), - ), - pw.Padding( - padding: const pw.EdgeInsets.all(4), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, + pw.SizedBox(height: 4), + ], + if (totalCadeaux > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.end, children: [ - if (detail.estCadeau) ...[ - pw.Text('${detail.sousTotal.toStringAsFixed(0)}', - style: pw.TextStyle( - fontSize: 7, - decoration: pw.TextDecoration.lineThrough, - color: PdfColors.grey600, - )), - pw.Text('GRATUIT', - style: pw.TextStyle( - fontSize: 8, - fontWeight: pw.FontWeight.bold, - color: PdfColors.green700, - )), - ] else if (detail.aRemise && detail.sousTotal != detail.prixFinal) ...[ - pw.Text('${detail.sousTotal.toStringAsFixed(0)}', + pw.Text('CADEAUX OFFERTS ($nombreCadeaux)', style: pw.TextStyle( - fontSize: 7, - decoration: pw.TextDecoration.lineThrough, - color: PdfColors.grey600, - )), - pw.Text('${detail.prixFinal.toStringAsFixed(0)}', - style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)), - ] else - pw.Text('${detail.prixFinal.toStringAsFixed(0)}', - style: normalTextStyle), + color: PdfColors.green700, fontSize: 9)), + pw.SizedBox(width: 20), + pw.Container( + width: 80, + child: pw.Text( + '-${totalCadeaux.toStringAsFixed(0)}', + style: pw.TextStyle( + color: PdfColors.green700, + fontWeight: pw.FontWeight.bold, + fontSize: 9), + textAlign: pw.TextAlign.right), + ), ], ), - ), - ], - ); - }).toList(), - ], - ), - - pw.SizedBox(height: 12), - - // Section totaux - alignée à droite - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.end, - children: [ - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, - children: [ - if (totalRemises > 0 || totalCadeaux > 0) ...[ - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.end, - children: [ - pw.Text('SOUS-TOTAL', style: normalTextStyle), - pw.SizedBox(width: 20), - pw.Container( - width: 80, - child: pw.Text('${sousTotal.toStringAsFixed(0)}', - style: normalTextStyle, textAlign: pw.TextAlign.right), - ), - ], - ), - pw.SizedBox(height: 4), - ], - - if (totalRemises > 0) ...[ + pw.SizedBox(height: 4), + ], + if (totalRemises > 0 || totalCadeaux > 0) ...[ + pw.Container( + width: 200, + height: 1, + color: PdfColors.black, + margin: const pw.EdgeInsets.symmetric(vertical: 4), + ), + ], pw.Row( mainAxisAlignment: pw.MainAxisAlignment.end, children: [ - pw.Text('REMISES TOTALES', style: pw.TextStyle(color: PdfColors.orange, fontSize: 9)), + pw.Text('TOTAL', style: boldTextStyle), pw.SizedBox(width: 20), pw.Container( width: 80, - child: pw.Text('-${totalRemises.toStringAsFixed(0)}', - style: pw.TextStyle(color: PdfColors.orange, fontWeight: pw.FontWeight.bold, fontSize: 9), - textAlign: pw.TextAlign.right), + child: pw.Text( + '${commande.montantTotal.toStringAsFixed(0)}', + style: boldTextStyle, + textAlign: pw.TextAlign.right), ), ], ), - pw.SizedBox(height: 4), - ], - - if (totalCadeaux > 0) ...[ - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.end, - children: [ - pw.Text('CADEAUX OFFERTS ($nombreCadeaux)', - style: pw.TextStyle(color: PdfColors.green700, fontSize: 9)), - pw.SizedBox(width: 20), - pw.Container( - width: 80, - child: pw.Text('-${totalCadeaux.toStringAsFixed(0)}', - style: pw.TextStyle(color: PdfColors.green700, fontWeight: pw.FontWeight.bold, fontSize: 9), - textAlign: pw.TextAlign.right), + if (totalRemises > 0 || totalCadeaux > 0) ...[ + pw.SizedBox(height: 4), + pw.Text( + 'Économies réalisées: ${(totalRemises + totalCadeaux).toStringAsFixed(0)} MGA', + style: pw.TextStyle( + fontSize: 8, + color: PdfColors.green, + fontStyle: pw.FontStyle.italic, ), - ], - ), - pw.SizedBox(height: 4), - ], - - if (totalRemises > 0 || totalCadeaux > 0) ...[ - pw.Container( - width: 200, - height: 1, - color: PdfColors.black, - margin: const pw.EdgeInsets.symmetric(vertical: 4), - ), - ], - - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.end, - children: [ - pw.Text('TOTAL', style: boldTextStyle), - pw.SizedBox(width: 20), - pw.Container( - width: 80, - child: pw.Text('${commande.montantTotal.toStringAsFixed(0)}', - style: boldTextStyle, textAlign: pw.TextAlign.right), ), ], - ), - - if (totalRemises > 0 || totalCadeaux > 0) ...[ - pw.SizedBox(height: 4), - pw.Text( - 'Économies réalisées: ${(totalRemises + totalCadeaux).toStringAsFixed(0)} MGA', - style: pw.TextStyle( - fontSize: 8, - color: PdfColors.green, - fontStyle: pw.FontStyle.italic, - ), - ), ], - ], - ), - ], - ), - - pw.SizedBox(height: 15), - - // Montant en lettres - pw.Text('Arrêté à la somme de: ${_numberToWords(commande.montantTotal.toInt())} Ariary', - style: italicTextStyle), - - pw.SizedBox(height: 15), - - // Informations vendeurs - Section dédiée - pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.all(12), - decoration: pw.BoxDecoration( - color: PdfColors.grey100, - borderRadius: pw.BorderRadius.circular(8), - border: pw.Border.all(color: PdfColors.grey300), + ), + ], ), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text( - 'INFORMATIONS VENDEURS', - style: pw.TextStyle( - fontSize: 11, - fontWeight: pw.FontWeight.bold, - color: PdfColors.blue700, + + pw.SizedBox(height: 15), + + // Montant en lettres + pw.Text( + 'Arrêté à la somme de: ${_numberToWords(commande.montantTotal.toInt())} Ariary', + style: italicTextStyle), + + pw.SizedBox(height: 15), + + // Informations vendeurs - Section dédiée + pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.all(12), + decoration: pw.BoxDecoration( + color: PdfColors.grey100, + borderRadius: pw.BorderRadius.circular(8), + border: pw.Border.all(color: PdfColors.grey300), + ), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + 'INFORMATIONS VENDEURS', + style: pw.TextStyle( + fontSize: 11, + fontWeight: pw.FontWeight.bold, + color: PdfColors.blue700, + ), ), - ), - pw.SizedBox(height: 8), - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text( - 'Vendeur initiateur:', - style: pw.TextStyle( - fontSize: 9, - fontWeight: pw.FontWeight.bold, - color: PdfColors.grey700, + pw.SizedBox(height: 8), + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + pw.Expanded( + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + 'Vendeur initiateur:', + style: pw.TextStyle( + fontSize: 9, + fontWeight: pw.FontWeight.bold, + color: PdfColors.grey700, + ), ), - ), - pw.SizedBox(height: 3), - pw.Text( - commandeur != null - ? '${commandeur.name} ${commandeur.lastName ?? ''}'.trim() - : 'Non spécifié', - style: pw.TextStyle( - fontSize: 10, - color: PdfColors.black, + pw.SizedBox(height: 3), + pw.Text( + commandeur != null + ? '${commandeur.name} ${commandeur.lastName ?? ''}' + .trim() + : 'Non spécifié', + style: pw.TextStyle( + fontSize: 10, + color: PdfColors.black, + ), ), - ), - pw.SizedBox(height: 3), - pw.Text( - 'Date: ${DateFormat('dd/MM/yyyy HH:mm').format(commande.dateCommande)}', - style: pw.TextStyle( - fontSize: 8, - color: PdfColors.grey600, + pw.SizedBox(height: 3), + pw.Text( + 'Date: ${DateFormat('dd/MM/yyyy HH:mm').format(commande.dateCommande)}', + style: pw.TextStyle( + fontSize: 8, + color: PdfColors.grey600, + ), ), - ), - ], + ], + ), ), - ), - pw.Container( - width: 1, - height: 40, - color: PdfColors.grey400, - ), - pw.SizedBox(width: 20), - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text( - 'Vendeur validateur:', - style: pw.TextStyle( - fontSize: 9, - fontWeight: pw.FontWeight.bold, - color: PdfColors.grey700, + pw.Container( + width: 1, + height: 40, + color: PdfColors.grey400, + ), + pw.SizedBox(width: 20), + pw.Expanded( + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + 'Vendeur validateur:', + style: pw.TextStyle( + fontSize: 9, + fontWeight: pw.FontWeight.bold, + color: PdfColors.grey700, + ), ), - ), - pw.SizedBox(height: 3), - pw.Text( - validateur != null - ? '${validateur.name} ${validateur.lastName ?? ''}'.trim() - : 'Non spécifié', - style: pw.TextStyle( - fontSize: 10, - color: PdfColors.black, + pw.SizedBox(height: 3), + pw.Text( + validateur != null + ? '${validateur.name} ${validateur.lastName ?? ''}' + .trim() + : 'Non spécifié', + style: pw.TextStyle( + fontSize: 10, + color: PdfColors.black, + ), ), - ), - pw.SizedBox(height: 3), - pw.Text( - 'Date: ${DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now())}', - style: pw.TextStyle( - fontSize: 8, - color: PdfColors.grey600, + pw.SizedBox(height: 3), + pw.Text( + 'Date: ${DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now())}', + style: pw.TextStyle( + fontSize: 8, + color: PdfColors.grey600, + ), ), - ), - ], + ], + ), + ), + ], + ), + ], + ), + ), + + pw.SizedBox(height: 12), + + // Note de remerciement pour les cadeaux + if (totalCadeaux > 0) ...[ + pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.all(10), + decoration: pw.BoxDecoration( + color: PdfColors.blue50, + borderRadius: pw.BorderRadius.circular(6), + border: pw.Border.all(color: PdfColors.blue200), + ), + child: pw.Row( + children: [ + pw.Text('🎁 ', style: emojifont), + pw.Expanded( + child: pw.Text( + 'Merci de votre confiance ! Nous espérons que nos cadeaux vous feront plaisir. ($nombreCadeaux article(s) offert(s) - Valeur: ${totalCadeaux.toStringAsFixed(0)} MGA)', + style: pw.TextStyle( + fontSize: 9, + fontStyle: pw.FontStyle.italic, + color: PdfColors.blue700, + ), ), ), ], ), - ], - ), - ), - - pw.SizedBox(height: 12), - - // Note de remerciement pour les cadeaux - if (totalCadeaux > 0) ...[ - pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.all(10), - decoration: pw.BoxDecoration( - color: PdfColors.blue50, - borderRadius: pw.BorderRadius.circular(6), - border: pw.Border.all(color: PdfColors.blue200), ), - child: pw.Row( - children: [ - pw.Text('🎁 ', style: emojifont), - pw.Expanded( - child: pw.Text( - 'Merci de votre confiance ! Nous espérons que nos cadeaux vous feront plaisir. ($nombreCadeaux article(s) offert(s) - Valeur: ${totalCadeaux.toStringAsFixed(0)} MGA)', + pw.SizedBox(height: 12), + ], + + // Signatures - espacées sur toute la largeur + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.center, + children: [ + pw.Text( + 'Signature vendeur initiateur', style: pw.TextStyle( - fontSize: 9, - fontStyle: pw.FontStyle.italic, - color: PdfColors.blue700, - ), + fontSize: 9, fontWeight: pw.FontWeight.bold), ), - ), - ], - ), + pw.SizedBox(height: 2), + pw.Text( + commandeur != null + ? '${commandeur.name} ${commandeur.lastName ?? ''}' + .trim() + : 'Non spécifié', + style: + pw.TextStyle(fontSize: 8, color: PdfColors.grey600), + ), + pw.SizedBox(height: 20), + pw.Container( + width: 120, height: 1, color: PdfColors.black), + ], + ), + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.center, + children: [ + pw.Text( + 'Signature vendeur validateur', + style: pw.TextStyle( + fontSize: 9, fontWeight: pw.FontWeight.bold), + ), + pw.SizedBox(height: 2), + pw.Text( + validateur != null + ? '${validateur.name} ${validateur.lastName ?? ''}' + .trim() + : 'Non spécifié', + style: + pw.TextStyle(fontSize: 8, color: PdfColors.grey600), + ), + pw.SizedBox(height: 20), + pw.Container( + width: 120, height: 1, color: PdfColors.black), + ], + ), + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.center, + children: [ + pw.Text( + 'Signature du client', + style: pw.TextStyle( + fontSize: 9, fontWeight: pw.FontWeight.bold), + ), + pw.SizedBox(height: 2), + pw.Text( + client?.nomComplet ?? 'Non spécifié', + style: + pw.TextStyle(fontSize: 8, color: PdfColors.grey600), + ), + pw.SizedBox(height: 20), + pw.Container( + width: 120, height: 1, color: PdfColors.black), + ], + ), + ], ), - pw.SizedBox(height: 12), ], - - // Signatures - espacées sur toute la largeur - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.center, - children: [ - pw.Text( - 'Signature vendeur initiateur', - style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold), - ), - pw.SizedBox(height: 2), - pw.Text( - commandeur != null - ? '${commandeur.name} ${commandeur.lastName ?? ''}'.trim() - : 'Non spécifié', - style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600), - ), - pw.SizedBox(height: 20), - pw.Container(width: 120, height: 1, color: PdfColors.black), - ], - ), - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.center, - children: [ - pw.Text( - 'Signature vendeur validateur', - style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold), - ), - pw.SizedBox(height: 2), - pw.Text( - validateur != null - ? '${validateur.name} ${validateur.lastName ?? ''}'.trim() - : 'Non spécifié', - style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600), - ), - pw.SizedBox(height: 20), - pw.Container(width: 120, height: 1, color: PdfColors.black), - ], - ), - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.center, - children: [ - pw.Text( - 'Signature du client', - style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold), - ), - pw.SizedBox(height: 2), - pw.Text( - client?.nomComplet ?? 'Non spécifié', - style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600), - ), - pw.SizedBox(height: 20), - pw.Container(width: 120, height: 1, color: PdfColors.black), - ], - ), - ], - ), - ], - ); - }, - ), - ); + ); + }, + ), + ); - final output = await getTemporaryDirectory(); - final file = File('${output.path}/facture_${commande.id}.pdf'); - await file.writeAsBytes(await pdf.save()); - await OpenFile.open(file.path); -} + final output = await getTemporaryDirectory(); + final file = File('${output.path}/facture_${commande.id}.pdf'); + await file.writeAsBytes(await pdf.save()); + await OpenFile.open(file.path); + } String _numberToWords(int number) { NumbersToLetters.toLetters('fr', number); return NumbersToLetters.toLetters('fr', number); } - -Future _generateInvoiceWithPasswordVerification(Commande commande) async { - await showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return PasswordVerificationDialog( - title: 'Génération de facture', - message: 'Pour générer la facture de la commande #${commande.id}, veuillez confirmer votre identité en saisissant votre mot de passe.', - onPasswordVerified: (String password) async { - // Afficher un indicateur de chargement - Get.dialog( - const Center( - child: CircularProgressIndicator(), - ), - barrierDismissible: false, - ); - - try { - await _generateInvoice(commande); - Get.back(); // Fermer l'indicateur de chargement - - Get.snackbar( - 'Succès', - 'Facture générée avec succès', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, - duration: const Duration(seconds: 2), - ); - } catch (e) { - Get.back(); // Fermer l'indicateur de chargement - Get.snackbar( - 'Erreur', - 'Erreur lors de la génération de la facture: $e', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 3), - ); - } - }, - ); - }, - ); -} -Future _generateBon_lifraisonWithPasswordVerification(Commande commande) async { - await showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return PasswordVerificationDialog( - title: 'Génération de Bon de livraison', - message: 'Pour générer de Bon de livraison de la commande #${commande.id}, veuillez confirmer votre identité en saisissant votre mot de passe.', - onPasswordVerified: (String password) async { - // Afficher un indicateur de chargement - Get.dialog( - const Center( - child: CircularProgressIndicator(), - ), - barrierDismissible: false, - ); - - try { - await _generateBonLivraison(commande); - Get.back(); // Fermer l'indicateur de chargement - - Get.snackbar( - 'Succès', - 'Facture générée avec succès', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, - duration: const Duration(seconds: 2), - ); - } catch (e) { - Get.back(); // Fermer l'indicateur de chargement - Get.snackbar( - 'Erreur', - 'Erreur lors de la génération de la facture: $e', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 3), + Future _generateInvoiceWithPasswordVerification( + Commande commande) async { + await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return PasswordVerificationDialog( + title: 'Génération de facture', + message: + 'Pour générer la facture de la commande #${commande.id}, veuillez confirmer votre identité en saisissant votre mot de passe.', + onPasswordVerified: (String password) async { + // Afficher un indicateur de chargement + Get.dialog( + const Center( + child: CircularProgressIndicator(), + ), + barrierDismissible: false, ); - } - }, - ); - }, - ); -} + try { + await _generateInvoice(commande); + Get.back(); // Fermer l'indicateur de chargement -String _getPaymentMethodLabel(PaymentMethod payment) { - switch (payment.type) { - case PaymentType.cash: - return 'LIQUIDE (${payment.amountGiven.toStringAsFixed(0)} MGA)'; - case PaymentType.card: - return 'CARTE BANCAIRE'; - case PaymentType.mvola: - return 'MVOLA'; - case PaymentType.orange: - return 'ORANGE MONEY'; - case PaymentType.airtel: - return 'AIRTEL MONEY'; - default: - return 'MÉTHODE INCONNUE (${payment.type.toString()})'; // Debug info + Get.snackbar( + 'Succès', + 'Facture générée avec succès', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + } catch (e) { + Get.back(); // Fermer l'indicateur de chargement + Get.snackbar( + 'Erreur', + 'Erreur lors de la génération de la facture: $e', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + } + }, + ); + }, + ); } -} - Future _generateReceipt(Commande commande, PaymentMethod payment) async { - final details = await _database.getDetailsCommande(commande.id!); - final client = await _database.getClientById(commande.clientId); - final commandeur = commande.commandeurId != null - ? await _database.getUserById(commande.commandeurId!) - : null; - final validateur = commande.validateurId != null - ? await _database.getUserById(commande.validateurId!) - : null; - final pointDeVente = commandeur?.pointDeVenteId != null - ? await _database.getPointDeVenteById(commandeur!.pointDeVenteId!) - : null; -final emojiSuportFont = pw.Font.ttf( await rootBundle.load('assets/NotoEmoji-Regular.ttf')); - final emojifont = pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold, font: emojiSuportFont); - final List> detailsAvecProduits = []; - for (final detail in details) { - final produit = await _database.getProductById(detail.produitId); - detailsAvecProduits.add({ - 'detail': detail, - 'produit': produit, - }); + + Future _generateBon_lifraisonWithPasswordVerification( + Commande commande) async { + await showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return PasswordVerificationDialog( + title: 'Génération de Bon de livraison', + message: + 'Pour générer de Bon de livraison de la commande #${commande.id}, veuillez confirmer votre identité en saisissant votre mot de passe.', + onPasswordVerified: (String password) async { + // Afficher un indicateur de chargement + Get.dialog( + const Center( + child: CircularProgressIndicator(), + ), + barrierDismissible: false, + ); + + try { + await _generateBonLivraison(commande); + Get.back(); // Fermer l'indicateur de chargement + + Get.snackbar( + 'Succès', + 'Facture générée avec succès', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + } catch (e) { + Get.back(); // Fermer l'indicateur de chargement + Get.snackbar( + 'Erreur', + 'Erreur lors de la génération de la facture: $e', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 3), + ); + } + }, + ); + }, + ); } - - double sousTotal = 0; - double totalRemises = 0; - double totalCadeaux = 0; - int nombreCadeaux = 0; - - for (final detail in details) { - sousTotal += detail.sousTotal; - if (detail.estCadeau) { - totalCadeaux += detail.sousTotal; - nombreCadeaux += detail.quantite; - } else { - totalRemises += detail.montantRemise; + + String _getPaymentMethodLabel(PaymentMethod payment) { + switch (payment.type) { + case PaymentType.cash: + return 'LIQUIDE (${payment.amountGiven.toStringAsFixed(0)} MGA)'; + case PaymentType.card: + return 'CARTE BANCAIRE'; + case PaymentType.mvola: + return 'MVOLA'; + case PaymentType.orange: + return 'ORANGE MONEY'; + case PaymentType.airtel: + return 'AIRTEL MONEY'; + default: + return 'MÉTHODE INCONNUE (${payment.type.toString()})'; // Debug info } } - - final pdf = pw.Document(); - final imageBytes = await loadImage(); - final image = pw.MemoryImage(imageBytes); - // DEBUG: Affichage des informations de paiement - print('=== DEBUG PAYMENT METHOD ==='); - print('Payment type: ${payment.type}'); - print('Payment type toString: ${payment.type.toString()}'); - print('Payment type runtimeType: ${payment.type.runtimeType}'); - print('Payment type index: ${payment.type.index}'); - print('Amount given: ${payment.amountGiven}'); - print('PaymentType.airtel: ${PaymentType.airtel}'); - print('payment.type == PaymentType.airtel: ${payment.type == PaymentType.airtel}'); - print('=== END DEBUG ==='); - pdf.addPage( - pw.Page( - pageFormat: PdfPageFormat(70 * PdfPageFormat.mm, double.infinity), - margin: const pw.EdgeInsets.all(4), - build: (pw.Context context) { - return pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.center, - children: [ - pw.Center( - child: pw.Container( - width: 40, - height: 40, - child: pw.Image(image), + + Future _generateReceipt( + Commande commande, PaymentMethod payment) async { + final details = await _database.getDetailsCommande(commande.id!); + final client = await _database.getClientById(commande.clientId); + final commandeur = commande.commandeurId != null + ? await _database.getUserById(commande.commandeurId!) + : null; + final validateur = commande.validateurId != null + ? await _database.getUserById(commande.validateurId!) + : null; + final pointDeVente = commandeur?.pointDeVenteId != null + ? await _database.getPointDeVenteById(commandeur!.pointDeVenteId!) + : null; + final emojiSuportFont = + pw.Font.ttf(await rootBundle.load('assets/NotoEmoji-Regular.ttf')); + final emojifont = pw.TextStyle( + fontSize: 7, fontWeight: pw.FontWeight.bold, font: emojiSuportFont); + final List> detailsAvecProduits = []; + for (final detail in details) { + final produit = await _database.getProductById(detail.produitId); + detailsAvecProduits.add({ + 'detail': detail, + 'produit': produit, + }); + } + + double sousTotal = 0; + double totalRemises = 0; + double totalCadeaux = 0; + int nombreCadeaux = 0; + + for (final detail in details) { + sousTotal += detail.sousTotal; + if (detail.estCadeau) { + totalCadeaux += detail.sousTotal; + nombreCadeaux += detail.quantite; + } else { + totalRemises += detail.montantRemise; + } + } + + final pdf = pw.Document(); + final imageBytes = await loadImage(); + final image = pw.MemoryImage(imageBytes); + // DEBUG: Affichage des informations de paiement + print('=== DEBUG PAYMENT METHOD ==='); + print('Payment type: ${payment.type}'); + print('Payment type toString: ${payment.type.toString()}'); + print('Payment type runtimeType: ${payment.type.runtimeType}'); + print('Payment type index: ${payment.type.index}'); + print('Amount given: ${payment.amountGiven}'); + print('PaymentType.airtel: ${PaymentType.airtel}'); + print( + 'payment.type == PaymentType.airtel: ${payment.type == PaymentType.airtel}'); + print('=== END DEBUG ==='); + pdf.addPage( + pw.Page( + pageFormat: PdfPageFormat(70 * PdfPageFormat.mm, double.infinity), + margin: const pw.EdgeInsets.all(4), + build: (pw.Context context) { + return pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.center, + children: [ + pw.Center( + child: pw.Container( + width: 40, + height: 40, + child: pw.Image(image), + ), ), - ), - pw.SizedBox(height: 4), - - pw.Text('GUYCOM MADAGASCAR', - style: pw.TextStyle( - fontSize: 10, - fontWeight: pw.FontWeight.bold, - )), - pw.Text('Tél: 033 37 808 18', style: const pw.TextStyle(fontSize: 7)), - pw.Text('www.guycom.mg', style: const pw.TextStyle(fontSize: 7)), - - pw.SizedBox(height: 6), - - pw.Text('TICKET DE CAISSE', - style: pw.TextStyle( - fontSize: 10, - fontWeight: pw.FontWeight.bold, - decoration: pw.TextDecoration.underline, - )), - pw.Text('N°: ${pointDeVente?['abreviation'] ?? 'PV'}-${commande.id}', - style: const pw.TextStyle(fontSize: 8)), - pw.Text('Date: ${DateFormat('dd/MM/yyyy HH:mm').format(commande.dateCommande)}', - style: const pw.TextStyle(fontSize: 8)), - - if (pointDeVente != null) - pw.Text('Point de vente: ${pointDeVente['designation']}', - style: const pw.TextStyle(fontSize: 8)), - - pw.Divider(thickness: 0.5), - - pw.Text('CLIENT: ${client?.nomComplet ?? 'Non spécifié'}', - style: pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold)), - if (client?.telephone != null) - pw.Text('Tél: ${client!.telephone}', style: const pw.TextStyle(fontSize: 7)), - - if (commandeur != null || validateur != null) - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, + pw.SizedBox(height: 4), + + pw.Text('GUYCOM MADAGASCAR', + style: pw.TextStyle( + fontSize: 10, + fontWeight: pw.FontWeight.bold, + )), + pw.Text('Tél: 033 37 808 18', + style: const pw.TextStyle(fontSize: 7)), + pw.Text('www.guycom.mg', style: const pw.TextStyle(fontSize: 7)), + + pw.SizedBox(height: 6), + + pw.Text('TICKET DE CAISSE', + style: pw.TextStyle( + fontSize: 10, + fontWeight: pw.FontWeight.bold, + decoration: pw.TextDecoration.underline, + )), + pw.Text( + 'N°: ${pointDeVente?['abreviation'] ?? 'PV'}-${commande.id}', + style: const pw.TextStyle(fontSize: 8)), + pw.Text( + 'Date: ${DateFormat('dd/MM/yyyy HH:mm').format(commande.dateCommande)}', + style: const pw.TextStyle(fontSize: 8)), + + if (pointDeVente != null) + pw.Text('Point de vente: ${pointDeVente['designation']}', + style: const pw.TextStyle(fontSize: 8)), + + pw.Divider(thickness: 0.5), + + pw.Text('CLIENT: ${client?.nomComplet ?? 'Non spécifié'}', + style: pw.TextStyle( + fontSize: 8, fontWeight: pw.FontWeight.bold)), + if (client?.telephone != null) + pw.Text('Tél: ${client!.telephone}', + style: const pw.TextStyle(fontSize: 7)), + + if (commandeur != null || validateur != null) + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Divider(thickness: 0.5), + if (commandeur != null) + pw.Text('Vendeur: ${commandeur.name}', + style: const pw.TextStyle(fontSize: 7)), + if (validateur != null) + pw.Text('Validateur: ${validateur.name}', + style: const pw.TextStyle(fontSize: 7)), + ], + ), + + pw.Divider(thickness: 0.5), + + // Tableau des produits avec cadeaux + pw.Table( + columnWidths: { + 0: const pw.FlexColumnWidth(3.5), + 1: const pw.FlexColumnWidth(1), + 2: const pw.FlexColumnWidth(1.5), + }, children: [ - pw.Divider(thickness: 0.5), - if (commandeur != null) - pw.Text('Vendeur: ${commandeur.name}', style: const pw.TextStyle(fontSize: 7)), - if (validateur != null) - pw.Text('Validateur: ${validateur.name}', style: const pw.TextStyle(fontSize: 7)), + pw.TableRow( + children: [ + pw.Text('Désignation', + style: pw.TextStyle( + fontSize: 7, fontWeight: pw.FontWeight.bold)), + pw.Text('Qté', + style: pw.TextStyle( + fontSize: 7, fontWeight: pw.FontWeight.bold)), + pw.Text('P.U', + style: pw.TextStyle( + fontSize: 7, fontWeight: pw.FontWeight.bold)), + ], + decoration: const pw.BoxDecoration( + border: pw.Border(bottom: pw.BorderSide(width: 0.5)), + ), + ), + ...detailsAvecProduits.map((item) { + final detail = item['detail'] as DetailCommande; + final produit = item['produit']; + + return pw.TableRow( + decoration: const pw.BoxDecoration( + border: pw.Border(bottom: pw.BorderSide(width: 0.2))), + children: [ + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Row( + children: [ + pw.Expanded( + child: pw.Text(detail.produitNom ?? 'Produit', + style: const pw.TextStyle(fontSize: 7)), + ), + if (detail.estCadeau) + pw.Text('🎁', style: emojifont), + ], + ), + if (produit?.reference != null) + pw.Text('Ref: ${produit!.reference}', + style: const pw.TextStyle(fontSize: 6)), + if (produit?.imei != null) + pw.Text('IMEI: ${produit!.imei}', + style: const pw.TextStyle(fontSize: 6)), + if (detail.estCadeau) + pw.Text('CADEAU OFFERT', + style: pw.TextStyle( + fontSize: 6, + color: PdfColors.green700, + fontWeight: pw.FontWeight.bold, + )), + if (detail.aRemise && !detail.estCadeau) + pw.Text('Remise: ${detail.remiseDescription}', + style: pw.TextStyle( + fontSize: 6, color: PdfColors.orange)), + ], + ), + pw.Text(detail.quantite.toString(), + style: const pw.TextStyle(fontSize: 7)), + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + if (detail.estCadeau) ...[ + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 6, + decoration: pw.TextDecoration.lineThrough, + color: PdfColors.grey600, + )), + pw.Text('GRATUIT', + style: pw.TextStyle( + fontSize: 7, + color: PdfColors.green700, + fontWeight: pw.FontWeight.bold, + )), + ] else if (detail.aRemise && + detail.prixUnitaire != + detail.prixFinal / detail.quantite) ...[ + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: pw.TextStyle( + fontSize: 6, + decoration: pw.TextDecoration.lineThrough, + color: PdfColors.grey600, + )), + pw.Text( + '${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}', + style: const pw.TextStyle(fontSize: 7)), + ] else + pw.Text( + '${detail.prixUnitaire.toStringAsFixed(0)}', + style: const pw.TextStyle(fontSize: 7)), + ], + ), + ], + ); + }), ], ), - - pw.Divider(thickness: 0.5), - - // Tableau des produits avec cadeaux - pw.Table( - columnWidths: { - 0: const pw.FlexColumnWidth(3.5), - 1: const pw.FlexColumnWidth(1), - 2: const pw.FlexColumnWidth(1.5), - }, - children: [ - pw.TableRow( + + pw.Divider(thickness: 0.5), + + // Totaux avec remises et cadeaux pour le ticket + if (totalRemises > 0 || totalCadeaux > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ - pw.Text('Désignation', style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)), - pw.Text('Qté', style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)), - pw.Text('P.U', style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)), + pw.Text('SOUS-TOTAL:', + style: const pw.TextStyle(fontSize: 8)), + pw.Text('${sousTotal.toStringAsFixed(0)} MGA', + style: const pw.TextStyle(fontSize: 8)), ], - decoration: const pw.BoxDecoration( - border: pw.Border(bottom: pw.BorderSide(width: 0.5)), - ), ), - - ...detailsAvecProduits.map((item) { - final detail = item['detail'] as DetailCommande; - final produit = item['produit']; - - return pw.TableRow( - decoration: const pw.BoxDecoration( - border: pw.Border(bottom: pw.BorderSide(width: 0.2))), + if (totalRemises > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Row( - children: [ - pw.Expanded( - child: pw.Text(detail.produitNom ?? 'Produit', - style: const pw.TextStyle(fontSize: 7)), - ), - if (detail.estCadeau) - pw.Text('🎁', style: emojifont), - ], - ), - if (produit?.reference != null) - pw.Text('Ref: ${produit!.reference}', - style: const pw.TextStyle(fontSize: 6)), - if (produit?.imei != null) - pw.Text('IMEI: ${produit!.imei}', - style: const pw.TextStyle(fontSize: 6)), - if (detail.estCadeau) - pw.Text('CADEAU OFFERT', - style: pw.TextStyle( - fontSize: 6, - color: PdfColors.green700, - fontWeight: pw.FontWeight.bold, - )), - if (detail.aRemise && !detail.estCadeau) - pw.Text('Remise: ${detail.remiseDescription}', - style: pw.TextStyle(fontSize: 6, color: PdfColors.orange)), - ], - ), - pw.Text(detail.quantite.toString(), - style: const pw.TextStyle(fontSize: 7)), - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, - children: [ - if (detail.estCadeau) ...[ - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', - style: pw.TextStyle( - fontSize: 6, - decoration: pw.TextDecoration.lineThrough, - color: PdfColors.grey600, - )), - pw.Text('GRATUIT', - style: pw.TextStyle( - fontSize: 7, - color: PdfColors.green700, - fontWeight: pw.FontWeight.bold, - )), - ] else if (detail.aRemise && detail.prixUnitaire != detail.prixFinal / detail.quantite) ...[ - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', - style: pw.TextStyle( - fontSize: 6, - decoration: pw.TextDecoration.lineThrough, - color: PdfColors.grey600, - )), - pw.Text('${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}', - style: const pw.TextStyle(fontSize: 7)), - ] else - pw.Text('${detail.prixUnitaire.toStringAsFixed(0)}', - style: const pw.TextStyle(fontSize: 7)), - ], - ), + pw.Text('REMISES:', + style: pw.TextStyle( + fontSize: 8, color: PdfColors.orange)), + pw.Text('-${totalRemises.toStringAsFixed(0)} MGA', + style: pw.TextStyle( + fontSize: 8, color: PdfColors.orange)), + ], + ), + ], + if (totalCadeaux > 0) ...[ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + pw.Text('CADEAUX ($nombreCadeaux):', + style: pw.TextStyle( + fontSize: 8, color: PdfColors.green700)), + pw.Text('-${totalCadeaux.toStringAsFixed(0)} MGA', + style: pw.TextStyle( + fontSize: 8, color: PdfColors.green700)), ], - ); - }), + ), + ], + pw.Divider(thickness: 0.3), ], - ), - - pw.Divider(thickness: 0.5), - - // Totaux avec remises et cadeaux pour le ticket - if (totalRemises > 0 || totalCadeaux > 0) ...[ + + // Total final pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ - pw.Text('SOUS-TOTAL:', - style: const pw.TextStyle(fontSize: 8)), - pw.Text('${sousTotal.toStringAsFixed(0)} MGA', - style: const pw.TextStyle(fontSize: 8)), + pw.Text('TOTAL:', + style: pw.TextStyle( + fontSize: 9, fontWeight: pw.FontWeight.bold)), + pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA', + style: pw.TextStyle( + fontSize: 9, fontWeight: pw.FontWeight.bold)), ], ), - - if (totalRemises > 0) ...[ - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Text('REMISES:', - style: pw.TextStyle(fontSize: 8, color: PdfColors.orange)), - pw.Text('-${totalRemises.toStringAsFixed(0)} MGA', - style: pw.TextStyle(fontSize: 8, color: PdfColors.orange)), - ], - ), - ], - - if (totalCadeaux > 0) ...[ - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Text('CADEAUX ($nombreCadeaux):', - style: pw.TextStyle(fontSize: 8, color: PdfColors.green700)), - pw.Text('-${totalCadeaux.toStringAsFixed(0)} MGA', - style: pw.TextStyle(fontSize: 8, color: PdfColors.green700)), - ], + + if (totalRemises > 0 || totalCadeaux > 0) ...[ + pw.SizedBox(height: 4), + pw.Text( + 'Économies: ${(totalRemises + totalCadeaux).toStringAsFixed(0)} MGA !', + style: pw.TextStyle( + fontSize: 7, + color: PdfColors.green, + fontStyle: pw.FontStyle.italic, + ), + textAlign: pw.TextAlign.center, ), ], - - pw.Divider(thickness: 0.3), - ], - - // Total final - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Text('TOTAL:', - style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)), - pw.Text('${commande.montantTotal.toStringAsFixed(0)} MGA', - style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)), - ], - ), - - if (totalRemises > 0 || totalCadeaux > 0) ...[ - pw.SizedBox(height: 4), - pw.Text('Économies: ${(totalRemises + totalCadeaux).toStringAsFixed(0)} MGA !', - style: pw.TextStyle( - fontSize: 7, - color: PdfColors.green, - fontStyle: pw.FontStyle.italic, - ), - textAlign: pw.TextAlign.center, + + pw.Divider(thickness: 0.5), + + // Détails du paiement + pw.Text('MODE DE PAIEMENT:', + style: const pw.TextStyle(fontSize: 8)), + pw.Text( + _getPaymentMethodLabel(payment), + style: + pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold), ), - ], - - pw.Divider(thickness: 0.5), - - // Détails du paiement - pw.Text('MODE DE PAIEMENT:', - style: const pw.TextStyle(fontSize: 8)), - pw.Text( - _getPaymentMethodLabel(payment), - style: pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold), -), - - if (payment.type == PaymentType.cash && payment.amountGiven > commande.montantTotal) - pw.Text('Monnaie rendue: ${(payment.amountGiven - commande.montantTotal).toStringAsFixed(0)} MGA', - style: const pw.TextStyle(fontSize: 8)), - - pw.SizedBox(height: 8), - - // Messages de fin avec cadeaux - if (totalCadeaux > 0) ...[ - pw.Container( - padding: const pw.EdgeInsets.all(4), - decoration: pw.BoxDecoration( - color: PdfColors.green50, - borderRadius: pw.BorderRadius.circular(4), - ), - child: pw.Column( - children: [ - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.center, - children: [ - - pw.Text('🎁', - style: emojifont, - textAlign: pw.TextAlign.center, - ), - pw.Text('Profitez de vos cadeaux !', - style: pw.TextStyle( - fontSize: 7, - fontWeight: pw.FontWeight.bold, - color: PdfColors.green700, - ), - textAlign: pw.TextAlign.center, - ), - pw.Text('🎁', - style: emojifont, - textAlign: pw.TextAlign.center, - ), - ] - ), - pw.Text('$nombreCadeaux article(s) offert(s)', - style: pw.TextStyle( - fontSize: 6, - color: PdfColors.green600, + + if (payment.type == PaymentType.cash && + payment.amountGiven > commande.montantTotal) + pw.Text( + 'Monnaie rendue: ${(payment.amountGiven - commande.montantTotal).toStringAsFixed(0)} MGA', + style: const pw.TextStyle(fontSize: 8)), + + pw.SizedBox(height: 8), + + // Messages de fin avec cadeaux + if (totalCadeaux > 0) ...[ + pw.Container( + padding: const pw.EdgeInsets.all(4), + decoration: pw.BoxDecoration( + color: PdfColors.green50, + borderRadius: pw.BorderRadius.circular(4), + ), + child: pw.Column( + children: [ + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.center, + children: [ + pw.Text( + '🎁', + style: emojifont, + textAlign: pw.TextAlign.center, + ), + pw.Text( + 'Profitez de vos cadeaux !', + style: pw.TextStyle( + fontSize: 7, + fontWeight: pw.FontWeight.bold, + color: PdfColors.green700, + ), + textAlign: pw.TextAlign.center, + ), + pw.Text( + '🎁', + style: emojifont, + textAlign: pw.TextAlign.center, + ), + ]), + pw.Text( + '$nombreCadeaux article(s) offert(s)', + style: pw.TextStyle( + fontSize: 6, + color: PdfColors.green600, + ), + textAlign: pw.TextAlign.center, ), - textAlign: pw.TextAlign.center, - ), - ], + ], + ), ), - ), - pw.SizedBox(height: 6), + pw.SizedBox(height: 6), + ], + + pw.Text('Article non échangeable - Garantie selon conditions', + style: const pw.TextStyle(fontSize: 6)), + pw.Text('Ticket à conserver comme justificatif', + style: const pw.TextStyle(fontSize: 6)), + pw.SizedBox(height: 8), + pw.Text('Merci pour votre confiance !', + style: pw.TextStyle( + fontSize: 8, fontStyle: pw.FontStyle.italic)), ], - - pw.Text('Article non échangeable - Garantie selon conditions', - style: const pw.TextStyle(fontSize: 6)), - pw.Text('Ticket à conserver comme justificatif', - style: const pw.TextStyle(fontSize: 6)), - pw.SizedBox(height: 8), - pw.Text('Merci pour votre confiance !', - style: pw.TextStyle(fontSize: 8, fontStyle: pw.FontStyle.italic)), - ], - ); - }, - ), - ); + ); + }, + ), + ); - final output = await getTemporaryDirectory(); - final file = File('${output.path}/ticket_${commande.id}.pdf'); - await file.writeAsBytes(await pdf.save()); - await OpenFile.open(file.path); -} + final output = await getTemporaryDirectory(); + final file = File('${output.path}/ticket_${commande.id}.pdf'); + await file.writeAsBytes(await pdf.save()); + await OpenFile.open(file.path); + } Color _getStatutColor(StatutCommande statut) { switch (statut) { @@ -2307,299 +2636,319 @@ final emojiSuportFont = pw.Font.ttf( await rootBundle.load('assets/NotoEmoji-Reg // Liste des commandes Expanded( - child: _filteredCommandes.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.inbox, - size: 64, - color: Colors.grey.shade400, - ), - const SizedBox(height: 16), - Text( - 'Aucune commande trouvée', - style: TextStyle( - fontSize: 18, - color: Colors.grey.shade600, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - Text( - 'Essayez de modifier vos filtres', - style: TextStyle( - fontSize: 14, - color: Colors.grey.shade500, + child: _filteredCommandes.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.inbox, + size: 64, + color: Colors.grey.shade400, ), - ), - ], - ), - ) - : ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 16), - itemCount: _filteredCommandes.length, - itemBuilder: (context, index) { - final commande = _filteredCommandes[index]; - - return FutureBuilder>( - future: _database.getDetailsCommande(commande.id!), - builder: (context, snapshot) { - double totalRemises = 0; - bool aDesRemises = false; - - if (snapshot.hasData) { - for (final detail in snapshot.data!) { - totalRemises += detail.montantRemise; - if (detail.aRemise) aDesRemises = true; - } - } - - return Container( - margin: const EdgeInsets.only(bottom: 12), - decoration: BoxDecoration( - color: _getStatutColor(commande.statut), - borderRadius: BorderRadius.circular(12), - border: aDesRemises - ? Border.all(color: Colors.orange.shade300, width: 2) - : null, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: ExpansionTile( - tilePadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - leading: Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(25), - border: aDesRemises - ? Border.all(color: Colors.orange.shade300, width: 2) - : null, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 2, - offset: const Offset(0, 1), - ), - ], - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - aDesRemises ? Icons.discount : _getStatutIcon(commande.statut), - size: 20, - color: aDesRemises - ? Colors.teal.shade700 - : commande.statut == StatutCommande.annulee - ? Colors.red - : Colors.blue.shade600, - ), - Text( - '#${commande.id}', - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - title: Text( - commande.clientNomComplet, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Row( - children: [ - Icon( - Icons.calendar_today, - size: 14, - color: Colors.grey.shade600, - ), - const SizedBox(width: 4), - Text( - DateFormat('dd/MM/yyyy').format(commande.dateCommande), - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - ), - ), - const SizedBox(width: 16), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 2, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - child: Text( - commande.statutLibelle, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w600, - color: commande.statut == StatutCommande.annulee - ? Colors.red - : Colors.blue.shade700, - ), - ), - ), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - Icon( - Icons.attach_money, - size: 14, - color: Colors.green.shade600, - ), - const SizedBox(width: 4), - Text( - '${commande.montantTotal.toStringAsFixed(2)} MGA', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.green.shade700, - ), - ), - // Affichage des remises si elles existent - if (totalRemises > 0) ...[ - const SizedBox(width: 12), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, - ), - decoration: BoxDecoration( - color: Colors.orange.shade100, - borderRadius: BorderRadius.circular(10), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.discount, - size: 12, - color: Colors.teal.shade700, + const SizedBox(height: 16), + Text( + 'Aucune commande trouvée', + style: TextStyle( + fontSize: 18, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, ), - const SizedBox(width: 2), - Text( - '-${totalRemises.toStringAsFixed(0)}', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Colors.teal.shade700, - ), + ), + const SizedBox(height: 8), + Text( + 'Essayez de modifier vos filtres', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade500, ), - ], - ), - ), - ], - ], - ), - ], - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 2, - offset: const Offset(0, 1), - ), - ], - ), - child: IconButton( - icon: Icon( - Icons.receipt_outlined, - color: Colors.blue.shade600, - ), - onPressed: () => _generateBon_lifraisonWithPasswordVerification(commande), - tooltip: 'Générer le Bon de livraison', - ), - ), - const SizedBox(width: 10,), - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 2, - offset: const Offset(0, 1), - ), - ], - ), - child: IconButton( - icon: Icon( - Icons.receipt_long, - color: Colors.blue.shade600, - ), - onPressed: () => _generateInvoiceWithPasswordVerification(commande), - tooltip: 'Générer la facture', - ), - ), - ], - ), - children: [ - Container( - padding: const EdgeInsets.all(16.0), - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(12), - bottomRight: Radius.circular(12), - ), - ), - child: Column( - children: [ - CommandeDetails(commande: commande), - const SizedBox(height: 16), - if (commande.statut != StatutCommande.annulee) - CommandeActions( - commande: commande, - onStatutChanged: _updateStatut, - onPaymentSelected: _showPaymentOptions, + ), + ], ), - ], - ), - ), - ], - ), - ); - }, - ); - }, -) - ), + ) + : ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: _filteredCommandes.length, + itemBuilder: (context, index) { + final commande = _filteredCommandes[index]; + + return FutureBuilder>( + future: _database.getDetailsCommande(commande.id!), + builder: (context, snapshot) { + double totalRemises = 0; + bool aDesRemises = false; + + if (snapshot.hasData) { + for (final detail in snapshot.data!) { + totalRemises += detail.montantRemise; + if (detail.aRemise) aDesRemises = true; + } + } + + return Container( + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: _getStatutColor(commande.statut), + borderRadius: BorderRadius.circular(12), + border: aDesRemises + ? Border.all( + color: Colors.orange.shade300, width: 2) + : null, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: ExpansionTile( + tilePadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + leading: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(25), + border: aDesRemises + ? Border.all( + color: Colors.orange.shade300, + width: 2) + : null, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 2, + offset: const Offset(0, 1), + ), + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + aDesRemises + ? Icons.discount + : _getStatutIcon(commande.statut), + size: 20, + color: aDesRemises + ? Colors.teal.shade700 + : commande.statut == + StatutCommande.annulee + ? Colors.red + : Colors.blue.shade600, + ), + Text( + '#${commande.id}', + style: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + title: Text( + commande.clientNomComplet, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Row( + children: [ + Icon( + Icons.calendar_today, + size: 14, + color: Colors.grey.shade600, + ), + const SizedBox(width: 4), + Text( + DateFormat('dd/MM/yyyy') + .format(commande.dateCommande), + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + const SizedBox(width: 16), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(12), + ), + child: Text( + commande.statutLibelle, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: commande.statut == + StatutCommande.annulee + ? Colors.red + : Colors.blue.shade700, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Icon( + Icons.attach_money, + size: 14, + color: Colors.green.shade600, + ), + const SizedBox(width: 4), + Text( + '${commande.montantTotal.toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.green.shade700, + ), + ), + // Affichage des remises si elles existent + if (totalRemises > 0) ...[ + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.orange.shade100, + borderRadius: + BorderRadius.circular(10), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.discount, + size: 12, + color: Colors.teal.shade700, + ), + const SizedBox(width: 2), + Text( + '-${totalRemises.toStringAsFixed(0)}', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.teal.shade700, + ), + ), + ], + ), + ), + ], + ], + ), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: + Colors.black.withOpacity(0.1), + blurRadius: 2, + offset: const Offset(0, 1), + ), + ], + ), + child: IconButton( + icon: Icon( + Icons.receipt_outlined, + color: Colors.blue.shade600, + ), + onPressed: () => + _generateBon_lifraisonWithPasswordVerification( + commande), + tooltip: 'Générer le Bon de livraison', + ), + ), + if (verifAdmin()) ...[ + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: + Colors.black.withOpacity(0.1), + blurRadius: 2, + offset: const Offset(0, 1), + ), + ], + ), + child: IconButton( + icon: Icon( + Icons.receipt_long, + color: Colors.blue.shade600, + ), + onPressed: () => + _generateInvoiceWithPasswordVerification( + commande), + tooltip: 'Générer la facture', + ), + ), + ], + ], + ), + children: [ + Container( + padding: const EdgeInsets.all(16.0), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(12), + bottomRight: Radius.circular(12), + ), + ), + child: Column( + children: [ + CommandeDetails(commande: commande), + const SizedBox(height: 16), + if (commande.statut != + StatutCommande.annulee) + CommandeActions( + commande: commande, + onStatutChanged: _updateStatut, + onPaymentSelected: + _showPaymentOptions, + ), + ], + ), + ), + ], + ), + ); + }, + ); + }, + )), ], ), ); } -} \ No newline at end of file +}