|
|
|
@ -289,9 +289,13 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
? await _database.getUserById(commande.validateurId!) |
|
|
|
: null; |
|
|
|
|
|
|
|
final iconPhone = await buildIconPhoneText(); |
|
|
|
final iconChecked = await buildIconCheckedText(); |
|
|
|
final iconGlobe = await buildIconGlobeText(); |
|
|
|
// ✅ DEBUG: Vérifiez combien de détails vous avez |
|
|
|
print('=== DEBUG BON DE LIVRAISON ==='); |
|
|
|
print('Nombre de détails récupérés: ${details.length}'); |
|
|
|
|
|
|
|
for (int i = 0; i < details.length; i++) { |
|
|
|
print('Détail $i: ${details[i].produitNom} x${details[i].quantite}'); |
|
|
|
} |
|
|
|
|
|
|
|
double sousTotal = 0; |
|
|
|
double totalRemises = 0; |
|
|
|
@ -308,43 +312,72 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ CORRECTION PRINCIPALE: Améliorer la récupération des produits |
|
|
|
final List<Map<String, dynamic>> detailsAvecProduits = []; |
|
|
|
for (final detail in details) { |
|
|
|
|
|
|
|
for (int i = 0; i < details.length; i++) { |
|
|
|
final detail = details[i]; |
|
|
|
print('Traitement détail $i: ${detail.produitNom}'); |
|
|
|
|
|
|
|
try { |
|
|
|
final produit = await _database.getProductById(detail.produitId); |
|
|
|
|
|
|
|
if (produit != null) { |
|
|
|
detailsAvecProduits.add({ |
|
|
|
'detail': detail, |
|
|
|
'produit': produit, |
|
|
|
}); |
|
|
|
print(' ✅ Produit trouvé: ${produit.name}'); |
|
|
|
} else { |
|
|
|
// ✅ Même si le produit est null, on l'ajoute quand même avec les infos du détail |
|
|
|
detailsAvecProduits.add({ |
|
|
|
'detail': detail, |
|
|
|
'produit': null, // On garde null mais on utilisera les infos du détail |
|
|
|
}); |
|
|
|
print(' ⚠️ Produit non trouvé, utilisation des données du détail'); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
print(' ❌ Erreur lors de la récupération du produit: $e'); |
|
|
|
// En cas d'erreur, on ajoute quand même le détail |
|
|
|
detailsAvecProduits.add({ |
|
|
|
'detail': detail, |
|
|
|
'produit': null, |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
print('Total detailsAvecProduits: ${detailsAvecProduits.length}'); |
|
|
|
|
|
|
|
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 agrandies pour une meilleure lisibilité |
|
|
|
final tinyTextStyle = pw.TextStyle(fontSize: 9); |
|
|
|
final smallTextStyle = pw.TextStyle(fontSize: 10); |
|
|
|
final normalTextStyle = pw.TextStyle(fontSize: 11); |
|
|
|
final boldTextStyle = |
|
|
|
pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold); |
|
|
|
final boldClientStyle = |
|
|
|
pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold); |
|
|
|
final frameTextStyle = pw.TextStyle(fontSize: 10); |
|
|
|
final italicTextStyle = pw.TextStyle( |
|
|
|
fontSize: 9, fontWeight: pw.FontWeight.bold, font: italicFont); |
|
|
|
final italicLogoStyle = pw.TextStyle( |
|
|
|
fontSize: 8, fontWeight: pw.FontWeight.bold, font: italicFont); |
|
|
|
final titleStyle = |
|
|
|
pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold); |
|
|
|
final headerStyle = |
|
|
|
pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold); |
|
|
|
|
|
|
|
// Fonction pour créer un exemplaire en mode paysage |
|
|
|
// ✅ AMÉLIORATION: Gestion des polices avec fallback |
|
|
|
pw.Font? italicFont; |
|
|
|
pw.Font? regularFont; |
|
|
|
|
|
|
|
try { |
|
|
|
italicFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Italic.ttf')); |
|
|
|
regularFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Regular.ttf')); |
|
|
|
} catch (e) { |
|
|
|
print('⚠️ Impossible de charger les polices personnalisées: $e'); |
|
|
|
// Utiliser les polices par défaut |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ DÉFINITION DES STYLES DE TEXTE - Variables globales dans la fonction |
|
|
|
final tinyTextStyle = pw.TextStyle(fontSize: 9, font: regularFont); |
|
|
|
final smallTextStyle = pw.TextStyle(fontSize: 10, font: regularFont); |
|
|
|
final normalTextStyle = pw.TextStyle(fontSize: 11, font: regularFont); |
|
|
|
final boldTextStyle = pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold, font: regularFont); |
|
|
|
final boldClientStyle = pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold, font: regularFont); |
|
|
|
final frameTextStyle = pw.TextStyle(fontSize: 10, font: regularFont); |
|
|
|
final italicTextStyle = pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, font: italicFont ?? regularFont); |
|
|
|
final italicLogoStyle = pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold, font: italicFont ?? regularFont); |
|
|
|
|
|
|
|
// ✅ Fonction pour créer un exemplaire - CORRIGÉE |
|
|
|
pw.Widget buildExemplaire(String typeExemplaire) { |
|
|
|
return pw.Container( |
|
|
|
height: 380, // Hauteur ajustée pour le mode paysage |
|
|
|
// ✅ PAS DE HAUTEUR FIXE - Elle s'adapte au contenu |
|
|
|
width: double.infinity, |
|
|
|
decoration: pw.BoxDecoration( |
|
|
|
border: pw.Border.all(color: PdfColors.black, width: 1.5), |
|
|
|
@ -357,9 +390,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
width: double.infinity, |
|
|
|
padding: const pw.EdgeInsets.all(5), |
|
|
|
decoration: pw.BoxDecoration( |
|
|
|
color: typeExemplaire == "CLIENT" |
|
|
|
? PdfColors.blue100 |
|
|
|
: PdfColors.green100, |
|
|
|
color: typeExemplaire == "CLIENT" ? PdfColors.blue100 : PdfColors.green100, |
|
|
|
), |
|
|
|
child: pw.Center( |
|
|
|
child: pw.Text( |
|
|
|
@ -367,21 +398,19 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 14, |
|
|
|
fontWeight: pw.FontWeight.bold, |
|
|
|
color: typeExemplaire == "CLIENT" |
|
|
|
? PdfColors.blue800 |
|
|
|
: PdfColors.green800, |
|
|
|
color: typeExemplaire == "CLIENT" ? PdfColors.blue800 : PdfColors.green800, |
|
|
|
font: regularFont, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
pw.Expanded( |
|
|
|
child: pw.Padding( |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(8), |
|
|
|
child: pw.Column( |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
|
|
children: [ |
|
|
|
// En-tête principal |
|
|
|
// En-tête principal (logo, infos entreprise, client) |
|
|
|
pw.Row( |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
|
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, |
|
|
|
@ -396,26 +425,16 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
child: pw.Image(image), |
|
|
|
), |
|
|
|
pw.SizedBox(height: 3), |
|
|
|
pw.Text('NOTRE COMPETENCE, A VOTRE SERVICE', |
|
|
|
style: italicLogoStyle), |
|
|
|
pw.Text('NOTRE COMPETENCE, A VOTRE SERVICE', style: italicLogoStyle), |
|
|
|
pw.SizedBox(height: 4), |
|
|
|
pw.Column( |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
|
|
children: [ |
|
|
|
pw.Text('📍 REMAX Andravoangy', |
|
|
|
style: tinyTextStyle), |
|
|
|
pw.Text( |
|
|
|
'📍 SUPREME CENTER Behoririka \n BOX 405 | 416 | 119', |
|
|
|
style: tinyTextStyle), |
|
|
|
pw.Text('📍 Tripolisa analankely BOX 7', |
|
|
|
style: tinyTextStyle), |
|
|
|
pw.Text('📞 033 37 808 18', |
|
|
|
style: tinyTextStyle), |
|
|
|
pw.Text('🌐 www.guycom.mg', |
|
|
|
style: tinyTextStyle), |
|
|
|
pw.SizedBox(height: 2), |
|
|
|
// pw.Text('NIF: 4000106673 - STAT 95210 11 2017 1 003651', |
|
|
|
// style: pw.TextStyle(fontSize: 7, fontWeight: pw.FontWeight.bold)), |
|
|
|
pw.Text('REMAX Andravoangy', style: tinyTextStyle), |
|
|
|
pw.Text('SUPREME CENTER Behoririka \n BOX 405 | 416 | 119', style: tinyTextStyle), |
|
|
|
pw.Text('Tripolisa analankely BOX 7', style: tinyTextStyle), |
|
|
|
pw.Text('033 37 808 18', style: tinyTextStyle), |
|
|
|
pw.Text('www.guycom.mg', style: tinyTextStyle), |
|
|
|
], |
|
|
|
), |
|
|
|
], |
|
|
|
@ -425,12 +444,9 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
pw.Column( |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.center, |
|
|
|
children: [ |
|
|
|
pw.Text( |
|
|
|
'Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', |
|
|
|
style: boldClientStyle), |
|
|
|
pw.Text('Date: ${DateFormat('dd/MM/yyyy').format(DateTime.now())}', style: boldClientStyle), |
|
|
|
pw.SizedBox(height: 4), |
|
|
|
pw.Container( |
|
|
|
width: 100, height: 2, color: PdfColors.black), |
|
|
|
pw.Container(width: 100, height: 2, color: PdfColors.black), |
|
|
|
pw.SizedBox(height: 4), |
|
|
|
pw.Container( |
|
|
|
padding: const pw.EdgeInsets.all(6), |
|
|
|
@ -440,13 +456,10 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
child: pw.Column( |
|
|
|
children: [ |
|
|
|
pw.Text('Boutique:', style: frameTextStyle), |
|
|
|
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', |
|
|
|
style: boldTextStyle), |
|
|
|
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', style: boldTextStyle), |
|
|
|
pw.SizedBox(height: 2), |
|
|
|
pw.Text('Bon N°:', style: frameTextStyle), |
|
|
|
pw.Text( |
|
|
|
'${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', |
|
|
|
style: boldTextStyle), |
|
|
|
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}-P${commande.id}', style: boldTextStyle), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
@ -457,8 +470,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
pw.Container( |
|
|
|
width: 120, |
|
|
|
decoration: pw.BoxDecoration( |
|
|
|
border: |
|
|
|
pw.Border.all(color: PdfColors.black, width: 1), |
|
|
|
border: pw.Border.all(color: PdfColors.black, width: 1), |
|
|
|
), |
|
|
|
padding: const pw.EdgeInsets.all(6), |
|
|
|
child: pw.Column( |
|
|
|
@ -466,20 +478,11 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
children: [ |
|
|
|
pw.Text('CLIENT', style: frameTextStyle), |
|
|
|
pw.SizedBox(height: 2), |
|
|
|
pw.Text( |
|
|
|
'ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', |
|
|
|
style: smallTextStyle), |
|
|
|
pw.Container( |
|
|
|
width: 100, |
|
|
|
height: 1, |
|
|
|
color: PdfColors.black, |
|
|
|
margin: const pw.EdgeInsets.symmetric( |
|
|
|
vertical: 2)), |
|
|
|
pw.Text('${client?.nom} \n ${client?.prenom}', |
|
|
|
style: boldTextStyle), |
|
|
|
pw.Text('ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', style: smallTextStyle), |
|
|
|
pw.Container(width: 100, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 2)), |
|
|
|
pw.Text('${client?.nom} \n ${client?.prenom}', style: boldTextStyle), |
|
|
|
pw.SizedBox(height: 2), |
|
|
|
pw.Text(client?.telephone ?? 'Non spécifié', |
|
|
|
style: tinyTextStyle), |
|
|
|
pw.Text(client?.telephone ?? 'Non spécifié', style: tinyTextStyle), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
@ -488,219 +491,245 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
|
|
|
|
pw.SizedBox(height: 8), |
|
|
|
|
|
|
|
// Tableau des produits (ajusté pour le mode paysage) |
|
|
|
pw.Expanded( |
|
|
|
child: pw.Table( |
|
|
|
// ✅ SOLUTION PRINCIPALE: Tableau avec hauteur dynamique |
|
|
|
pw.Column( |
|
|
|
children: [ |
|
|
|
// Debug: Afficher le nombre d'articles |
|
|
|
pw.Text('Articles trouvés: ${detailsAvecProduits.length}', |
|
|
|
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey, font: regularFont)), |
|
|
|
pw.SizedBox(height: 5), |
|
|
|
|
|
|
|
// ✅ TABLE SANS CONTRAINTE DE HAUTEUR - Elle s'adapte au contenu |
|
|
|
pw.Table( |
|
|
|
border: pw.TableBorder.all(width: 1), |
|
|
|
columnWidths: { |
|
|
|
0: const pw.FlexColumnWidth(5), |
|
|
|
1: const pw.FlexColumnWidth(1.2), |
|
|
|
2: const pw.FlexColumnWidth(1.5), |
|
|
|
3: const pw.FlexColumnWidth(1.5), |
|
|
|
4: const pw.FlexColumnWidth(1.5), |
|
|
|
0: const pw.FlexColumnWidth(5), // Désignations |
|
|
|
1: const pw.FlexColumnWidth(1.2), // Quantité |
|
|
|
2: const pw.FlexColumnWidth(1.5), // Prix unitaire |
|
|
|
3: const pw.FlexColumnWidth(1.5), // Montant |
|
|
|
}, |
|
|
|
children: [ |
|
|
|
// En-tête du tableau |
|
|
|
pw.TableRow( |
|
|
|
decoration: const pw.BoxDecoration( |
|
|
|
color: PdfColors.grey200), |
|
|
|
decoration: const pw.BoxDecoration(color: PdfColors.grey200), |
|
|
|
children: [ |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(3), |
|
|
|
child: pw.Text('Désignations', |
|
|
|
style: boldTextStyle)), |
|
|
|
padding: const pw.EdgeInsets.all(4), |
|
|
|
child: pw.Text('Désignations', style: boldTextStyle) |
|
|
|
), |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(3), |
|
|
|
child: pw.Text('Qté', |
|
|
|
style: boldTextStyle, |
|
|
|
textAlign: pw.TextAlign.center)), |
|
|
|
padding: const pw.EdgeInsets.all(4), |
|
|
|
child: pw.Text('Qté', style: boldTextStyle, textAlign: pw.TextAlign.center) |
|
|
|
), |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(3), |
|
|
|
child: pw.Text('P.U.', |
|
|
|
style: boldTextStyle, |
|
|
|
textAlign: pw.TextAlign.right)), |
|
|
|
// pw.Padding(padding: const pw.EdgeInsets.all(3), |
|
|
|
// child: pw.Text('Remise/Cadeau', style: boldTextStyle, textAlign: pw.TextAlign.center)), |
|
|
|
padding: const pw.EdgeInsets.all(4), |
|
|
|
child: pw.Text('P.U.', style: boldTextStyle, textAlign: pw.TextAlign.right) |
|
|
|
), |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(3), |
|
|
|
child: pw.Text('Montant', |
|
|
|
style: boldTextStyle, |
|
|
|
textAlign: pw.TextAlign.right)), |
|
|
|
padding: const pw.EdgeInsets.all(4), |
|
|
|
child: pw.Text('Montant', style: boldTextStyle, textAlign: pw.TextAlign.right) |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
...detailsAvecProduits.map((item) { |
|
|
|
|
|
|
|
// ✅ TOUTES LES LIGNES DE PRODUITS - SANS LIMITATION |
|
|
|
...detailsAvecProduits.asMap().entries.map((entry) { |
|
|
|
final index = entry.key; |
|
|
|
final item = entry.value; |
|
|
|
final detail = item['detail'] as DetailCommande; |
|
|
|
final produit = item['produit']; |
|
|
|
|
|
|
|
// Debug pour chaque ligne |
|
|
|
print('📋 Ligne PDF $index: ${detail.produitNom} (Quantité: ${detail.quantite})'); |
|
|
|
|
|
|
|
return pw.TableRow( |
|
|
|
decoration: detail.estCadeau |
|
|
|
? const pw.BoxDecoration( |
|
|
|
color: PdfColors.green50) |
|
|
|
? const pw.BoxDecoration(color: PdfColors.green50) |
|
|
|
: detail.aRemise |
|
|
|
? const pw.BoxDecoration( |
|
|
|
color: PdfColors.orange50) |
|
|
|
? const pw.BoxDecoration(color: PdfColors.orange50) |
|
|
|
: index % 2 == 0 |
|
|
|
? const pw.BoxDecoration(color: PdfColors.grey50) |
|
|
|
: null, |
|
|
|
children: [ |
|
|
|
// ✅ Colonne Désignations - Plus compacte |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(3), |
|
|
|
padding: const pw.EdgeInsets.all(4), |
|
|
|
child: pw.Column( |
|
|
|
crossAxisAlignment: |
|
|
|
pw.CrossAxisAlignment.start, |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
|
|
mainAxisSize: pw.MainAxisSize.min, |
|
|
|
children: [ |
|
|
|
// Nom du produit avec badge |
|
|
|
pw.Row( |
|
|
|
children: [ |
|
|
|
pw.Expanded( |
|
|
|
child: pw.Text( |
|
|
|
detail.produitNom ?? |
|
|
|
'Produit inconnu', |
|
|
|
'${detail.produitNom ?? 'Produit inconnu'}', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 10, |
|
|
|
fontWeight: |
|
|
|
pw.FontWeight.bold)), |
|
|
|
fontWeight: pw.FontWeight.bold, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
), |
|
|
|
if (detail.estCadeau) |
|
|
|
pw.Container( |
|
|
|
padding: |
|
|
|
const pw.EdgeInsets.symmetric( |
|
|
|
horizontal: 2, |
|
|
|
vertical: 1), |
|
|
|
padding: const pw.EdgeInsets.symmetric(horizontal: 3, vertical: 1), |
|
|
|
decoration: pw.BoxDecoration( |
|
|
|
color: PdfColors.green, |
|
|
|
borderRadius: |
|
|
|
pw.BorderRadius.circular(2), |
|
|
|
color: PdfColors.green600, |
|
|
|
borderRadius: pw.BorderRadius.circular(3), |
|
|
|
), |
|
|
|
child: pw.Text('🎁', |
|
|
|
child: pw.Text( |
|
|
|
'CADEAU', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 5, |
|
|
|
color: PdfColors.white)), |
|
|
|
fontSize: 6, |
|
|
|
color: PdfColors.white, |
|
|
|
font: regularFont, |
|
|
|
fontWeight: pw.FontWeight.bold |
|
|
|
) |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
if (produit?.category != null && |
|
|
|
produit!.category.isNotEmpty) |
|
|
|
|
|
|
|
pw.SizedBox(height: 2), |
|
|
|
|
|
|
|
// Informations complémentaires sur une seule ligne |
|
|
|
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.Row( |
|
|
|
children: [ |
|
|
|
if (produit?.ram != null && |
|
|
|
produit!.ram!.isNotEmpty) |
|
|
|
pw.Text('${produit.ram}', |
|
|
|
style: smallTextStyle), |
|
|
|
if (produit?.memoireInterne != null && |
|
|
|
produit! |
|
|
|
.memoireInterne!.isNotEmpty) |
|
|
|
[ |
|
|
|
if (produit?.category?.isNotEmpty == true) produit!.category, |
|
|
|
if (produit?.marque?.isNotEmpty == true) produit!.marque, |
|
|
|
if (produit?.imei?.isNotEmpty == true) 'IMEI: ${produit!.imei}', |
|
|
|
].where((info) => info != null).join(' , '), |
|
|
|
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey700, font: regularFont), |
|
|
|
), |
|
|
|
|
|
|
|
// Spécifications techniques |
|
|
|
if (produit?.ram?.isNotEmpty == true || produit?.memoireInterne?.isNotEmpty == true || produit?.reference?.isNotEmpty == true) |
|
|
|
pw.Text( |
|
|
|
' | ${produit.memoireInterne}', |
|
|
|
style: smallTextStyle), |
|
|
|
pw.Text(' | ${produit.reference}', |
|
|
|
style: smallTextStyle), |
|
|
|
], |
|
|
|
[ |
|
|
|
if (produit?.ram?.isNotEmpty == true) 'RAM: ${produit!.ram}', |
|
|
|
if (produit?.memoireInterne?.isNotEmpty == true) 'Stockage: ${produit!.memoireInterne}', |
|
|
|
if (produit?.reference?.isNotEmpty == true) 'Ref: ${produit!.reference}', |
|
|
|
].join(' , '), |
|
|
|
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600, font: regularFont), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
// Colonne Quantité |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(3), |
|
|
|
child: pw.Text('${detail.quantite}', |
|
|
|
padding: const pw.EdgeInsets.all(4), |
|
|
|
child: pw.Text( |
|
|
|
'${detail.quantite}', |
|
|
|
style: normalTextStyle, |
|
|
|
textAlign: pw.TextAlign.center), |
|
|
|
textAlign: pw.TextAlign.center |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
// Colonne Prix Unitaire |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(3), |
|
|
|
padding: const pw.EdgeInsets.all(4), |
|
|
|
child: pw.Column( |
|
|
|
crossAxisAlignment: |
|
|
|
pw.CrossAxisAlignment.end, |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.end, |
|
|
|
mainAxisSize: pw.MainAxisSize.min, |
|
|
|
children: [ |
|
|
|
if (detail.estCadeau) ...[ |
|
|
|
pw.Text( |
|
|
|
'${detail.prixUnitaire.toStringAsFixed(0)}', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 8, |
|
|
|
decoration: pw |
|
|
|
.TextDecoration.lineThrough, |
|
|
|
color: PdfColors.grey600)), |
|
|
|
pw.Text('GRATUIT', |
|
|
|
decoration: pw.TextDecoration.lineThrough, |
|
|
|
color: PdfColors.grey600, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
pw.Text( |
|
|
|
'GRATUIT', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 9, |
|
|
|
color: PdfColors.green700, |
|
|
|
fontWeight: |
|
|
|
pw.FontWeight.bold)), |
|
|
|
fontWeight: pw.FontWeight.bold, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
] else if (detail.aRemise) ...[ |
|
|
|
pw.Text( |
|
|
|
'${detail.prixUnitaire.toStringAsFixed(0)}', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 8, |
|
|
|
decoration: pw |
|
|
|
.TextDecoration.lineThrough, |
|
|
|
color: PdfColors.grey600)), |
|
|
|
decoration: pw.TextDecoration.lineThrough, |
|
|
|
color: PdfColors.grey600, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
pw.Text( |
|
|
|
'${(detail.prixFinal / detail.quantite).toStringAsFixed(0)}', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 9, |
|
|
|
color: PdfColors.orange)), |
|
|
|
fontSize: 10, |
|
|
|
color: PdfColors.orange700, |
|
|
|
fontWeight: pw.FontWeight.bold, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
] else |
|
|
|
pw.Text( |
|
|
|
'${detail.prixUnitaire.toStringAsFixed(0)}', |
|
|
|
style: smallTextStyle), |
|
|
|
style: smallTextStyle |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
// pw.Padding( |
|
|
|
// padding: const pw.EdgeInsets.all(3), |
|
|
|
// child: pw.Text( |
|
|
|
// detail.estCadeau |
|
|
|
// ? 'CADEAU' |
|
|
|
// : detail.aRemise |
|
|
|
// ? 'REMISE' |
|
|
|
// : '-', |
|
|
|
// style: pw.TextStyle( |
|
|
|
// fontSize: 9, |
|
|
|
// color: detail.estCadeau ? PdfColors.green700 : detail.aRemise ? PdfColors.orange : PdfColors.grey600, |
|
|
|
// fontWeight: detail.estCadeau ? pw.FontWeight.bold : pw.FontWeight.normal, |
|
|
|
// ), |
|
|
|
// textAlign: pw.TextAlign.center, |
|
|
|
// ), |
|
|
|
// ), |
|
|
|
|
|
|
|
// Colonne Montant |
|
|
|
pw.Padding( |
|
|
|
padding: const pw.EdgeInsets.all(3), |
|
|
|
padding: const pw.EdgeInsets.all(4), |
|
|
|
child: pw.Column( |
|
|
|
crossAxisAlignment: |
|
|
|
pw.CrossAxisAlignment.end, |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.end, |
|
|
|
mainAxisSize: pw.MainAxisSize.min, |
|
|
|
children: [ |
|
|
|
if (detail.estCadeau) ...[ |
|
|
|
pw.Text( |
|
|
|
'${detail.sousTotal.toStringAsFixed(0)}', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 8, |
|
|
|
decoration: pw |
|
|
|
.TextDecoration.lineThrough, |
|
|
|
color: PdfColors.grey600)), |
|
|
|
pw.Text('GRATUIT', |
|
|
|
decoration: pw.TextDecoration.lineThrough, |
|
|
|
color: PdfColors.grey600, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
pw.Text( |
|
|
|
'GRATUIT', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 9, |
|
|
|
fontWeight: pw.FontWeight.bold, |
|
|
|
color: PdfColors.green700)), |
|
|
|
color: PdfColors.green700, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
] else if (detail.aRemise) ...[ |
|
|
|
pw.Text( |
|
|
|
'${detail.sousTotal.toStringAsFixed(0)}', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 8, |
|
|
|
decoration: pw |
|
|
|
.TextDecoration.lineThrough, |
|
|
|
color: PdfColors.grey600)), |
|
|
|
decoration: pw.TextDecoration.lineThrough, |
|
|
|
color: PdfColors.grey600, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
pw.Text( |
|
|
|
'${detail.prixFinal.toStringAsFixed(0)}', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 9, |
|
|
|
fontWeight: |
|
|
|
pw.FontWeight.bold)), |
|
|
|
fontSize: 10, |
|
|
|
fontWeight: pw.FontWeight.bold, |
|
|
|
font: regularFont |
|
|
|
) |
|
|
|
), |
|
|
|
] else |
|
|
|
pw.Text( |
|
|
|
'${detail.prixFinal.toStringAsFixed(0)}', |
|
|
|
style: smallTextStyle), |
|
|
|
style: smallTextStyle |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
@ -709,11 +738,12 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
}).toList(), |
|
|
|
], |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
|
|
|
|
pw.SizedBox(height: 8), |
|
|
|
pw.SizedBox(height: 12), |
|
|
|
|
|
|
|
// Section finale (ajustée pour le mode paysage) |
|
|
|
// Section finale - Totaux et signatures |
|
|
|
pw.Row( |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
|
|
children: [ |
|
|
|
@ -727,89 +757,55 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
pw.Row( |
|
|
|
mainAxisAlignment: pw.MainAxisAlignment.end, |
|
|
|
children: [ |
|
|
|
pw.Text('SOUS-TOTAL:', |
|
|
|
style: smallTextStyle), |
|
|
|
pw.Text('SOUS-TOTAL:', style: smallTextStyle), |
|
|
|
pw.SizedBox(width: 10), |
|
|
|
pw.Text('${sousTotal.toStringAsFixed(0)}', |
|
|
|
style: smallTextStyle), |
|
|
|
pw.Text('${sousTotal.toStringAsFixed(0)}', style: smallTextStyle), |
|
|
|
], |
|
|
|
), |
|
|
|
pw.SizedBox(height: 2), |
|
|
|
], |
|
|
|
|
|
|
|
if (totalRemises > 0) ...[ |
|
|
|
pw.Row( |
|
|
|
mainAxisAlignment: pw.MainAxisAlignment.end, |
|
|
|
children: [ |
|
|
|
pw.Text('REMISES:', |
|
|
|
style: pw.TextStyle( |
|
|
|
color: PdfColors.orange, |
|
|
|
fontSize: 10)), |
|
|
|
pw.Text('REMISES:', style: pw.TextStyle(color: PdfColors.orange, fontSize: 10, font: regularFont)), |
|
|
|
pw.SizedBox(width: 10), |
|
|
|
pw.Text( |
|
|
|
'-${totalRemises.toStringAsFixed(0)}', |
|
|
|
style: pw.TextStyle( |
|
|
|
color: PdfColors.orange, |
|
|
|
fontSize: 10)), |
|
|
|
pw.Text('-${totalRemises.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.orange, fontSize: 10, font: regularFont)), |
|
|
|
], |
|
|
|
), |
|
|
|
pw.SizedBox(height: 2), |
|
|
|
], |
|
|
|
|
|
|
|
if (totalCadeaux > 0) ...[ |
|
|
|
pw.Row( |
|
|
|
mainAxisAlignment: pw.MainAxisAlignment.end, |
|
|
|
children: [ |
|
|
|
pw.Text('CADEAUX ($nombreCadeaux):', |
|
|
|
style: pw.TextStyle( |
|
|
|
color: PdfColors.green700, |
|
|
|
fontSize: 10)), |
|
|
|
pw.Text('CADEAUX ($nombreCadeaux):', style: pw.TextStyle(color: PdfColors.green700, fontSize: 10, font: regularFont)), |
|
|
|
pw.SizedBox(width: 10), |
|
|
|
pw.Text( |
|
|
|
'-${totalCadeaux.toStringAsFixed(0)}', |
|
|
|
style: pw.TextStyle( |
|
|
|
color: PdfColors.green700, |
|
|
|
fontSize: 10)), |
|
|
|
pw.Text('-${totalCadeaux.toStringAsFixed(0)}', style: pw.TextStyle(color: PdfColors.green700, fontSize: 10, font: regularFont)), |
|
|
|
], |
|
|
|
), |
|
|
|
pw.SizedBox(height: 2), |
|
|
|
], |
|
|
|
pw.Container( |
|
|
|
width: 120, |
|
|
|
height: 1.5, |
|
|
|
color: PdfColors.black, |
|
|
|
margin: const pw.EdgeInsets.symmetric( |
|
|
|
vertical: 2)), |
|
|
|
|
|
|
|
pw.Container(width: 120, height: 1.5, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 2)), |
|
|
|
|
|
|
|
pw.Row( |
|
|
|
mainAxisAlignment: pw.MainAxisAlignment.end, |
|
|
|
children: [ |
|
|
|
pw.Text('TOTAL:', style: boldTextStyle), |
|
|
|
pw.SizedBox(width: 10), |
|
|
|
pw.Text( |
|
|
|
'${commande.montantTotal.toStringAsFixed(0)} MGA', |
|
|
|
style: boldTextStyle), |
|
|
|
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: 9, color: PdfColors.green700), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
pw.SizedBox(width: 15), |
|
|
|
|
|
|
|
// Informations vendeurs et signatures |
|
|
|
// Section vendeurs et signatures |
|
|
|
pw.Expanded( |
|
|
|
flex: 3, |
|
|
|
child: pw.Column( |
|
|
|
@ -823,48 +819,32 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
borderRadius: pw.BorderRadius.circular(3), |
|
|
|
), |
|
|
|
child: pw.Column( |
|
|
|
crossAxisAlignment: |
|
|
|
pw.CrossAxisAlignment.start, |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
|
|
children: [ |
|
|
|
pw.Text('VENDEURS', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 10, |
|
|
|
fontWeight: pw.FontWeight.bold)), |
|
|
|
pw.Text('VENDEURS', style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold, font: regularFont)), |
|
|
|
pw.SizedBox(height: 3), |
|
|
|
pw.Row( |
|
|
|
children: [ |
|
|
|
pw.Expanded( |
|
|
|
child: pw.Column( |
|
|
|
crossAxisAlignment: |
|
|
|
pw.CrossAxisAlignment.start, |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
|
|
children: [ |
|
|
|
pw.Text('Initiateur:', |
|
|
|
style: tinyTextStyle), |
|
|
|
pw.Text('Initiateur:', style: tinyTextStyle), |
|
|
|
pw.Text( |
|
|
|
commandeur != null |
|
|
|
? '${commandeur.name} ${commandeur.lastName ?? ''}' |
|
|
|
.trim() |
|
|
|
: 'N/A', |
|
|
|
style: |
|
|
|
pw.TextStyle(fontSize: 9), |
|
|
|
commandeur != null ? '${commandeur.name} ${commandeur.lastName ?? ''}'.trim() : 'N/A', |
|
|
|
style: pw.TextStyle(fontSize: 9, font: regularFont), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
pw.Expanded( |
|
|
|
child: pw.Column( |
|
|
|
crossAxisAlignment: |
|
|
|
pw.CrossAxisAlignment.start, |
|
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start, |
|
|
|
children: [ |
|
|
|
pw.Text('Validateur:', |
|
|
|
style: tinyTextStyle), |
|
|
|
pw.Text('Validateur:', style: tinyTextStyle), |
|
|
|
pw.Text( |
|
|
|
validateur != null |
|
|
|
? '${validateur.name} ${validateur.lastName ?? ''}' |
|
|
|
.trim() |
|
|
|
: 'N/A', |
|
|
|
style: |
|
|
|
pw.TextStyle(fontSize: 9), |
|
|
|
validateur != null ? '${validateur.name} ${validateur.lastName ?? ''}'.trim() : 'N/A', |
|
|
|
style: pw.TextStyle(fontSize: 9, font: regularFont), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
@ -879,33 +859,20 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
|
|
|
|
// Signatures |
|
|
|
pw.Row( |
|
|
|
mainAxisAlignment: |
|
|
|
pw.MainAxisAlignment.spaceBetween, |
|
|
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, |
|
|
|
children: [ |
|
|
|
pw.Column( |
|
|
|
children: [ |
|
|
|
pw.Text('Vendeur', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 9, |
|
|
|
fontWeight: pw.FontWeight.bold)), |
|
|
|
pw.Text('Vendeur', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, font: regularFont)), |
|
|
|
pw.SizedBox(height: 15), |
|
|
|
pw.Container( |
|
|
|
width: 70, |
|
|
|
height: 1, |
|
|
|
color: PdfColors.black), |
|
|
|
pw.Container(width: 70, height: 1, color: PdfColors.black), |
|
|
|
], |
|
|
|
), |
|
|
|
pw.Column( |
|
|
|
children: [ |
|
|
|
pw.Text('Client', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 9, |
|
|
|
fontWeight: pw.FontWeight.bold)), |
|
|
|
pw.Text('Client', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, font: regularFont)), |
|
|
|
pw.SizedBox(height: 15), |
|
|
|
pw.Container( |
|
|
|
width: 70, |
|
|
|
height: 1, |
|
|
|
color: PdfColors.black), |
|
|
|
pw.Container(width: 70, height: 1, color: PdfColors.black), |
|
|
|
], |
|
|
|
), |
|
|
|
], |
|
|
|
@ -916,7 +883,7 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
], |
|
|
|
), |
|
|
|
|
|
|
|
pw.SizedBox(height: 4), |
|
|
|
pw.SizedBox(height: 6), |
|
|
|
|
|
|
|
// Note finale |
|
|
|
pw.Text( |
|
|
|
@ -926,61 +893,70 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// PAGE EN MODE PAYSAGE : Les deux exemplaires sur une seule page |
|
|
|
// PAGE EN MODE PAYSAGE |
|
|
|
pdf.addPage( |
|
|
|
pw.Page( |
|
|
|
pageFormat: PdfPageFormat.a4.landscape, // Mode paysage |
|
|
|
pageFormat: PdfPageFormat.a4.landscape, |
|
|
|
margin: const pw.EdgeInsets.all(12), |
|
|
|
build: (pw.Context context) { |
|
|
|
return pw.Row( |
|
|
|
// Utilisation de Row au lieu de Column pour placer côte à côte |
|
|
|
children: [ |
|
|
|
// Premier exemplaire (CLIENT) |
|
|
|
pw.Expanded( |
|
|
|
child: buildExemplaire("CLIENT"), |
|
|
|
), |
|
|
|
|
|
|
|
pw.Expanded(child: buildExemplaire("CLIENT")), |
|
|
|
pw.SizedBox(width: 15), |
|
|
|
|
|
|
|
// Trait de séparation vertical |
|
|
|
// ✅ AMÉLIORATION: Remplacer les caractères Unicode par du texte simple |
|
|
|
pw.Container( |
|
|
|
width: 2, |
|
|
|
height: double.infinity, |
|
|
|
child: pw.Column( |
|
|
|
mainAxisAlignment: pw.MainAxisAlignment.center, |
|
|
|
children: [ |
|
|
|
pw.Text('✂️', style: pw.TextStyle(fontSize: 14)), |
|
|
|
pw.Container( |
|
|
|
width: 20, |
|
|
|
height: 20, |
|
|
|
decoration: pw.BoxDecoration( |
|
|
|
shape: pw.BoxShape.circle, |
|
|
|
border: pw.Border.all(color: PdfColors.black, width: 2), |
|
|
|
), |
|
|
|
child: pw.Center( |
|
|
|
child: pw.Text('X', style: pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold, font: regularFont)), |
|
|
|
), |
|
|
|
), |
|
|
|
pw.SizedBox(height: 10), |
|
|
|
pw.Transform.rotate( |
|
|
|
angle: 1.5708, // 90 degrés en radians (π/2) |
|
|
|
child: pw.Text('DÉCOUPER ICI', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 10, fontWeight: pw.FontWeight.bold)), |
|
|
|
angle: 1.5708, |
|
|
|
child: pw.Text('DÉCOUPER ICI', style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold, font: regularFont)), |
|
|
|
), |
|
|
|
pw.SizedBox(height: 10), |
|
|
|
pw.Text('✂️', style: pw.TextStyle(fontSize: 14)), |
|
|
|
pw.Container( |
|
|
|
width: 20, |
|
|
|
height: 20, |
|
|
|
decoration: pw.BoxDecoration( |
|
|
|
shape: pw.BoxShape.circle, |
|
|
|
border: pw.Border.all(color: PdfColors.black, width: 2), |
|
|
|
), |
|
|
|
child: pw.Center( |
|
|
|
child: pw.Text('X', style: pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold, font: regularFont)), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
pw.SizedBox(width: 15), |
|
|
|
|
|
|
|
// Deuxième exemplaire (MAGASIN) |
|
|
|
pw.Expanded( |
|
|
|
child: buildExemplaire("MAGASIN"), |
|
|
|
), |
|
|
|
pw.Expanded(child: buildExemplaire("MAGASIN")), |
|
|
|
], |
|
|
|
); |
|
|
|
}, |
|
|
|
), |
|
|
|
); |
|
|
|
|
|
|
|
print('=== RÉSULTAT FINAL ==='); |
|
|
|
print('PDF généré avec ${detailsAvecProduits.length} produits'); |
|
|
|
|
|
|
|
// Sauvegarder le PDF |
|
|
|
final output = await getTemporaryDirectory(); |
|
|
|
final file = File("${output.path}/bon_livraison_${commande.id}.pdf"); |
|
|
|
@ -2284,14 +2260,6 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> { |
|
|
|
textAlign: pw.TextAlign.center, |
|
|
|
), |
|
|
|
]), |
|
|
|
pw.Text( |
|
|
|
'$nombreCadeaux article(s) offert(s)', |
|
|
|
style: pw.TextStyle( |
|
|
|
fontSize: 6, |
|
|
|
color: PdfColors.green600, |
|
|
|
), |
|
|
|
textAlign: pw.TextAlign.center, |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
|