Browse Source

modification 2026

28062025_02
Stephane 3 weeks ago
parent
commit
58154af680
  1. 38
      lib/Components/commandManagementComponents/CommandDetails.dart
  2. 26
      lib/Models/Client.dart
  3. 71
      lib/Services/stock_managementDatabase.dart
  4. 426
      lib/Views/commandManagement.dart
  5. 28
      lib/Views/historique.dart

38
lib/Components/commandManagementComponents/CommandDetails.dart

@ -1,5 +1,3 @@
// Remplacez complètement votre fichier CommandeDetails par celui-ci :
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/client.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart';
@ -24,7 +22,8 @@ class CommandeDetails extends StatelessWidget {
); );
} }
Widget _buildTableCell(String text, {bool isAmount = false, Color? textColor}) { Widget _buildTableCell(String text,
{bool isAmount = false, Color? textColor}) {
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
@ -66,7 +65,9 @@ class CommandeDetails extends StatelessWidget {
), ),
); );
} else { } else {
return _buildTableCell('${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)} MGA', isAmount: true); return _buildTableCell(
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)} MGA',
isAmount: true);
} }
} }
@ -130,7 +131,9 @@ class CommandeDetails extends StatelessWidget {
), ),
); );
} else { } else {
return _buildTableCell('${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)} MGA', isAmount: true); return _buildTableCell(
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)} MGA',
isAmount: true);
} }
} }
@ -178,21 +181,27 @@ class CommandeDetails extends StatelessWidget {
children: [ children: [
Icon( Icon(
hasRemises ? Icons.discount : Icons.receipt_long, hasRemises ? Icons.discount : Icons.receipt_long,
color: hasRemises ? Colors.orange.shade700 : Colors.blue.shade700, color: hasRemises
? Colors.orange.shade700
: Colors.blue.shade700,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
hasRemises ? 'Détails de la commande (avec remises)' : 'Détails de la commande', hasRemises
? 'Détails de la commande (avec remises)'
: 'Détails de la commande',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 16, fontSize: 16,
color: hasRemises ? Colors.orange.shade800 : Colors.black87, color:
hasRemises ? Colors.orange.shade800 : Colors.black87,
), ),
), ),
if (hasRemises) ...[ if (hasRemises) ...[
const Spacer(), const Spacer(),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.orange.shade100, color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -255,6 +264,17 @@ class CommandeDetails extends StatelessWidget {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
if (detail.produitImei != null) ...[
const SizedBox(height: 2),
Text(
'IMEI: ${detail.produitImei}',
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
fontStyle: FontStyle.italic,
),
)
],
if (detail.aRemise) ...[ if (detail.aRemise) ...[
const SizedBox(height: 2), const SizedBox(height: 2),
Row( Row(

26
lib/Models/Client.dart

@ -53,7 +53,8 @@ class Client {
return DateTime.fromMillisecondsSinceEpoch(dateValue); return DateTime.fromMillisecondsSinceEpoch(dateValue);
} }
print("Type de date non reconnu: ${dateValue.runtimeType}, valeur: $dateValue"); print(
"Type de date non reconnu: ${dateValue.runtimeType}, valeur: $dateValue");
return DateTime.now(); return DateTime.now();
} }
@ -73,11 +74,7 @@ class Client {
String get nomComplet => '$prenom $nom'; String get nomComplet => '$prenom $nom';
} }
enum StatutCommande { enum StatutCommande { enAttente, confirmee, annulee }
enAttente,
confirmee,
annulee
}
class Commande { class Commande {
final int? id; final int? id;
@ -173,10 +170,7 @@ class Commande {
} }
// REMPLACEZ COMPLÈTEMENT votre classe DetailCommande dans Models/client.dart par celle-ci : // REMPLACEZ COMPLÈTEMENT votre classe DetailCommande dans Models/client.dart par celle-ci :
enum RemiseType { enum RemiseType { pourcentage, montant }
pourcentage,
montant
}
class DetailCommande { class DetailCommande {
final int? id; final int? id;
@ -193,6 +187,7 @@ class DetailCommande {
final String? produitNom; final String? produitNom;
final String? produitImage; final String? produitImage;
final String? produitReference; final String? produitReference;
final String? produitImei; // NOUVEAU : IMEI du produit, si applicable
DetailCommande({ DetailCommande({
this.id, this.id,
@ -209,6 +204,7 @@ class DetailCommande {
this.produitNom, this.produitNom,
this.produitImage, this.produitImage,
this.produitReference, this.produitReference,
this.produitImei,
}); });
// Constructeur pour créer un détail sans remise // Constructeur pour créer un détail sans remise
@ -222,6 +218,7 @@ class DetailCommande {
String? produitNom, String? produitNom,
String? produitImage, String? produitImage,
String? produitReference, String? produitReference,
String? produitImei,
}) { }) {
final sousTotal = quantite * prixUnitaire; final sousTotal = quantite * prixUnitaire;
final prixFinal = estCadeau ? 0.0 : sousTotal; final prixFinal = estCadeau ? 0.0 : sousTotal;
@ -238,6 +235,7 @@ class DetailCommande {
produitNom: produitNom, produitNom: produitNom,
produitImage: produitImage, produitImage: produitImage,
produitReference: produitReference, produitReference: produitReference,
produitImei: produitImei,
); );
} }
@ -251,6 +249,7 @@ class DetailCommande {
String? produitNom, String? produitNom,
String? produitImage, String? produitImage,
String? produitReference, String? produitReference,
String? produitImei,
}) { }) {
return DetailCommande( return DetailCommande(
id: id, id: id,
@ -264,6 +263,7 @@ class DetailCommande {
produitNom: produitNom, produitNom: produitNom,
produitImage: produitImage, produitImage: produitImage,
produitReference: produitReference, produitReference: produitReference,
produitImei: produitImei,
); );
} }
@ -301,6 +301,7 @@ class DetailCommande {
produitNom: produitNom, produitNom: produitNom,
produitImage: produitImage, produitImage: produitImage,
produitReference: produitReference, produitReference: produitReference,
produitImei: produitImei,
); );
} }
@ -321,6 +322,7 @@ class DetailCommande {
produitNom: produitNom, produitNom: produitNom,
produitImage: produitImage, produitImage: produitImage,
produitReference: produitReference, produitReference: produitReference,
produitImei: produitImei,
); );
} }
@ -341,6 +343,7 @@ class DetailCommande {
produitNom: produitNom, produitNom: produitNom,
produitImage: produitImage, produitImage: produitImage,
produitReference: produitReference, produitReference: produitReference,
produitImei: produitImei,
); );
} }
@ -361,11 +364,13 @@ class DetailCommande {
produitNom: produitNom, produitNom: produitNom,
produitImage: produitImage, produitImage: produitImage,
produitReference: produitReference, produitReference: produitReference,
produitImei: produitImei,
); );
} }
// Getters utiles // Getters utiles
bool get aRemise => remiseType != null && montantRemise > 0 && !estCadeau; bool get aRemise => remiseType != null && montantRemise > 0 && !estCadeau;
bool get aimei => produitImei != null;
double get pourcentageRemise { double get pourcentageRemise {
if (!aRemise) return 0.0; if (!aRemise) return 0.0;
@ -432,6 +437,7 @@ class DetailCommande {
produitNom: map['produitNom'] as String?, produitNom: map['produitNom'] as String?,
produitImage: map['produitImage'] as String?, produitImage: map['produitImage'] as String?,
produitReference: map['produitReference'] as String?, produitReference: map['produitReference'] as String?,
produitImei: map['produitImei'] as String?,
); );
} }
} }

71
lib/Services/stock_managementDatabase.dart

@ -47,7 +47,6 @@ class AppDatabase {
await insertDefaultPointsDeVente(); await insertDefaultPointsDeVente();
} }
String _formatDate(DateTime date) { String _formatDate(DateTime date) {
return DateFormat('yyyy-MM-dd HH:mm:ss').format(date); return DateFormat('yyyy-MM-dd HH:mm:ss').format(date);
} }
@ -608,6 +607,7 @@ String _formatDate(DateTime date) {
return []; return [];
} }
} }
Future<double> getValeurTotaleStock() async { Future<double> getValeurTotaleStock() async {
final db = await database; final db = await database;
@ -624,7 +624,6 @@ Future<double> getValeurTotaleStock() async {
} }
} }
// --- STATISTIQUES --- // --- STATISTIQUES ---
Future<Map<String, dynamic>> getStatistiques() async { Future<Map<String, dynamic>> getStatistiques() async {
@ -914,7 +913,8 @@ Future<double> getValeurTotaleStock() async {
dc.*, dc.*,
p.name as produitNom, p.name as produitNom,
p.image as produitImage, p.image as produitImage,
p.reference as produitReference p.reference as produitReference,
p.imei as produitImei
FROM details_commandes dc FROM details_commandes dc
LEFT JOIN products p ON dc.produitId = p.id LEFT JOIN products p ON dc.produitId = p.id
WHERE dc.commandeId = ? WHERE dc.commandeId = ?
@ -923,6 +923,7 @@ Future<double> getValeurTotaleStock() async {
return result.map((row) => DetailCommande.fromMap(row.fields)).toList(); return result.map((row) => DetailCommande.fromMap(row.fields)).toList();
} }
Future<List<Map<String, dynamic>>> getCommandesParPointDeVente( Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
int pointVenteId, { int pointVenteId, {
DateTime? dateDebut, DateTime? dateDebut,
@ -939,7 +940,8 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
if (aujourdHuiSeulement) { if (aujourdHuiSeulement) {
final today = DateTime.now(); final today = DateTime.now();
final startOfDay = DateTime(today.year, today.month, today.day); final startOfDay = DateTime(today.year, today.month, today.day);
final endOfDay = DateTime(today.year, today.month, today.day, 23, 59, 59); final endOfDay =
DateTime(today.year, today.month, today.day, 23, 59, 59);
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?'; whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
whereArgs.addAll([ whereArgs.addAll([
@ -947,7 +949,8 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
_formatDate(endOfDay), _formatDate(endOfDay),
]); ]);
} else if (dateDebut != null && dateFin != null) { } else if (dateDebut != null && dateFin != null) {
final adjustedEndDate = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59); final adjustedEndDate =
DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?'; whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
whereArgs.addAll([ whereArgs.addAll([
_formatDate(dateDebut), _formatDate(dateDebut),
@ -957,7 +960,8 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
whereClause += ' AND c.dateCommande >= ?'; whereClause += ' AND c.dateCommande >= ?';
whereArgs.add(_formatDate(dateDebut)); whereArgs.add(_formatDate(dateDebut));
} else if (dateFin != null) { } else if (dateFin != null) {
final adjustedEndDate = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59); final adjustedEndDate =
DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
whereClause += ' AND c.dateCommande <= ?'; whereClause += ' AND c.dateCommande <= ?';
whereArgs.add(_formatDate(adjustedEndDate)); whereArgs.add(_formatDate(adjustedEndDate));
} }
@ -1026,7 +1030,8 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
Future<List<Map<String, dynamic>>> getPointsDeVentes() async { Future<List<Map<String, dynamic>>> getPointsDeVentes() async {
final db = await database; final db = await database;
final result = await db.query('SELECT DISTINCT * FROM pointsdevente ORDER BY nom ASC'); final result =
await db.query('SELECT DISTINCT * FROM pointsdevente ORDER BY nom ASC');
return result.map((row) => row.fields).toList(); return result.map((row) => row.fields).toList();
} }
@ -1241,7 +1246,6 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
return result.affectedRows!; return result.affectedRows!;
} }
// Future<int> deletePointDeVente(int id) async { // Future<int> deletePointDeVente(int id) async {
// final db = await database; // final db = await database;
// final result = await db.query('DELETE FROM points_de_vente WHERE id = ?', [id]); // final result = await db.query('DELETE FROM points_de_vente WHERE id = ?', [id]);
@ -1435,6 +1439,7 @@ Future<List<Map<String, dynamic>>> getCommandesParPointDeVente(
await db.query('SELECT * FROM points_de_vente WHERE id = ?', [id]); await db.query('SELECT * FROM points_de_vente WHERE id = ?', [id]);
return result.isNotEmpty ? result.first : null; return result.isNotEmpty ? result.first : null;
} }
List<String> parseHeaderInfo(dynamic blobData) { List<String> parseHeaderInfo(dynamic blobData) {
if (blobData == null) return []; if (blobData == null) return [];
@ -1474,9 +1479,6 @@ List<String> parseHeaderInfo(dynamic blobData) {
} }
} }
Future<int?> getOrCreatePointDeVenteByNom(String nom) async { Future<int?> getOrCreatePointDeVenteByNom(String nom) async {
final db = await database; final db = await database;
@ -2018,7 +2020,6 @@ Future<int> updateStatutCommande(
} }
} }
Future<List<Commande>> getCommandesByClient(int clientId) async { Future<List<Commande>> getCommandesByClient(int clientId) async {
final db = await database; final db = await database;
final result = await db.query(''' final result = await db.query('''
@ -2557,6 +2558,7 @@ Future<int> updateStatutCommande(
return erreurs; return erreurs;
} }
// --- MÉTHODES POUR LES VENTES PAR POINT DE VENTE --- // --- MÉTHODES POUR LES VENTES PAR POINT DE VENTE ---
Future<List<Map<String, dynamic>>> getVentesParPointDeVente({ Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
DateTime? dateDebut, DateTime? dateDebut,
@ -2573,7 +2575,8 @@ Future<List<Map<String, dynamic>>> getVentesParPointDeVente({
if (aujourdHuiSeulement == true) { if (aujourdHuiSeulement == true) {
final today = DateTime.now(); final today = DateTime.now();
final startOfDay = DateTime(today.year, today.month, today.day); final startOfDay = DateTime(today.year, today.month, today.day);
final endOfDay = DateTime(today.year, today.month, today.day, 23, 59, 59); final endOfDay =
DateTime(today.year, today.month, today.day, 23, 59, 59);
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?'; whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
whereArgs.addAll([ whereArgs.addAll([
@ -2633,7 +2636,8 @@ Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
if (aujourdHuiSeulement == true) { if (aujourdHuiSeulement == true) {
final today = DateTime.now(); final today = DateTime.now();
final startOfDay = DateTime(today.year, today.month, today.day); final startOfDay = DateTime(today.year, today.month, today.day);
final endOfDay = DateTime(today.year, today.month, today.day, 23, 59, 59); final endOfDay =
DateTime(today.year, today.month, today.day, 23, 59, 59);
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?'; whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
whereArgs.addAll([ whereArgs.addAll([
@ -2641,7 +2645,8 @@ Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
_formatDate(endOfDay), _formatDate(endOfDay),
]); ]);
} else if (dateDebut != null && dateFin != null) { } else if (dateDebut != null && dateFin != null) {
final adjustedEndDate = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59); final adjustedEndDate =
DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?'; whereClause += ' AND c.dateCommande >= ? AND c.dateCommande <= ?';
whereArgs.addAll([ whereArgs.addAll([
_formatDate(dateDebut), _formatDate(dateDebut),
@ -3374,7 +3379,6 @@ Future<List<Map<String, dynamic>>> getTopProduitsParPointDeVente(
} }
} }
// 1. Mise à jour de la méthode dans stock_managementDatabase.dart // 1. Mise à jour de la méthode dans stock_managementDatabase.dart
Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
int? adminId, int? adminId,
@ -3398,12 +3402,14 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
} }
if (adminId != null) { if (adminId != null) {
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.admin_id = ?'; whereClause +=
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.admin_id = ?';
params.add(adminId); params.add(adminId);
} }
if (statut != null) { if (statut != null) {
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.statut = ?'; whereClause +=
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.statut = ?';
params.add(statut); params.add(statut);
} }
@ -3411,27 +3417,34 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
if (aujourdHuiSeulement) { if (aujourdHuiSeulement) {
final today = DateTime.now(); final today = DateTime.now();
final startOfDay = DateTime(today.year, today.month, today.day); final startOfDay = DateTime(today.year, today.month, today.day);
final endOfDay = DateTime(today.year, today.month, today.day, 23, 59, 59); final endOfDay =
DateTime(today.year, today.month, today.day, 23, 59, 59);
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') +
' sp.date_sortie >= ? AND sp.date_sortie <= ?'; ' sp.date_sortie >= ? AND sp.date_sortie <= ?';
params.add(startOfDay.toIso8601String()); params.add(startOfDay.toIso8601String());
params.add(endOfDay.toIso8601String()); params.add(endOfDay.toIso8601String());
} else if (dateDebut != null && dateFin != null) { } else if (dateDebut != null && dateFin != null) {
final startOfDay = DateTime(dateDebut.year, dateDebut.month, dateDebut.day); final startOfDay =
final endOfDay = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59); DateTime(dateDebut.year, dateDebut.month, dateDebut.day);
final endOfDay =
DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') +
' sp.date_sortie >= ? AND sp.date_sortie <= ?'; ' sp.date_sortie >= ? AND sp.date_sortie <= ?';
params.add(startOfDay.toIso8601String()); params.add(startOfDay.toIso8601String());
params.add(endOfDay.toIso8601String()); params.add(endOfDay.toIso8601String());
} else if (dateDebut != null) { } else if (dateDebut != null) {
final startOfDay = DateTime(dateDebut.year, dateDebut.month, dateDebut.day); final startOfDay =
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.date_sortie >= ?'; DateTime(dateDebut.year, dateDebut.month, dateDebut.day);
whereClause +=
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.date_sortie >= ?';
params.add(startOfDay.toIso8601String()); params.add(startOfDay.toIso8601String());
} else if (dateFin != null) { } else if (dateFin != null) {
final endOfDay = DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59); final endOfDay =
whereClause += (whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.date_sortie <= ?'; DateTime(dateFin.year, dateFin.month, dateFin.day, 23, 59, 59);
whereClause +=
(whereClause.isEmpty ? 'WHERE' : ' AND') + ' sp.date_sortie <= ?';
params.add(endOfDay.toIso8601String()); params.add(endOfDay.toIso8601String());
} }
@ -3460,6 +3473,7 @@ Future<List<Map<String, dynamic>>> getHistoriqueSortiesPersonnelles({
return []; return [];
} }
} }
Future<void> updatePointDeVentes( Future<void> updatePointDeVentes(
int id, int id,
String nom, String nom,
@ -3498,10 +3512,6 @@ Future<void> updatePointDeVentes(
} }
} }
Future<Map<String, dynamic>> getStatistiquesSortiesPersonnelles() async { Future<Map<String, dynamic>> getStatistiquesSortiesPersonnelles() async {
final db = await database; final db = await database;
@ -3561,5 +3571,4 @@ Future<void> updatePointDeVentes(
} }
} }
class _formatDate { class _formatDate {}
}

426
lib/Views/commandManagement.dart

@ -189,7 +189,8 @@ class _GestionCommandesPageState extends State<GestionCommandesPage> {
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text('Montant total: ${NumberFormat('#,##0', 'fr_FR').format(montantFinal)} MGA'), Text(
'Montant total: ${NumberFormat('#,##0', 'fr_FR').format(montantFinal)} MGA'),
const SizedBox(height: 10), const SizedBox(height: 10),
TextField( TextField(
controller: amountController, controller: amountController,
@ -293,7 +294,6 @@ Future<void> _generateBonLivraison(Commande commande) async {
} }
final pointDeVente = pointDeVenteComplet; final pointDeVente = pointDeVenteComplet;
// Récupérer les informations des vendeurs // Récupérer les informations des vendeurs
final commandeur = commande.commandeurId != null final commandeur = commande.commandeurId != null
? await _database.getUserById(commande.commandeurId!) ? await _database.getUserById(commande.commandeurId!)
@ -318,7 +318,6 @@ Future<void> _generateBonLivraison(Commande commande) async {
print('==============================='); print('===============================');
} }
// Infos par défaut si aucune info personnalisée // Infos par défaut si aucune info personnalisée
final infosLivraisonDefaut = [ final infosLivraisonDefaut = [
'REMAX Andravoangy', 'REMAX Andravoangy',
@ -394,8 +393,10 @@ Future<void> _generateBonLivraison(Commande commande) async {
pw.Font? regularFont; pw.Font? regularFont;
try { try {
italicFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Italic.ttf')); italicFont =
regularFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Regular.ttf')); 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) { } catch (e) {
print('⚠️ Impossible de charger les polices personnalisées: $e'); print('⚠️ Impossible de charger les polices personnalisées: $e');
} }
@ -404,11 +405,19 @@ Future<void> _generateBonLivraison(Commande commande) async {
final tinyTextStyle = pw.TextStyle(fontSize: 9, font: regularFont); final tinyTextStyle = pw.TextStyle(fontSize: 9, font: regularFont);
final smallTextStyle = pw.TextStyle(fontSize: 10, font: regularFont); final smallTextStyle = pw.TextStyle(fontSize: 10, font: regularFont);
final normalTextStyle = pw.TextStyle(fontSize: 11, font: regularFont); final normalTextStyle = pw.TextStyle(fontSize: 11, font: regularFont);
final boldTextStyle = pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold, font: regularFont); final boldTextStyle = pw.TextStyle(
final boldClientStyle = pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold, font: regularFont); 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 frameTextStyle = pw.TextStyle(fontSize: 10, font: regularFont);
final italicTextStyle = pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, font: italicFont ?? regularFont); final italicTextStyle = pw.TextStyle(
final italicLogoStyle = pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold, font: italicFont ?? regularFont); fontSize: 9,
fontWeight: pw.FontWeight.bold,
font: italicFont ?? regularFont);
final italicLogoStyle = pw.TextStyle(
fontSize: 8,
fontWeight: pw.FontWeight.bold,
font: italicFont ?? regularFont);
Future<pw.Widget> buildLogoWidget() async { Future<pw.Widget> buildLogoWidget() async {
final logoRaw = pointDeVenteComplet?['logo']; final logoRaw = pointDeVenteComplet?['logo'];
@ -424,7 +433,8 @@ Future<void> _generateBonLivraison(Commande commande) async {
dynamic blobDynamic = logoRaw; dynamic blobDynamic = logoRaw;
bytes = blobDynamic.toBytes(); bytes = blobDynamic.toBytes();
} else { } else {
throw Exception("Format de logo non supporté: ${logoRaw.runtimeType}"); throw Exception(
"Format de logo non supporté: ${logoRaw.runtimeType}");
} }
final imageLogo = pw.MemoryImage(bytes); final imageLogo = pw.MemoryImage(bytes);
@ -436,12 +446,12 @@ Future<void> _generateBonLivraison(Commande commande) async {
return pw.Image(image, width: 100, height: 100); return pw.Image(image, width: 100, height: 100);
} }
final logoWidget = await buildLogoWidget(); final logoWidget = await buildLogoWidget();
// FONCTION POUR CONSTRUIRE L'EN-TÊTE DYNAMIQUE // FONCTION POUR CONSTRUIRE L'EN-TÊTE DYNAMIQUE
pw.Widget buildEnteteInfos() { pw.Widget buildEnteteInfos() {
final infosAUtiliser = infosLivraison.isNotEmpty ? infosLivraison : infosLivraisonDefaut; final infosAUtiliser =
infosLivraison.isNotEmpty ? infosLivraison : infosLivraisonDefaut;
return pw.Column( return pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
@ -469,7 +479,9 @@ final logoWidget = await buildLogoWidget();
width: double.infinity, width: double.infinity,
padding: const pw.EdgeInsets.all(5), padding: const pw.EdgeInsets.all(5),
decoration: pw.BoxDecoration( decoration: pw.BoxDecoration(
color: typeExemplaire == "CLIENT" ? PdfColors.blue100 : PdfColors.green100, color: typeExemplaire == "CLIENT"
? PdfColors.blue100
: PdfColors.green100,
), ),
child: pw.Center( child: pw.Center(
child: pw.Text( child: pw.Text(
@ -477,7 +489,9 @@ final logoWidget = await buildLogoWidget();
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
color: typeExemplaire == "CLIENT" ? PdfColors.blue800 : PdfColors.green800, color: typeExemplaire == "CLIENT"
? PdfColors.blue800
: PdfColors.green800,
font: regularFont, font: regularFont,
), ),
), ),
@ -500,7 +514,8 @@ final logoWidget = await buildLogoWidget();
children: [ children: [
logoWidget, logoWidget,
pw.SizedBox(height: 3), 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.SizedBox(height: 4),
buildEnteteInfos(), // EN-TÊTE DYNAMIQUE ICI buildEnteteInfos(), // EN-TÊTE DYNAMIQUE ICI
], ],
@ -510,9 +525,12 @@ final logoWidget = await buildLogoWidget();
pw.Column( pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.center, crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [ 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.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.SizedBox(height: 4),
pw.Container( pw.Container(
padding: const pw.EdgeInsets.all(6), padding: const pw.EdgeInsets.all(6),
@ -522,10 +540,13 @@ final logoWidget = await buildLogoWidget();
child: pw.Column( child: pw.Column(
children: [ children: [
pw.Text('Boutique:', style: frameTextStyle), pw.Text('Boutique:', style: frameTextStyle),
pw.Text('${pointDeVente?['nom'] ?? 'S405A'}', style: boldTextStyle), pw.Text('${pointDeVente?['nom'] ?? 'S405A'}',
style: boldTextStyle),
pw.SizedBox(height: 2), pw.SizedBox(height: 2),
pw.Text('Bon N°:', style: frameTextStyle), 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),
], ],
), ),
), ),
@ -536,7 +557,8 @@ final logoWidget = await buildLogoWidget();
pw.Container( pw.Container(
width: 120, width: 120,
decoration: pw.BoxDecoration( 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), padding: const pw.EdgeInsets.all(6),
child: pw.Column( child: pw.Column(
@ -544,11 +566,20 @@ final logoWidget = await buildLogoWidget();
children: [ children: [
pw.Text('CLIENT', style: frameTextStyle), pw.Text('CLIENT', style: frameTextStyle),
pw.SizedBox(height: 2), pw.SizedBox(height: 2),
pw.Text('ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}', style: smallTextStyle), pw.Text(
pw.Container(width: 100, height: 1, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 2)), 'ID: ${pointDeVente?['nom'] ?? 'S405A'}-${client?.id ?? 'Non spécifié'}',
pw.Text('${client?.nom} ${client?.prenom}', style: boldTextStyle), style: smallTextStyle),
pw.Container(
width: 100,
height: 1,
color: PdfColors.black,
margin:
const pw.EdgeInsets.symmetric(vertical: 2)),
pw.Text('${client?.nom} ${client?.prenom}',
style: boldTextStyle),
pw.SizedBox(height: 2), pw.SizedBox(height: 2),
pw.Text(client?.telephone ?? 'Non spécifié', style: tinyTextStyle), pw.Text(client?.telephone ?? 'Non spécifié',
style: tinyTextStyle),
], ],
), ),
), ),
@ -562,7 +593,10 @@ final logoWidget = await buildLogoWidget();
children: [ children: [
// Debug: Afficher le nombre d'articles // Debug: Afficher le nombre d'articles
pw.Text('Articles trouvés: ${detailsAvecProduits.length}', pw.Text('Articles trouvés: ${detailsAvecProduits.length}',
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey, font: regularFont)), style: pw.TextStyle(
fontSize: 8,
color: PdfColors.grey,
font: regularFont)),
pw.SizedBox(height: 5), pw.SizedBox(height: 5),
// TABLE SANS CONTRAINTE DE HAUTEUR - Elle s'adapte au contenu // TABLE SANS CONTRAINTE DE HAUTEUR - Elle s'adapte au contenu
@ -577,24 +611,28 @@ final logoWidget = await buildLogoWidget();
children: [ children: [
// En-tête du tableau // En-tête du tableau
pw.TableRow( pw.TableRow(
decoration: const pw.BoxDecoration(color: PdfColors.grey200), decoration: const pw.BoxDecoration(
color: PdfColors.grey200),
children: [ children: [
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(4), padding: const pw.EdgeInsets.all(4),
child: pw.Text('Désignations', style: boldTextStyle) child: pw.Text('Désignations',
), style: boldTextStyle)),
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(4), padding: const pw.EdgeInsets.all(4),
child: pw.Text('Qté', style: boldTextStyle, textAlign: pw.TextAlign.center) child: pw.Text('Qté',
), style: boldTextStyle,
textAlign: pw.TextAlign.center)),
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(4), padding: const pw.EdgeInsets.all(4),
child: pw.Text('P.U.', style: boldTextStyle, textAlign: pw.TextAlign.right) child: pw.Text('P.U.',
), style: boldTextStyle,
textAlign: pw.TextAlign.right)),
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(4), padding: const pw.EdgeInsets.all(4),
child: pw.Text('Montant', style: boldTextStyle, textAlign: pw.TextAlign.right) child: pw.Text('Montant',
), style: boldTextStyle,
textAlign: pw.TextAlign.right)),
], ],
), ),
@ -606,22 +644,27 @@ final logoWidget = await buildLogoWidget();
final produit = item['produit']; final produit = item['produit'];
// Debug pour chaque ligne // Debug pour chaque ligne
print('📋 Ligne PDF $index: ${detail.produitNom} (Quantité: ${detail.quantite})'); print(
'📋 Ligne PDF $index: ${detail.produitNom} (Quantité: ${detail.quantite})');
return pw.TableRow( return pw.TableRow(
decoration: detail.estCadeau decoration: detail.estCadeau
? const pw.BoxDecoration(color: PdfColors.green50) ? const pw.BoxDecoration(
color: PdfColors.green50)
: detail.aRemise : detail.aRemise
? const pw.BoxDecoration(color: PdfColors.orange50) ? const pw.BoxDecoration(
color: PdfColors.orange50)
: index % 2 == 0 : index % 2 == 0
? const pw.BoxDecoration(color: PdfColors.grey50) ? const pw.BoxDecoration(
color: PdfColors.grey50)
: null, : null,
children: [ children: [
// Colonne Désignations - Plus compacte // Colonne Désignations - Plus compacte
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(4), padding: const pw.EdgeInsets.all(4),
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment:
pw.CrossAxisAlignment.start,
mainAxisSize: pw.MainAxisSize.min, mainAxisSize: pw.MainAxisSize.min,
children: [ children: [
// Nom du produit avec badge // Nom du produit avec badge
@ -632,27 +675,28 @@ final logoWidget = await buildLogoWidget();
'${detail.produitNom ?? 'Produit inconnu'}', '${detail.produitNom ?? 'Produit inconnu'}',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 10, fontSize: 10,
fontWeight: pw.FontWeight.bold, fontWeight:
font: regularFont pw.FontWeight.bold,
) font: regularFont)),
),
), ),
if (detail.estCadeau) if (detail.estCadeau)
pw.Container( pw.Container(
padding: const pw.EdgeInsets.symmetric(horizontal: 3, vertical: 1), padding:
const pw.EdgeInsets.symmetric(
horizontal: 3,
vertical: 1),
decoration: pw.BoxDecoration( decoration: pw.BoxDecoration(
color: PdfColors.green600, color: PdfColors.green600,
borderRadius: pw.BorderRadius.circular(3), borderRadius:
pw.BorderRadius.circular(3),
), ),
child: pw.Text( child: pw.Text('CADEAU',
'CADEAU',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 6, fontSize: 6,
color: PdfColors.white, color: PdfColors.white,
font: regularFont, font: regularFont,
fontWeight: pw.FontWeight.bold fontWeight:
) pw.FontWeight.bold)),
),
), ),
], ],
), ),
@ -662,22 +706,47 @@ final logoWidget = await buildLogoWidget();
// Informations complémentaires sur une seule ligne // Informations complémentaires sur une seule ligne
pw.Text( pw.Text(
[ [
if (produit?.category?.isNotEmpty == true) produit!.category, if (produit?.category?.isNotEmpty ==
if (produit?.marque?.isNotEmpty == true) produit!.marque, true)
if (produit?.imei?.isNotEmpty == true) 'IMEI: ${produit!.imei}', produit!.category,
].where((info) => info != null).join(' , '), if (produit?.marque?.isNotEmpty ==
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey700, font: regularFont), 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 // Spécifications techniques
if (produit?.ram?.isNotEmpty == true || produit?.memoireInterne?.isNotEmpty == true || produit?.reference?.isNotEmpty == true) if (produit?.ram?.isNotEmpty == true ||
produit?.memoireInterne?.isNotEmpty ==
true ||
produit?.reference?.isNotEmpty ==
true)
pw.Text( pw.Text(
[ [
if (produit?.ram?.isNotEmpty == true) 'RAM: ${produit!.ram}', if (produit?.ram?.isNotEmpty ==
if (produit?.memoireInterne?.isNotEmpty == true) 'Stockage: ${produit!.memoireInterne}', true)
if (produit?.reference?.isNotEmpty == true) 'Ref: ${produit!.reference}', 'RAM: ${produit!.ram}',
if (produit?.memoireInterne
?.isNotEmpty ==
true)
'Stockage: ${produit!.memoireInterne}',
if (produit
?.reference?.isNotEmpty ==
true)
'Ref: ${produit!.reference}',
].join(' , '), ].join(' , '),
style: pw.TextStyle(fontSize: 8, color: PdfColors.grey600, font: regularFont), style: pw.TextStyle(
fontSize: 8,
color: PdfColors.grey600,
font: regularFont),
), ),
], ],
), ),
@ -686,18 +755,17 @@ final logoWidget = await buildLogoWidget();
// Colonne Quantité // Colonne Quantité
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(4), padding: const pw.EdgeInsets.all(4),
child: pw.Text( child: pw.Text('${detail.quantite}',
'${detail.quantite}',
style: normalTextStyle, style: normalTextStyle,
textAlign: pw.TextAlign.center textAlign: pw.TextAlign.center),
),
), ),
// Colonne Prix Unitaire // Colonne Prix Unitaire
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(4), padding: const pw.EdgeInsets.all(4),
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end, crossAxisAlignment:
pw.CrossAxisAlignment.end,
mainAxisSize: pw.MainAxisSize.min, mainAxisSize: pw.MainAxisSize.min,
children: [ children: [
if (detail.estCadeau) ...[ if (detail.estCadeau) ...[
@ -705,44 +773,37 @@ final logoWidget = await buildLogoWidget();
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)}', '${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)}',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 8, fontSize: 8,
decoration: pw.TextDecoration.lineThrough, decoration: pw
.TextDecoration.lineThrough,
color: PdfColors.grey600, color: PdfColors.grey600,
font: regularFont font: regularFont)),
) pw.Text('GRATUIT',
),
pw.Text(
'GRATUIT',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 9, fontSize: 9,
color: PdfColors.green700, color: PdfColors.green700,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
font: regularFont font: regularFont)),
)
),
] else if (detail.aRemise) ...[ ] else if (detail.aRemise) ...[
pw.Text( pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)}', '${NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire)}',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 8, fontSize: 8,
decoration: pw.TextDecoration.lineThrough, decoration: pw
.TextDecoration.lineThrough,
color: PdfColors.grey600, color: PdfColors.grey600,
font: regularFont font: regularFont)),
)
),
pw.Text( pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal / detail.quantite)}', '${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal / detail.quantite)}',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 10, fontSize: 10,
color: PdfColors.orange700, color: PdfColors.orange700,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
font: regularFont font: regularFont)),
)
),
] else ] else
pw.Text( pw.Text(
NumberFormat('#,##0', 'fr_FR').format(detail.prixUnitaire), NumberFormat('#,##0', 'fr_FR')
style: smallTextStyle .format(detail.prixUnitaire),
), style: smallTextStyle),
], ],
), ),
), ),
@ -751,51 +812,45 @@ final logoWidget = await buildLogoWidget();
pw.Padding( pw.Padding(
padding: const pw.EdgeInsets.all(4), padding: const pw.EdgeInsets.all(4),
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end, crossAxisAlignment:
pw.CrossAxisAlignment.end,
mainAxisSize: pw.MainAxisSize.min, mainAxisSize: pw.MainAxisSize.min,
children: [ children: [
if (detail.estCadeau) ...[ if (detail.estCadeau) ...[
pw.Text( pw.Text(
NumberFormat('#,##0', 'fr_FR').format(detail.sousTotal), NumberFormat('#,##0', 'fr_FR')
.format(detail.sousTotal),
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 8, fontSize: 8,
decoration: pw.TextDecoration.lineThrough, decoration: pw
.TextDecoration.lineThrough,
color: PdfColors.grey600, color: PdfColors.grey600,
font: regularFont font: regularFont)),
) pw.Text('GRATUIT',
),
pw.Text(
'GRATUIT',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 9, fontSize: 9,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
color: PdfColors.green700, color: PdfColors.green700,
font: regularFont font: regularFont)),
)
),
] else if (detail.aRemise) ...[ ] else if (detail.aRemise) ...[
pw.Text( pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(detail.sousTotal)}', '${NumberFormat('#,##0', 'fr_FR').format(detail.sousTotal)}',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 8, fontSize: 8,
decoration: pw.TextDecoration.lineThrough, decoration: pw
.TextDecoration.lineThrough,
color: PdfColors.grey600, color: PdfColors.grey600,
font: regularFont font: regularFont)),
)
),
pw.Text( pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)}', '${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)}',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 10, fontSize: 10,
fontWeight: pw.FontWeight.bold, fontWeight: pw.FontWeight.bold,
font: regularFont font: regularFont)),
)
),
] else ] else
pw.Text( pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)}', '${NumberFormat('#,##0', 'fr_FR').format(detail.prixFinal)}',
style: smallTextStyle style: smallTextStyle),
),
], ],
), ),
), ),
@ -825,44 +880,67 @@ final logoWidget = await buildLogoWidget();
children: [ children: [
pw.Text('SOUS-TOTAL:', style: smallTextStyle), pw.Text('SOUS-TOTAL:', style: smallTextStyle),
pw.SizedBox(width: 10), pw.SizedBox(width: 10),
pw.Text('${NumberFormat('#,##0', 'fr_FR').format(sousTotal)}', style: smallTextStyle), pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(sousTotal)}',
style: smallTextStyle),
], ],
), ),
pw.SizedBox(height: 2), pw.SizedBox(height: 2),
], ],
if (totalRemises > 0) ...[ if (totalRemises > 0) ...[
pw.Row( pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.end, mainAxisAlignment: pw.MainAxisAlignment.end,
children: [ children: [
pw.Text('REMISES:', style: pw.TextStyle(color: PdfColors.orange, fontSize: 10, font: regularFont)), pw.Text('REMISES:',
style: pw.TextStyle(
color: PdfColors.orange,
fontSize: 10,
font: regularFont)),
pw.SizedBox(width: 10), pw.SizedBox(width: 10),
pw.Text('-${NumberFormat('#,##0', 'fr_FR').format(totalRemises)}', style: pw.TextStyle(color: PdfColors.orange, fontSize: 10, font: regularFont)), pw.Text(
'-${NumberFormat('#,##0', 'fr_FR').format(totalRemises)}',
style: pw.TextStyle(
color: PdfColors.orange,
fontSize: 10,
font: regularFont)),
], ],
), ),
pw.SizedBox(height: 2), pw.SizedBox(height: 2),
], ],
if (totalCadeaux > 0) ...[ if (totalCadeaux > 0) ...[
pw.Row( pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.end, mainAxisAlignment: pw.MainAxisAlignment.end,
children: [ children: [
pw.Text('CADEAUX ($nombreCadeaux):', style: pw.TextStyle(color: PdfColors.green700, fontSize: 10, font: regularFont)), pw.Text('CADEAUX ($nombreCadeaux):',
style: pw.TextStyle(
color: PdfColors.green700,
fontSize: 10,
font: regularFont)),
pw.SizedBox(width: 10), pw.SizedBox(width: 10),
pw.Text('-${NumberFormat('#,##0', 'fr_FR').format(totalCadeaux)}', style: pw.TextStyle(color: PdfColors.green700, fontSize: 10, font: regularFont)), pw.Text(
'-${NumberFormat('#,##0', 'fr_FR').format(totalCadeaux)}',
style: pw.TextStyle(
color: PdfColors.green700,
fontSize: 10,
font: regularFont)),
], ],
), ),
pw.SizedBox(height: 2), pw.SizedBox(height: 2),
], ],
pw.Container(
pw.Container(width: 120, height: 1.5, color: PdfColors.black, margin: const pw.EdgeInsets.symmetric(vertical: 2)), width: 120,
height: 1.5,
color: PdfColors.black,
margin:
const pw.EdgeInsets.symmetric(vertical: 2)),
pw.Row( pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.end, mainAxisAlignment: pw.MainAxisAlignment.end,
children: [ children: [
pw.Text('TOTAL:', style: boldTextStyle), pw.Text('TOTAL:', style: boldTextStyle),
pw.SizedBox(width: 10), pw.SizedBox(width: 10),
pw.Text('${NumberFormat('#,##0', 'fr_FR').format(commande.montantTotal)} MGA', style: boldTextStyle), pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(commande.montantTotal)} MGA',
style: boldTextStyle),
], ],
), ),
], ],
@ -887,30 +965,48 @@ final logoWidget = await buildLogoWidget();
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [ children: [
pw.Text('VENDEURS', style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold, font: regularFont)), pw.Text('VENDEURS',
style: pw.TextStyle(
fontSize: 10,
fontWeight: pw.FontWeight.bold,
font: regularFont)),
pw.SizedBox(height: 3), pw.SizedBox(height: 3),
pw.Row( pw.Row(
children: [ children: [
pw.Expanded( pw.Expanded(
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment:
pw.CrossAxisAlignment.start,
children: [ children: [
pw.Text('Initiateur:', style: tinyTextStyle), pw.Text('Initiateur:',
style: tinyTextStyle),
pw.Text( pw.Text(
commandeur != null ? '${commandeur.name} ${commandeur.lastName ?? ''}'.trim() : 'N/A', commandeur != null
style: pw.TextStyle(fontSize: 9, font: regularFont), ? '${commandeur.name} ${commandeur.lastName ?? ''}'
.trim()
: 'N/A',
style: pw.TextStyle(
fontSize: 9,
font: regularFont),
), ),
], ],
), ),
), ),
pw.Expanded( pw.Expanded(
child: pw.Column( child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment:
pw.CrossAxisAlignment.start,
children: [ children: [
pw.Text('Validateur:', style: tinyTextStyle), pw.Text('Validateur:',
style: tinyTextStyle),
pw.Text( pw.Text(
validateur != null ? '${validateur.name} ${validateur.lastName ?? ''}'.trim() : 'N/A', validateur != null
style: pw.TextStyle(fontSize: 9, font: regularFont), ? '${validateur.name} ${validateur.lastName ?? ''}'
.trim()
: 'N/A',
style: pw.TextStyle(
fontSize: 9,
font: regularFont),
), ),
], ],
), ),
@ -925,20 +1021,35 @@ final logoWidget = await buildLogoWidget();
// Signatures // Signatures
pw.Row( pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, mainAxisAlignment:
pw.MainAxisAlignment.spaceBetween,
children: [ children: [
pw.Column( pw.Column(
children: [ children: [
pw.Text('Vendeur', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, font: regularFont)), pw.Text('Vendeur',
style: pw.TextStyle(
fontSize: 9,
fontWeight: pw.FontWeight.bold,
font: regularFont)),
pw.SizedBox(height: 15), pw.SizedBox(height: 15),
pw.Container(width: 70, height: 1, color: PdfColors.black), pw.Container(
width: 70,
height: 1,
color: PdfColors.black),
], ],
), ),
pw.Column( pw.Column(
children: [ children: [
pw.Text('Client', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, font: regularFont)), pw.Text('Client',
style: pw.TextStyle(
fontSize: 9,
fontWeight: pw.FontWeight.bold,
font: regularFont)),
pw.SizedBox(height: 15), pw.SizedBox(height: 15),
pw.Container(width: 70, height: 1, color: PdfColors.black), pw.Container(
width: 70,
height: 1,
color: PdfColors.black),
], ],
), ),
], ],
@ -989,13 +1100,21 @@ final logoWidget = await buildLogoWidget();
border: pw.Border.all(color: PdfColors.black, width: 2), border: pw.Border.all(color: PdfColors.black, width: 2),
), ),
child: pw.Center( child: pw.Center(
child: pw.Text('X', style: pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold, font: regularFont)), child: pw.Text('X',
style: pw.TextStyle(
fontSize: 12,
fontWeight: pw.FontWeight.bold,
font: regularFont)),
), ),
), ),
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
pw.Transform.rotate( pw.Transform.rotate(
angle: 1.5708, angle: 1.5708,
child: pw.Text('DÉCOUPER ICI', style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold, font: regularFont)), child: pw.Text('DÉCOUPER ICI',
style: pw.TextStyle(
fontSize: 10,
fontWeight: pw.FontWeight.bold,
font: regularFont)),
), ),
pw.SizedBox(height: 10), pw.SizedBox(height: 10),
pw.Container( pw.Container(
@ -1006,7 +1125,11 @@ final logoWidget = await buildLogoWidget();
border: pw.Border.all(color: PdfColors.black, width: 2), border: pw.Border.all(color: PdfColors.black, width: 2),
), ),
child: pw.Center( child: pw.Center(
child: pw.Text('X', style: pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold, font: regularFont)), child: pw.Text('X',
style: pw.TextStyle(
fontSize: 12,
fontWeight: pw.FontWeight.bold,
font: regularFont)),
), ),
), ),
], ],
@ -1149,11 +1272,13 @@ final logoWidget = await buildLogoWidget();
dynamic blobDynamic = logoRaw; dynamic blobDynamic = logoRaw;
bytes = blobDynamic.toBytes(); bytes = blobDynamic.toBytes();
} else { } else {
throw Exception("Format de logo non supporté: ${logoRaw.runtimeType}"); throw Exception(
"Format de logo non supporté: ${logoRaw.runtimeType}");
} }
final imageLogo = pw.MemoryImage(bytes); final imageLogo = pw.MemoryImage(bytes);
return pw.Container(width: 200, height: 120, child: pw.Image(imageLogo)); return pw.Container(
width: 200, height: 120, child: pw.Image(imageLogo));
} catch (e) { } catch (e) {
print('Erreur chargement logo BDD: $e'); print('Erreur chargement logo BDD: $e');
} }
@ -1163,7 +1288,8 @@ final logoWidget = await buildLogoWidget();
final logoWidget = await buildLogoWidget(); final logoWidget = await buildLogoWidget();
pw.Widget buildEnteteFactureInfos() { pw.Widget buildEnteteFactureInfos() {
final infosAUtiliser = infosFacture.isNotEmpty ? infosFacture : infosFactureDefaut; final infosAUtiliser =
infosFacture.isNotEmpty ? infosFacture : infosFactureDefaut;
return pw.Column( return pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.start,
@ -1182,7 +1308,6 @@ pw.Widget buildEnteteFactureInfos() {
); );
} }
pdf.addPage( pdf.addPage(
pw.Page( pw.Page(
pageFormat: PdfPageFormat.a4, // Mode portrait pageFormat: PdfPageFormat.a4, // Mode portrait
@ -1566,7 +1691,8 @@ pw.Widget buildEnteteFactureInfos() {
pw.SizedBox(width: 20), pw.SizedBox(width: 20),
pw.Container( pw.Container(
width: 80, width: 80,
child: pw.Text('${NumberFormat('#,##0', 'fr_FR').format(sousTotal)}', child: pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(sousTotal)}',
style: normalTextStyle, style: normalTextStyle,
textAlign: pw.TextAlign.right), textAlign: pw.TextAlign.right),
), ),
@ -2001,7 +2127,6 @@ pw.Widget buildEnteteFactureInfos() {
Future<void> _generateReceipt( Future<void> _generateReceipt(
Commande commande, PaymentMethod payment) async { Commande commande, PaymentMethod payment) async {
final details = await _database.getDetailsCommande(commande.id!); final details = await _database.getDetailsCommande(commande.id!);
final client = await _database.getClientById(commande.clientId); final client = await _database.getClientById(commande.clientId);
final commandeur = commande.commandeurId != null final commandeur = commande.commandeurId != null
@ -2242,7 +2367,8 @@ pw.Widget buildEnteteFactureInfos() {
children: [ children: [
pw.Text('SOUS-TOTAL:', pw.Text('SOUS-TOTAL:',
style: const pw.TextStyle(fontSize: 8)), style: const pw.TextStyle(fontSize: 8)),
pw.Text('${NumberFormat('#,##0', 'fr_FR').format(sousTotal)} MGA', pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(sousTotal)} MGA',
style: const pw.TextStyle(fontSize: 8)), style: const pw.TextStyle(fontSize: 8)),
], ],
), ),
@ -2253,7 +2379,8 @@ pw.Widget buildEnteteFactureInfos() {
pw.Text('REMISES:', pw.Text('REMISES:',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 8, color: PdfColors.orange)), fontSize: 8, color: PdfColors.orange)),
pw.Text('-${NumberFormat('#,##0', 'fr_FR').format(totalRemises)} MGA', pw.Text(
'-${NumberFormat('#,##0', 'fr_FR').format(totalRemises)} MGA',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 8, color: PdfColors.orange)), fontSize: 8, color: PdfColors.orange)),
], ],
@ -2266,7 +2393,8 @@ pw.Widget buildEnteteFactureInfos() {
pw.Text('CADEAUX ($nombreCadeaux):', pw.Text('CADEAUX ($nombreCadeaux):',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 8, color: PdfColors.green700)), fontSize: 8, color: PdfColors.green700)),
pw.Text('-${NumberFormat('#,##0', 'fr_FR').format(totalCadeaux)} MGA', pw.Text(
'-${NumberFormat('#,##0', 'fr_FR').format(totalCadeaux)} MGA',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 8, color: PdfColors.green700)), fontSize: 8, color: PdfColors.green700)),
], ],
@ -2282,7 +2410,8 @@ pw.Widget buildEnteteFactureInfos() {
pw.Text('TOTAL:', pw.Text('TOTAL:',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 9, fontWeight: pw.FontWeight.bold)), fontSize: 9, fontWeight: pw.FontWeight.bold)),
pw.Text('${NumberFormat('#,##0', 'fr_FR').format(commande.montantTotal)} MGA', pw.Text(
'${NumberFormat('#,##0', 'fr_FR').format(commande.montantTotal)} MGA',
style: pw.TextStyle( style: pw.TextStyle(
fontSize: 9, fontWeight: pw.FontWeight.bold)), fontSize: 9, fontWeight: pw.FontWeight.bold)),
], ],
@ -2974,7 +3103,8 @@ pw.Widget buildEnteteFactureInfos() {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color:
Colors.black.withOpacity(0.1),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@ -2985,8 +3115,10 @@ pw.Widget buildEnteteFactureInfos() {
Icons.payment, Icons.payment,
color: Colors.green.shade600, color: Colors.green.shade600,
), ),
onPressed: () => _showPaymentOptions(commande), onPressed: () =>
tooltip: 'Générer le ticket de la commande', _showPaymentOptions(commande),
tooltip:
'Générer le ticket de la commande',
), ),
), ),
const SizedBox( const SizedBox(
@ -3067,8 +3199,8 @@ pw.Widget buildEnteteFactureInfos() {
CommandeActions( CommandeActions(
commande: commande, commande: commande,
onStatutChanged: _updateStatut, onStatutChanged: _updateStatut,
onGenerateBonLivraison:_generateBon_lifraisonWithPasswordVerification onGenerateBonLivraison:
), _generateBon_lifraisonWithPasswordVerification),
], ],
), ),
), ),

28
lib/Views/historique.dart

@ -92,7 +92,9 @@ class _HistoriquePageState extends State<HistoriquePage> {
final pointDeVenteId = _userController.pointDeVenteId; final pointDeVenteId = _userController.pointDeVenteId;
final commandes = pointDeVenteId == 0 final commandes = pointDeVenteId == 0
? allCommandes ? allCommandes
: allCommandes.where((cmd) => cmd.pointDeVenteId == pointDeVenteId).toList(); : allCommandes
.where((cmd) => cmd.pointDeVenteId == pointDeVenteId)
.toList();
setState(() { setState(() {
_commandes.clear(); _commandes.clear();
@ -146,7 +148,8 @@ class _HistoriquePageState extends State<HistoriquePage> {
_filteredCommandes.clear(); _filteredCommandes.clear();
for (var commande in _commandes) { for (var commande in _commandes) {
if (pointDeVenteId != 0 && commande.pointDeVenteId != pointDeVenteId) continue; if (pointDeVenteId != 0 && commande.pointDeVenteId != pointDeVenteId)
continue;
bool matchesSearch = searchText.isEmpty || bool matchesSearch = searchText.isEmpty ||
commande.clientNom!.toLowerCase().contains(searchText) || commande.clientNom!.toLowerCase().contains(searchText) ||
commande.clientPrenom!.toLowerCase().contains(searchText) || commande.clientPrenom!.toLowerCase().contains(searchText) ||
@ -641,10 +644,25 @@ class _HistoriquePageState extends State<HistoriquePage> {
), ),
child: const Icon(Icons.shopping_bag, size: 20), child: const Icon(Icons.shopping_bag, size: 20),
), ),
title: Text( title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
detail.produitNom ?? 'Produit inconnu', detail.produitNom ?? 'Produit inconnu',
style: style: const TextStyle(
const TextStyle(fontWeight: FontWeight.w500), fontWeight: FontWeight.w500),
),
if (detail.produitImei != null) ...[
const SizedBox(height: 4),
Text(
'IMEI: ${detail.produitImei}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
],
), ),
subtitle: Text( subtitle: Text(
'${detail.quantite} x ${NumberFormat('#,##0.00', 'fr_FR').format(detail.prixUnitaire)} MGA', '${detail.quantite} x ${NumberFormat('#,##0.00', 'fr_FR').format(detail.prixUnitaire)} MGA',

Loading…
Cancel
Save